subprocess
O subprocess cria programas externos, captura stdout/stderr e gerencia códigos de saída. Prefira listas de argumentos sem shell, a menos que recursos de shell sejam explicitamente necessários.
Receita
import subprocess
result = subprocess.run(
["python", "--version"],
capture_output=True,
text=True,
check=True,
)
print(result.stdout.strip())Quando usar isso:
- Wrappers de CLI para Git, ffmpeg, openssl
- Ferramentas legadas sem API Python
- Scripts de empacotamento que invocam compiladores
- Verificações de integridade chamando o binário curl
- Isolamento em sandbox via processo separado
Exemplo de Trabalho
import subprocess
from pathlib import Path
def run_git(args: list[str], cwd: Path) -> str:
completed = subprocess.run(
["git", *args],
cwd=cwd,
capture_output=True,
text=True,
check=False,
)
if completed.returncode != 0:
raise RuntimeError(completed.stderr.strip() or "git falhou")
return completed.stdout.strip()
def safe_ls(path: Path) -> str:
# Nunca use shell=True com entrada do usuário
proc = subprocess.run(
["ls", "-la", str(path)],
capture_output=True,
text=True,
timeout=5,
)
proc.check_returncode()
return proc.stdout
print(run_git(["--version"], Path.cwd()))O que isso demonstra:
- Argumentos em formato de lista evitam injeção de shell
check_returncodeoucheck=Truepara falhastimeoutimpede que processos filhos fiquem travadostext=Truedecodifica bytes para str usando a codificação padrão
Mergulho Profundo
run vs Popen
| API | Uso |
|---|---|
run | Execução única aguardando a conclusão |
Popen | IO de streaming, em segundo plano |
IO
capture_output=Trueredireciona stdout/stderrstdin=subprocess.DEVNULLquando não há entrada
Armadilhas
- shell=True com entrada do usuário - injeção de comando. Correção: use listas de argumentos,
shlex.quotese o shell for necessário. - Saída ilimitada - esgotamento de memória. Correção: use streaming com
Popeniter ou redirecione para um arquivo. - Timeout ausente - travamentos de zumbis. Correção: sempre use timeout em serviços.
- Confiança em CWD - binários relativos do diretório errado. Correção: use
cwdeenvexplícitos. - Loop de eventos bloqueante - subprocesso em rota assíncrona. Correção: use
asyncio.create_subprocess_exec.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| shutil.which | Resolver o caminho do binário | Não precisa executar |
| subprocess do asyncio | Streaming assíncrono | Script síncrono |
| Biblioteca Python pura | API estável existe | CLI pontual é aceitável |
FAQs
shell=True alguma vez?
Apenas comandos fixos confiáveis em scripts controlados - nunca com entrada externa.
Código de retorno 0?
Convenção de sucesso - verifique explicitamente, mesmo que não esteja usando check=True.
Mesclar stderr?
Use stderr=subprocess.STDOUT para o pipe stdout.
Saída binária?
Omita text=True; manipule bytes e decodifique explicitamente.
Windows?
Use creationflags para um novo console; listas de argumentos ainda são preferidas.
Ambiente?
Use env={**os.environ, "K": "V"} para sobrescrita parcial.
Processo de longa duração?
Use Popen + communicate com timeout ou loop de polling.
Auditoria de segurança?
Marque qualquer uso de shell=True e concatenação de strings em revisões de segurança.
uv run?
Use ["uv", "run", "python", "-m", "tool"] em formato de lista para um ambiente reproduzível.
pytest?
Use fixtures com tmp_path; simule subprocess.run para testes unitários.
Relacionados
- pathlib & os - caminhos cwd
- argparse & configparser - ferramentas de CLI que envolvem subprocessos
- Melhores Práticas da Biblioteca Padrão - segurança
- Noções Básicas de Concorrência - bloqueante vs assíncrono
Versões do Stack: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+ e uv 0.6+.