¿Qué es un subproceso en Python? [5 Usage Examples]

Los subprocesos te permiten interactuar a un nivel totalmente nuevo con el Sistema Operativo.

Nuestra computadora ejecuta subprocesos todo el tiempo. De hecho, con solo leer este artículo, está ejecutando muchos procesos, como un administrador de red o el navegador de Internet.

Lo bueno de esto es que cualquier acción que hagamos en nuestra computadora implica invocar un subproceso. Eso sigue siendo cierto incluso si estamos escribiendo un script simple de «hola mundo» en python.

El concepto del subproceso puede parecer oscuro incluso si ha estado aprendiendo programación por un tiempo. Este artículo analizará en profundidad el concepto principal del subproceso y cómo usar Python biblioteca estándar de subprocesos.

Al final de este tutorial, usted:

  • Comprender el concepto de subproceso
  • Haber aprendido los conceptos básicos de la biblioteca de subprocesos de Python
  • Practicaste tus habilidades con Python con ejemplos útiles

entremos en eso

El concepto de subproceso

En términos generales, un subproceso es un proceso de computadora creado por otro proceso.

Podemos pensar en un subproceso como un árbol, en el que cada proceso principal tiene procesos secundarios ejecutándose detrás de él. Sé que esto puede ser bastante confuso, pero veámoslo con un gráfico simple.

Hay varias formas en que podemos visualizar el proceso que se ejecuta en nuestra computadora. Por ejemplo, en UNIX (Linux y MAC) tenemos arriba, que es un visor de procesos interactivo.

El modo de árbol es la herramienta más útil para echar un vistazo a los subprocesos en ejecución. Podemos activarlo con F5.

Si observamos de cerca la sección de comandos, podemos notar la estructura de los procesos que se ejecutan en nuestra computadora.

Todo comienza con /sbin/iniciar que es el comando que inicia cada proceso en nuestra computadora. Desde ese punto, podemos ver el comienzo de otros procesos como xfce4-screenshoter y xfce4-terminal (lo que conduce a aún más subprocesos)

Echando un vistazo a Windows, tenemos el mítico administrador de tareas lo que resulta útil a la hora de acabar con los programas que fallan en nuestra máquina.

Ahora tenemos un concepto claro como el cristal. Veamos cómo podemos implementar subprocesos en Python.

Subprocesos en Python

Un subproceso en Python es una tarea que un script de Python delega al sistema operativo (SO).

La biblioteca de subprocesos nos permite ejecutar y administrar subprocesos directamente desde Python. Eso implica trabajar con la entrada estándar stdin, la salida estándar stdout y los códigos de retorno.

No tenemos que instalarlo con PIP, ya que es parte de Python biblioteca estándar.

Por lo tanto, podemos comenzar a usar subprocesos en python simplemente importando el módulo.

import subprocess

# Using the module ....

Nota: Para seguir con este artículo, debe tener Python 3.5 +

Para verificar la versión de python que tiene actualmente, simplemente ejecute.

❯ python --version
Python 3.9.5 # My result

En caso de que la versión de Python que obtenga sea 2.x, puede usar el siguiente comando

python3 --version

Continuando con el tema, la idea principal detrás de la biblioteca de subprocesos es poder interactuar con el sistema operativo ejecutando cualquier comando que queramos, directamente desde el intérprete de Python.

Eso significa que podemos hacer lo que queramos, siempre que nuestro sistema operativo nos lo permita (y siempre que no elimine su sistema de archivos raíz 😅).

Veamos cómo usarlo creando un script simple que enumere los archivos del directorio actual.

Primera aplicación de subproceso

Primero, creemos un archivo list_dir.py. Este será el archivo en el que vamos a experimentar con la lista de archivos.

touch list_dir.py

Ahora abramos ese archivo y usemos el siguiente código.

import subprocess 

subprocess.run('ls')

Primero, estamos importando el módulo de subproceso y luego usando la función ejecutar que ejecuta, el comando que pasamos como argumento.

