Python多進程(1)——subprocess與Popen()


  Python多進程方面涉及的模塊主要包括:

 

  本文主要介紹 subprocess 模塊及其提供的 Popen 類,以及如何使用該構造器在一個進程中創建新的子進程。此外,還會簡要介紹 subprocess 模塊提供的其他方法與屬性,這些功能上雖然沒有 Popen 強大的工具,在某些情況下卻非常方便和高效。

  本文的目錄如下:

  1. subprocess.Popen 類

  2. Popen 對象的屬性

  3. Popen 對象的方法

  4. subprocess模塊的其他簡便方法

  5. subprocess模塊的其他屬性

  6. subprocess模塊定義的異常

 

subprocess.Popen 類

  通過調用:

subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)

  創建並返回一個子進程,並在這個進程中執行指定的程序。

  實例化 Popen 可以通過許多參數詳細定制子進程的環境,但是只有一個參數是必須的,即位置參數 args ,下面也會詳細介紹剩余的具名參數。

參數介紹:

  • args:要執行的命令或可執行文件的路徑。一個由字符串組成的序列(通常是列表),列表的第一個元素是可執行程序的路徑,剩下的是傳給這個程序的參數,如果沒有要傳給這個程序的參數,args 參數可以僅僅是一個字符串。
  • bufsize:控制 stdin, stdout, stderr 等參數指定的文件的緩沖,和打開文件的 open()函數中的參數 bufsize 含義相同。
  • executable:如果這個參數不是 None,將替代參數 args 作為可執行程序;
  • stdin:指定子進程的標准輸入;
  • stdout:指定子進程的標准輸出;
  • stderr:指定子進程的標准錯誤輸出;

  對於 stdin, stdoutstderr 而言,如果他們是 None(默認情況),那么子進程使用和父進程相同的標准流文件。

  父進程如果想要和子進程通過 communicate() 方法通信,對應的參數必須是 subprocess.PIPE(見下文例4);

  當然 stdin, stdoutstderr 也可以是已經打開的 file 對象,前提是以合理的方式打開,比如 stdin 對應的文件必須要可讀等。 

  • preexec_fn:默認是None,否則必須是一個函數或者可調用對象,在子進程中首先執行這個函數,然后再去執行為子進程指定的程序或Shell。
  • close_fds:布爾型變量,為 True 時,在子進程執行前強制關閉所有除 stdin,stdout和stderr外的文件;
  • shell:布爾型變量,明確要求使用shell運行程序,與參數 executable 一同指定子進程運行在什么 Shell 中——如果executable=None 而 shell=True,則使用 /bin/sh 來執行 args 指定的程序;也就是說,Python首先起一個shell,再用這個shell來解釋指定運行的命令。
  • cwd:代表路徑的字符串,指定子進程運行的工作目錄,要求這個目錄必須存在;
  • env:字典,鍵和值都是為子進程定義環境變量的字符串;
  • universal_newline:布爾型變量,為 True 時,stdout 和 stderr 以通用換行(universal newline)模式打開,
  • startupinfo:見下一個參數;
  • creationfalgs:最后這兩個參數是Windows中才有的參數,傳遞給Win32的CreateProcess API調用。

  同 Linux 中創建子進程類似,父進程創建完子進程后,並不會自動等待子進程執行,父進程在子進程之前推出將導致子進程成為孤兒進程,孤兒進程統一由 init 進程接管,負責其終止后的回收工作。

  如果父進程在子進程之后終止,但子進程終止時父進程沒有進行最后的回收工作,子進程殘留的數據結構稱為僵屍進程。大量僵屍進程將耗費系統資源,因此父進程及時等待和回收子進程是必要的,除非能夠確認自己比子進程先終止,從而將回收工作過渡給 init 進程。

  這個等待和回收子進程的操作就是wait()函數,下文中將會介紹。

例1:

  創建一個子進程,然后執行一個簡單的命令

>>> import subprocess
>>> p = subprocess.Popen('ls -l', shell=True)
>>> total 164
-rw-r--r--  1 root root   133 Jul  4 16:25 admin-openrc.sh
-rw-r--r--  1 root root   268 Jul 10 15:55 admin-openrc-v3.sh
...
>>> p.returncode
>>> p.wait()
0
>>> p.returncode
0

  這里也可以使用 p = subprocess.Popen(['ls', '-cl']) 來創建子進程。

 

Popen 對象的屬性

  Popen創建的子進程有一些有用的屬性,假設 p 是 Popen 創建的子進程,p 的屬性包括:

1. 

p.pid

  子進程的PID。

 2. 

p.returncode

  該屬性表示子進程的返回狀態,returncode可能有多重情況:

  • None —— 子進程尚未結束;
  • ==0 —— 子進程正常退出;
  • > 0—— 子進程異常退出,returncode對應於出錯碼;
  • < 0—— 子進程被信號殺掉了。

 3. 

p.stdin, p.stdout, p.stderr

  子進程對應的一些初始文件,如果調用Popen()的時候對應的參數是subprocess.PIPE,則這里對應的屬性是一個包裹了這個管道的 file 對象,

  

Popen 對象的方法

1.

p.poll()

  檢查子進程  p 是否已經終止,返回 p.returncode 屬性 (參考下文 Popen 對象的屬性);

2.

p.wait()

  等待子進程 p 終止,返回 p.returncode 屬性;

  注意:

    wait() 立即阻塞父進程,直到子進程結束!

3.

