Python 中的子進程是什麼? [5 用法示例]

已發表: 2021-05-23

子流程讓您可以在全新的層面上與操作系統進行交互。

我們的計算機一直在運行子進程。 事實上,僅僅通過閱讀本文,您就可以運行許多進程,例如網絡管理器或 Internet 瀏覽器本身。

很酷的一點是,我們在計算機上執行的任何操作都涉及調用子進程。 即使我們在 python 中編寫一個簡單的“hello world”腳本,這仍然是正確的。

即使您已經學習了一段時間的編程,子流程的概念也可能看起來很模糊。 本文將深入介紹子進程的主要概念,以及如何使用Python子進程標準庫。

在本教程結束時,您將:

  • 理解子進程的概念
  • 已經學習了Python子進程庫的基礎知識
  • 用有用的例子練習你的 Python 技能

讓我們進入它

子進程的概念

從廣義上講,子進程是由另一個進程創建的計算機進程。

我們可以將子進程視為一棵樹,其中每個父進程都有子進程在其後面運行。 我知道這可能很令人困惑,但讓我們用一個簡單的圖形來看看它。

我們可以通過多種方式將計算機上運行的進程可視化。 例如,在 UNIX(Linux 和 MAC)中,我們有 htop,它是一個交互式進程查看器。

Htop進程查看器

樹模式是查看正在運行的子進程的最有用的工具。 我們可以用F5激活它。

如果我們仔細查看命令部分,我們會注意到在我們的計算機上運行的進程的結構。

htop進程結構
這一切都從/sbin/init開始,它是啟動我們計算機上每個進程的命令。 從那一點,我們可以看到其他進程的開始,比如xfce4-screenshoterxfce4-terminal (這有助於更多的子進程)

看看 Windows,我們有神話般的任務管理器,它在殺死我們機器上的那些崩潰程序時很有用。

Windows 任務管理器

現在我們有了一個清晰的概念。 讓我們看看如何在 Python 中實現子流程。

Python 中的子進程

Python 中的子進程是 Python 腳本委託給操作系統 (OS) 的任務。

子進程庫允許我們直接從 Python 執行和管理子進程。 這涉及使用標準輸入stdin 、標準輸出stdout和返回代碼。

我們不必用 PIP 安裝它,因為它是 Python 標準庫的一部分。

因此,我們可以通過導入模塊開始在 python 中使用子進程。

 import subprocess # Using the module ....

注意:要繼續閱讀本文,您應該擁有 Python 3.5 +

要檢查您當前擁有的 python 版本,只需運行。

 ❯ python --version Python 3.9.5 # My result

如果您獲得的 Python 版本是 2.x,您可以使用以下命令

python3 --version

繼續該主題,子進程庫背後的主要思想是能夠通過直接從 Python 解釋器執行我們想要的任何命令來與操作系統交互。

這意味著我們可以做任何我們想做的事情,只要我們的操作系統允許我們(並且只要您不刪除您的根文件系統)。

讓我們通過創建一個列出當前目錄的文件的簡單腳本來看看如何使用它。

第一個子流程應用

首先,讓我們創建一個文件list_dir.py 。 這將是我們要試驗列出文件的文件。

 touch list_dir.py

現在讓我們打開該文件並使用以下代碼。

 import subprocess subprocess.run('ls')

首先,我們導入 subprocess 模塊,然後使用運行的函數run ,我們將命令作為參數傳遞。

這個函數是在 Python 3.5 中引入的,作為 subprocess.Popen 的友好快捷方式。 subprocess.run 函數允許我們運行命令並等待它完成,與 Popen 相比,我們可以選擇稍後調用通信。

談到代碼輸出, ls是一個 UNIX 命令,它列出您所在目錄的文件。因此,如果您運行此命令,您將獲得當前目錄中存在的文件列表。

 ❯ python list_dir.py example.py LICENSE list_dir.py README.md

注意:注意,如果您使用的是 Windows,則需要使用不同的命令。 例如,您可以使用“dir”代替“ls

這可能看起來太簡單了,你是對的。 您想全面了解 shell 為您帶來的所有功能。 因此,讓我們學習如何使用 subprocess 將參數傳遞給 shell。

例如,要列出隱藏文件(以點開頭的文件),並列出文件的所有元數據,我們編寫以下代碼。

 import subprocess # subprocess.run('ls') # Simple command subprocess.run('ls -la', shell=True)

我們將此命令作為字符串運行並使用參數shell 。 這意味著我們在子進程執行開始時調用一個 shell,並且命令參數由 shell 直接解釋。

但是,使用shell=True有很多缺點,最糟糕的是可能存在安全漏洞。 您可以在官方文檔中閱讀有關它們的信息。

將命令傳遞給 run 函數的最佳方法是使用一個列表,其中lst[0]是要調用的命令(在本例中為 ls),而lst[n]是該命令的參數。

如果我們這樣做,我們的代碼將如下所示。

 import subprocess # subprocess.run('ls') # Simple command # subprocess.run('ls -la', shell=True) # Dangerous command subprocess.run(['ls', '-la'])

如果我們想將子進程的標準輸出存儲在一個變量中,我們可以通過將參數 capture_output 設置為 true 來實現。

 list_of_files = subprocess.run(['ls', '-la'], capture_output=True) print(list_of_files.stdout) ❯ python list_dir.py b'total 36\ndrwxr-xr-x 3 daniel daniel 4096 may 20 21:08 .\ndrwx------ 30 daniel daniel 4096 may 20 18:03 ..\n-rw-r--r-- 1 daniel daniel 55 may 20 20:18 example.py\ndrwxr-xr-x 8 daniel daniel 4096 may 20 17:31 .git\n-rw-r--r-- 1 daniel daniel 2160 may 17 22:23 .gitignore\n-rw-r--r-- 1 daniel daniel 271 may 20 19:53 internet_checker.py\n-rw-r--r-- 1 daniel daniel 1076 may 17 22:23 LICENSE\n-rw-r--r-- 1 daniel daniel 216 may 20 22:12 list_dir.py\n-rw-r--r-- 1 daniel daniel 22 may 17 22:23 README.md\n'

要訪問進程的輸出,我們使用實例屬性stdout

在這種情況下,我們希望將輸出存儲為字符串,而不是字節,我們可以通過將 text 參數設置為 true 來實現。

 list_of_files = subprocess.run(['ls', '-la'], capture_output=True, text=True) print(list_of_files.stdout) ❯ python list_dir.py total 36 drwxr-xr-x 3 daniel daniel 4096 may 20 21:08 . drwx------ 30 daniel daniel 4096 may 20 18:03 .. -rw-r--r-- 1 daniel daniel 55 may 20 20:18 example.py drwxr-xr-x 8 daniel daniel 4096 may 20 17:31 .git -rw-r--r-- 1 daniel daniel 2160 may 17 22:23 .gitignore -rw-r--r-- 1 daniel daniel 271 may 20 19:53 internet_checker.py -rw-r--r-- 1 daniel daniel 1076 may 17 22:23 LICENSE -rw-r--r-- 1 daniel daniel 227 may 20 22:14 list_dir.py -rw-r--r-- 1 daniel daniel 22 may 17 22:23 README.md

完美,現在我們了解了子流程庫的基礎知識,是時候繼續介紹一些使用示例了。

Python中子進程的使用示例

在本節中,我們將回顧 subprocess 庫的一些實際用途。 你可以在這個 Github 存儲庫中查看所有這些。

程序檢查器

該庫的主要用途之一是能夠進行簡單的操作系統操作。

例如,一個檢查程序是否安裝的簡單腳本。 在 Linux 中,我們可以使用which命令執行此操作。

 '''Program checker with subprocess''' import subprocess program = 'git' process = subprocess. run(['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'The program "{program}" is installed') print(f'The location of the binary is: {process.stdout}') else: print(f'Sorry the {program} is not installed') print(process.stderr)

注意:在 UNIX 中,當命令成功時,其狀態碼為 0。否則,在執行過程中出現問題

由於我們沒有使用shell=True參數,我們可以安全地獲取用戶輸入。 此外,我們可以檢查輸入是否是具有正則表達式模式的有效程序。

 import subprocess import re programs = input('Separe the programs with a space: ').split() secure_pattern = '[\w\d]' for program in programs: if not re.match(secure_pattern, program): print("Sorry we can't check that program") continue process = subprocess. run( ['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'The program "{program}" is installed') print(f'The location of the binary is: {process.stdout}') else: print(f'Sorry the {program} is not installed') print(process.stderr) print('\n')

在這種情況下,我們從用戶那裡獲取程序並使用正則表達式來證明程序字符串僅包含字母和數字。 我們使用 for 循環檢查每個程序是否存在。

Python 中的簡單 Grep

您的朋友Tom在一個文本文件和另一個大文件中有一個模式列表,他想在其中獲取每個模式的匹配數。 他會花幾個小時為每個模式運行 grep 命令。

幸運的是,你知道如何用 Python 解決這個問題,你會在幾秒鐘內幫助他完成這個任務。

 import subprocess patterns_file = 'patterns.txt' readfile = 'romeo-full.txt' with open(patterns_file, 'r') as f: for pattern in f: pattern = pattern.strip() process = subprocess.run( ['grep', '-c', f'{pattern}', readfile], capture_output=True, text=True) if int(process.stdout) == 0: print( f'The pattern "{pattern}" did not match any line of {readfile}') continue print(f'The pattern "{pattern}" matched {process.stdout.strip()} times')

看看這個文件,我們定義了兩個變量,它們是我們想要使用的文件名。 然後我們打開包含所有模式的文件並迭代它們。 接下來,我們調用一個子進程,它運行帶有“-c”標誌(表示計數)的 grep 命令,並使用條件確定匹配的輸出。

如果你運行這個文件(記住你可以從 Github repo 下載文本文件)

使用子進程設置 virtualenv

使用 Python 可以做的最酷的事情之一就是流程自動化。 這種腳本每周可以為您節省數小時的時間。

例如,我們將創建一個安裝腳本,為我們創建一個虛擬環境,並嘗試在當前目錄中找到一個requirements.txt文件來安裝所有依賴項。

 import subprocess from pathlib import Path VENV_NAME = '.venv' REQUIREMENTS = 'requirements.txt' process1 = subprocess.run(['which', 'python3'], capture_output=True, text=True) if process1.returncode != 0: raise OSError('Sorry python3 is not installed') python_bin = process1.stdout.strip() print(f'Python found in: {python_bin}') process2 = subprocess.run('echo "$SHELL"', shell=True, capture_output=True, text=True) shell_bin = process2.stdout.split('/')[-1] create_venv = subprocess.run([python_bin, '-m', 'venv', VENV_NAME], check=True) if create_venv.returncode == 0: print(f'Your venv {VENV_NAME} has been created') pip_bin = f'{VENV_NAME}/bin/pip3' if Path(REQUIREMENTS).exists(): print(f'Requirements file "{REQUIREMENTS}" found') print('Installing requirements') subprocess.run([pip_bin, 'install', '-r', REQUIREMENTS]) print('Process completed! Now activate your environment with "source .venv/bin/activate"') else: print("No requirements specified ...")

在這種情況下,我們使用多個進程並在我們的 python 腳本中解析我們需要的數據。 我們還使用了 pathlib 庫,它允許我們判斷requirements.txt文件是否存在。

如果您運行 python 文件,您將獲得一些關於操作系統正在發生的事情的有用消息。

 ❯ python setup.py Python found in: /usr/bin/python3 Your venv .venv has been created Requirements file "requirements.txt" found Installing requirements Collecting asgiref==3.3.4 ....... Process completed! Now activate your environment with "source .venv/bin/activate"

請注意,我們從安裝過程中獲得輸出,因為我們沒有將標準輸出重定向到變量。

運行另一種編程語言

我們可以使用 python 運行其他編程語言並從這些文件中獲取輸出。 這是可能的,因為子進程直接與操作系統交互。

例如,讓我們用 C++ 和 Java 創建一個 hello world 程序。 為了執行以下文件,您需要安裝 C++ 和 Java 編譯器。

helloworld.cpp

 #include <iostream> int main(){ std::cout << "This is a hello world in C++" << std::endl; return 0; }


你好世界

class HelloWorld{ public static void main(String args[]){ System.out.println("This is a hello world in Java"); } }


我知道與簡單的 Python one-liner 相比,這似乎是很多代碼,但這僅用於測試目的。

我們將創建一個 Python 腳本,該腳本運行目錄中的所有 C++ 和 Java 文件。 首先,我們希望根據文件擴展名獲取文件列表,而 glob 允許我們輕鬆完成!

 from glob import glob # Gets files with each extension java_files = glob('*.java') cpp_files = glob('*.cpp')

之後,我們可以開始使用子進程來執行每種類型的文件。

 for file in cpp_files: process = subprocess.run(f'g++ {file} -o out; ./out', shell=True, capture_output=True, text=True) output = process.stdout.strip() + ' BTW this was runned by Python' print(output) for file in java_files: without_ext = file.strip('.java') process = subprocess.run(f'java {file}; java {without_ext}',shell=True, capture_output=True, text=True) output = process.stdout.strip() + ' A Python subprocess runned this :)' print(output)

一個小技巧就是使用字符串函數strip來修改輸出,只得到我們需要的。

注意:運行大型 Java 或 C++ 文件時要小心,因為我們將它們的輸出加載到內存中,這可能會產生內存洩漏。

打開外部程序

我們可以通過子進程調用其他程序的二進製文件位置來運行其他程序。

讓我們打開我喜歡的網絡瀏覽器勇敢嘗試一下。

 import subprocess subprocess.run('brave')

這將打開一個瀏覽器實例,或者如果您已經運行了瀏覽器,則只是另一個選項卡。

打開瀏覽器

與任何其他接受標誌的程序一樣,我們可以使用它們來產生所需的行為。

 import subprocess subprocess.run(['brave', '--incognito'])

隱身標誌

總結

子進程是由另一個進程創建的計算機進程。 我們可以使用 htop 和任務管理器等工具檢查計算機正在運行的進程。

Python 有自己的庫來處理子進程。 目前, run函數為我們提供了一個簡單的界面來創建和管理子流程。

我們可以用它們創建任何類型的應用程序,因為我們直接與操作系統交互。

最後,請記住,最好的學習方法是創建您想使用的東西。