Esta función se introdujo en Python 3.5, como un atajo amigable para subproceso.Popen. La función subprocess.run nos permite ejecutar un comando y esperar a que finalice, a diferencia de Popen donde tenemos la opción de llamar a comunicar más tarde.

Hablando de la salida del código, ls es un comando de UNIX que enumera los archivos del directorio en el que se encuentra. Por lo tanto, si ejecuta este comando, obtendrá una lista de los archivos presentes en el directorio actual.

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

Nota: Toma en cuenta que si estás en Windows, necesitarás usar diferentes comandos. Por ejemplo, en lugar de usar «ls» puedes usar «dir»

Esto puede parecer demasiado simple, y tienes razón. Desea adoptar un enfoque completo de todo el poder que le brinda el caparazón. Entonces, aprendamos cómo pasar argumentos al shell con subproceso.

Por ejemplo para listar también los archivos ocultos (Aquellos que comienzan con un punto), y también listar todos los metadatos de los archivos, escribimos el siguiente código.

import subprocess

# subprocess.run('ls')  # Simple command

subprocess.run('ls -la', shell=True)

Estamos ejecutando este comando como una cadena y usando el shell de argumentos. Eso significa que estamos invocando un shell al comienzo de la ejecución de nuestro subproceso, y el shell interpreta directamente el argumento del comando.

Sin embargo, el uso de shell=True tiene muchas desventajas, y las peores son las posibles filtraciones de seguridad. Puedes leer sobre ellos en el documentación oficial.

La mejor manera de pasar comandos a la función de ejecución es usar una lista donde lst[0] es el comando para llamar (ls en este caso) y lst[n] son los argumentos de ese comando.

Si lo hacemos, nuestro código se verá así.

import subprocess

# subprocess.run('ls')  # Simple command

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

subprocess.run(['ls', '-la'])

Si queremos almacenar la salida estándar de un subproceso en una variable, podemos hacerlo estableciendo el argumento capture_output en verdadero.

list_of_files = subprocess.run(['ls', '-la'], capture_output=True)

print(list_of_files.stdout)

❯ python list_dir.py 
b'total 36ndrwxr-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.pyndrwxr-xr-x 8 daniel daniel 4096 may 20 17:31 .gitn-rw-r--r-- 1 daniel daniel 2160 may 17 22:23 .gitignoren-rw-r--r-- 1 daniel daniel 271 may 20 19:53 internet_checker.pyn-rw-r--r-- 1 daniel daniel 1076 may 17 22:23 LICENSEn-rw-r--r-- 1 daniel daniel 216 may 20 22:12 list_dir.pyn-rw-r--r-- 1 daniel daniel 22 may 17 22:23 README.mdn'

Para acceder a la salida de un proceso, usamos el atributo de instancia stdout.

En este caso, queremos almacenar la salida como una cadena, en lugar de bytes, y podemos hacerlo configurando el argumento de texto como verdadero.

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

Perfecto, ahora que conocemos los conceptos básicos de la biblioteca de subprocesos, es hora de pasar a algunos ejemplos de uso.

Ejemplos de uso de subproceso en Python

En esta sección, vamos a revisar algunos usos prácticos de la biblioteca de subprocesos. Puedes consultarlos todos en este Repositorio Github.

Comprobador de programa

Uno de los usos principales de esta biblioteca es la capacidad de realizar operaciones simples del sistema operativo.

Por ejemplo, un script simple que verifica si un programa está instalado. En Linux, podemos hacer esto con el comando 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)

Nota: en UNIX, cuando un comando tiene éxito, su código de estado es 0. De lo contrario, algo salió mal durante la ejecución.

Como no estamos usando el argumento shell=True, podemos tomar la entrada del usuario de forma segura. Además, podemos verificar si la entrada es un programa válido con un patrón de expresiones regulares.

import subprocess

import re

programs = input('Separe the programs with a space: ').split()

secure_pattern = 'Dom'

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')

En este caso, obtenemos los programas del usuario y usamos una expresión regular que certifica que la cadena del programa solo incluye letras y dígitos. Comprobamos la existencia de cada programa con un bucle for a.

Grep simple en Python

Tu amigo Tom tiene una lista de patrones en un archivo de texto y otro archivo grande en el que quiere obtener el número de coincidencias para cada patrón. Pasaba horas ejecutando el comando grep para cada patrón.

Afortunadamente, sabes cómo resolver este problema con Python y lo ayudarás a realizar esta tarea en unos segundos.

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')

Echando un vistazo a este archivo, definimos dos variables que son los nombres de archivo con los que queremos trabajar. Luego abrimos el archivo que contiene todos los patrones e iteramos sobre ellos. A continuación, llamamos a un subproceso que ejecuta un comando grep con el indicador «-c» (significa contar) y determinamos la salida de la coincidencia con un condicional.

Si ejecuta este archivo (recuerde que puede descargar los archivos de texto desde el repositorio de Github)

Configurar un virtualenv con subproceso

Una de las mejores cosas que puede hacer con Python es la automatización de procesos. Este tipo de script puede ahorrarle horas de tiempo por semana.

Por ejemplo, vamos a crear un script de instalación que crea un entorno virtual para nosotros e intenta encontrar un archivo requirements.txt en el directorio actual para instalar todas las dependencias.

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 ...")

En este caso, usamos varios procesos y analizamos los datos que necesitamos en nuestro script de python. También estamos usando el rutalib biblioteca que nos permite averiguar si existe el archivo requirements.txt.

Si ejecuta el archivo python, obtendrá algunos mensajes útiles de lo que está sucediendo con el sistema operativo.

❯ 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"

Tenga en cuenta que obtenemos el resultado del proceso de instalación porque no estamos redirigiendo el resultado estándar a una variable.

Ejecutar otro lenguaje de programación

Podemos ejecutar otros lenguajes de programación con python y obtener el resultado de esos archivos. Esto es posible porque los subprocesos interactúan directamente con el sistema operativo.

Por ejemplo, creemos un programa hola mundo en C++ y Java. Para ejecutar el siguiente archivo, deberá instalar C++ y Java compiladores.

holamundo.cpp

#include <iostream>

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

holamundo.java

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

Sé que esto parece mucho código en comparación con una simple línea de Python, pero esto es solo para fines de prueba.

Vamos a crear un script de Python que ejecute todos los archivos de C++ y Java en un directorio. Para hacer esto primero queremos obtener una lista de archivos dependiendo de la extensión del archivo, y globo nos permite hacerlo fácilmente!

from glob import glob

# Gets files with each extension
java_files = glob('*.java')

cpp_files = glob('*.cpp')

Después de eso, podemos comenzar a usar subprocesos para ejecutar cada tipo de archivo.

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)

Un pequeño truco es usar la tira de función de cadena para modificar la salida y obtener solo lo que necesitamos.

Nota: tenga cuidado al ejecutar archivos grandes de Java o C++, ya que estamos cargando su salida en la memoria y eso podría producir una pérdida de memoria.

Abrir programas externos

Podemos ejecutar otros programas simplemente llamando a la ubicación de sus binarios a través de un subproceso.

Probémoslo abriendo Brave, mi navegador web preferido.

import subprocess

subprocess.run('brave')

Esto abrirá una instancia del navegador, o simplemente otra pestaña si ya está ejecutando el navegador.

Como con cualquier otro programa que acepte banderas, podemos usarlas para producir el comportamiento deseado.

import subprocess

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

Para resumir

Un subproceso es un proceso informático creado por otro proceso. Podemos comprobar los procesos que está ejecutando nuestro ordenador con herramientas como htop y el administrador de tareas.

Python tiene su propia biblioteca para trabajar con subprocesos. Actualmente, la función ejecutar nos brinda una interfaz simple para crear y administrar subprocesos.

Podemos crear cualquier tipo de aplicación con ellos porque estamos interactuando directamente con el sistema operativo.

Finalmente, recuerda que la mejor manera de aprender es crear algo que te gustaría usar.