p.communicate(input=None)

  和子進程 p 交流,將參數 input (字符串)中的數據發送到子進程的 stdin,同時從子進程的 stdout 和 stderr 讀取數據,直到EOF。

  返回值:

    二元組 (stdoutdata, stderrdata) 分別表示從標准出和標准錯誤中讀出的數據。

  父進程調用 p.communicate() 和子進程通信有以下限制:

  (1) 只能通過管道和子進程通信,也就是說,只有調用 Popen() 創建子進程的時候參數 stdin=subprocess.PIPE,才能通過 p.communicate(input) 向子進程的 stdin 發送數據;只有參數 stout 和 stderr 也都為 subprocess.PIPE ,才能通過p.communicate() 從子進程接收數據,否則接收到的二元組中,對應的位置是None。

  (2)父進程從子進程讀到的數據緩存在內存中,因此commucate()不適合與子進程交換過大的數據。

  注意:

    communicate() 立即阻塞父進程,直到子進程結束!

4.

p.send_signal(signal)

  向子進程發送信號 signal

5.

p.terminate()

  終止子進程 p ,等於向子進程發送 SIGTERM 信號;

6.

p.kill()

  殺死子進程 p ,等於向子進程發送 SIGKILL 信號;

 

subprocess模塊的其他方法

1. 

subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False)

  父進程直接創建子進程執行程序,然后等待子進程完成

  返回值:

    call() 返回子進程的 退出狀態 即 child.returncode 屬性;

2. 

subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False)

  父進程直接創建子進程執行程序,然后等待子進程完成,具體可以使用的參數,參考上文 Popen 類的介紹。

  返回值:

    無論子進程是否成功,該函數都返回 0;但是 

  如果子進程的退出狀態不是0,check_call() 拋出異常 CalledProcessError,異常對象中包含了 child.returncode 對應的返回碼。

例2:

  check_call()正常與錯誤執行命令

>>> p = subprocess.check_call(['ping' ,'-c', '2', 'www.baidu.com'])
PING www.a.shifen.com (220.181.111.188) 56(84) bytes of data.
64 bytes from 220.181.111.188: icmp_seq=1 ttl=42 time=37.4 ms
64 bytes from 220.181.111.188: icmp_seq=2 ttl=42 time=37.3 ms

--- www.a.shifen.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 37.335/37.410/37.486/0.207 ms
>>> print p
0
>>> p = subprocess.check_call(['ping' ,'-c', '4', 'www.!@$#@!(*^.com'])
ping: unknown host www.!@$#@!(*^.com
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/subprocess.py", line 540, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['ping', '-c', '4', 'www.!@$#@!(*^.com']' returned non-zero exit status 2
>>> print p
0

  

3.

subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False)

  父進程直接創建子進程執行程序,以字符串的形式返回子進程的輸出。

  返回值:

    字符串形式的子進程的輸出結果,但是,

  如果子進程的 退出狀態 不是0,那么拋出異常 CalledProcessError,異常對象中包含了 child.returncode 對應的返回碼。

  注意:

    check_output() 的函數簽名中沒有參數 stdout,調用該方法時,子進程的輸出默認就返回給父進程。

例3:

  check_output() 調用的子進程正常與錯誤退出

>>> subprocess.check_output(["echo", "Hello World!"])
'Hello World!\n'

>>> subprocess.check_output("exit 1", shell=True)
Traceback (most recent call last):
   ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

 

注意:

  使用上面提到的三個方法:call()、check_call() 和 check_output() 時,盡量不要將參數 stderr stdout 設置為 subprocess.PIPE,這幾個函數默認都會等待子進程完成,子進程產生大量的輸出數據如果造成管道堵塞,父進程再等待子進程完成可能造成死鎖。

  

subprocess模塊的其他屬性

subprocess.PIPE

  調用本模塊提供的若干函數時,作為 std* 參數的值,為標准流文件打開一個管道。

例4:

  使用管道連接標准流文件

import subprocess
child1 = subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE)
child2 = subprocess.Popen(['wc', '-l'], stdin=child1.stdout, stdout=subprocess.PIPE)
out = child2.communicate()
child1.wait()
child2.wait()
print(out)

  這里將子進程 child1 的標准輸出作為子進程 child2 的標准輸入,父進程通過 communicate() 讀取 child2 的標准輸出后打印。

 

subprocess.STDOUT

  調用本模塊提供的若干函數時,作為 stderr 參數的值,將子進程的標准錯誤輸出打印到標准輸出。

 

subprocess模塊定義的異常

exception subprocess.CalledProcessError

  (1)什么時候可能拋出該異常:調用 check_call() 或 check_output() ,子進程的退出狀態不為 0 時。

  (2)該異常包含以下信息:

  • returncode:子進程的退出狀態;
  • cmd:創建子進程時指定的命令;
  • output:如果是調用 check_output() 時拋出的該異常,這里包含子進程的輸出,否則該屬性為None。

 

  總結

  本文介紹了Python subprocess的基本用法,使用 Popen 可以在Python進程中創建子進程,如果只對子進程的執行退出狀態感興趣,可以調用 subprocess.call() 函數,如果想通過異常處理機制解決子進程異常退出的情形,可以考慮使用 subprocess.check_call() 和 subprocess.check_output。如果希望獲得子進程的輸出,可以調用 subprocess.check_output(),但 Popen() 無疑是功能最強大的。

  subprocess模塊的缺陷在於默認提供的父子進程間通信手段有限,只有管道;同時創建的子進程專門用來執行外部的程序或命令。

  Linux下進程間通信的手段很多,子進程也完全可能從創建之后繼續調用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM