os, sys и subprocess — системные операции
Модуль os — операционная система
Работа с путями (pathlib)
В современном Python рекомендуется использовать pathlib:
from pathlib import Path
# Build paths with / operator
config = Path.home() / ".config" / "myapp" / "config.toml"
print(config)
# Path properties
p = Path("/home/user/documents/report.pdf")
print(p.name) # report.pdf
print(p.stem) # report
print(p.suffix) # .pdf
print(p.parent) # /home/user/documents
print(p.exists()) # False
# Iterate directory
for item in Path("/tmp").iterdir():
if item.is_file():
print(f"Файл: {item.name} ({item.stat().st_size} байт)")
# Glob patterns
for py_file in Path(".").rglob("*.py"):
print(py_file)
# Create directories
data_dir = Path("data") / "output"
data_dir.mkdir(parents=True, exist_ok=True)
# Read/write files
file_path = data_dir / "result.txt"
file_path.write_text("Привет, мир!", encoding="utf-8")
content = file_path.read_text(encoding="utf-8")
print(content) # Привет, мир!
Переменные окружения
import os
# Read environment variables
home = os.environ.get("HOME", "/tmp")
debug = os.environ.get("DEBUG", "false")
# Safe access with default
db_host = os.getenv("DB_HOST", "localhost")
db_port = int(os.getenv("DB_PORT", "5432"))
# Set environment variable (current process only)
os.environ["MY_APP_MODE"] = "testing"
# Check if variable exists
if "SECRET_KEY" not in os.environ:
print("WARNING: SECRET_KEY не установлен!")
# Pattern: configuration from environment
from dataclasses import dataclass
@dataclass
class AppConfig:
"""Application configuration from environment."""
db_host: str = "localhost"
db_port: int = 5432
debug: bool = False
@classmethod
def from_env(cls) -> "AppConfig":
return cls(
db_host=os.getenv("DB_HOST", "localhost"),
db_port=int(os.getenv("DB_PORT", "5432")),
debug=os.getenv("DEBUG", "").lower() in ("1", "true", "yes"),
)
Файловая система
import os
import shutil
from pathlib import Path
# Current working directory
cwd = os.getcwd()
# Walk directory tree
for root, dirs, files in os.walk("."):
dirs[:] = [d for d in dirs if not d.startswith(".")]
for f in files:
if f.endswith(".py"):
full_path = os.path.join(root, f)
print(f"{full_path}: {os.path.getsize(full_path)} bytes")
# File operations with shutil
# shutil.copy2("src.txt", "dst.txt") # Copy with metadata
# shutil.copytree("src_dir", "dst_dir") # Copy directory tree
# shutil.rmtree("dir_to_remove") # Remove directory tree
# shutil.move("old_path", "new_path") # Move file/directory
# Disk usage
usage = shutil.disk_usage("/")
print(f"Всего: {usage.total / (1024**3):.1f} GB")
print(f"Свободно: {usage.free / (1024**3):.1f} GB")
# Temporary files
import tempfile
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
f.write("Временные данные")
print(f"Temp file: {f.name}")
with tempfile.TemporaryDirectory() as tmpdir:
print(f"Temp dir: {tmpdir}")
# Directory deleted when exiting context
Модуль sys — параметры интерпретатора
import sys
# Python version
print(sys.version) # 3.14.0 (main, ...)
print(sys.version_info) # sys.version_info(major=3, minor=14, ...)
# Check Python version
if sys.version_info < (3, 10):
print("Требуется Python 3.10+")
sys.exit(1)
# Platform
print(sys.platform) # darwin, linux, win32
# Command line arguments
print(sys.argv) # ['script.py', 'arg1', 'arg2']
# Standard streams
sys.stdout.write("Стандартный вывод\n")
sys.stderr.write("Ошибка\n")
# Memory and limits
print(sys.getsizeof([1, 2, 3])) # Size in bytes
print(sys.getrecursionlimit()) # Default: 1000
Аргументы командной строки с argparse
import argparse
def main() -> None:
parser = argparse.ArgumentParser(description="Обработка данных")
parser.add_argument("input_file", help="Входной файл")
parser.add_argument(
"-o", "--output",
default="result.txt",
help="Выходной файл (по умолчанию: result.txt)",
)
parser.add_argument(
"-v", "--verbose",
action="store_true",
help="Подробный вывод",
)
parser.add_argument(
"-n", "--count",
type=int,
default=10,
help="Количество записей",
)
args = parser.parse_args()
if args.verbose:
print(f"Входной файл: {args.input_file}")
print(f"Выходной файл: {args.output}")
print(f"Количество: {args.count}")
if __name__ == "__main__":
main()
subprocess — запуск внешних процессов
import subprocess
# Simple command execution
result = subprocess.run(
["ls", "-la", "/tmp"],
capture_output=True,
text=True,
)
print(f"Return code: {result.returncode}")
print(f"Output: {result.stdout[:200]}")
# Run with timeout
try:
result = subprocess.run(["sleep", "10"], timeout=3)
except subprocess.TimeoutExpired:
print("Команда превысила таймаут")
# Check for errors
try:
subprocess.run(
["python", "-c", "import nonexistent"],
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as e:
print(f"Ошибка (код {e.returncode}): {e.stderr}")
# Pass input to command
result = subprocess.run(
["grep", "python"],
input="python is great\njava is ok\npython rocks\n",
capture_output=True,
text=True,
)
print(result.stdout)
# Working directory
result = subprocess.run(
["pwd"],
capture_output=True,
text=True,
cwd="/tmp",
)
print(result.stdout.strip()) # /tmp
Безопасность: shell=True
import subprocess
# DANGEROUS: shell=True with user input
# user_input = "file.txt; rm -rf /"
# subprocess.run(f"cat {user_input}", shell=True) # NEVER!
# SAFE: pass arguments as list (no shell injection)
user_input = "file.txt; rm -rf /"
subprocess.run(["cat", user_input], capture_output=True)
# Safely passes the entire string as one argument
# shell=True is OK for static commands only
result = subprocess.run(
"echo $HOME | tr '/' '\n'",
shell=True,
capture_output=True,
text=True,
)
Практический пример: утилита бэкапа
import os
import shutil
import subprocess
from datetime import datetime
from pathlib import Path
from dataclasses import dataclass
@dataclass
class BackupConfig:
source_dir: str
backup_dir: str
max_backups: int = 5
compress: bool = True
@classmethod
def from_env(cls) -> "BackupConfig":
return cls(
source_dir=os.getenv("BACKUP_SOURCE", "./data"),
backup_dir=os.getenv("BACKUP_DIR", "./backups"),
max_backups=int(os.getenv("BACKUP_MAX", "5")),
compress=os.getenv("BACKUP_COMPRESS", "true").lower() == "true",
)
def create_backup(config: BackupConfig) -> Path:
"""Create a backup of the source directory."""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_name = f"backup_{timestamp}"
backup_path = Path(config.backup_dir) / backup_name
backup_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(config.source_dir, str(backup_path))
print(f"Скопировано: {config.source_dir} -> {backup_path}")
if config.compress:
archive = f"{backup_path}.tar.gz"
result = subprocess.run(
["tar", "-czf", archive, "-C", config.backup_dir, backup_name],
capture_output=True, text=True,
)
if result.returncode == 0:
shutil.rmtree(str(backup_path))
return Path(archive)
return backup_path
def cleanup_old_backups(config: BackupConfig) -> None:
"""Remove old backups, keeping only max_backups most recent."""
backup_dir = Path(config.backup_dir)
if not backup_dir.exists():
return
backups = sorted(
backup_dir.glob("backup_*"),
key=lambda p: p.stat().st_mtime,
reverse=True,
)
for old_backup in backups[config.max_backups:]:
if old_backup.is_file():
old_backup.unlink()
else:
shutil.rmtree(str(old_backup))
print(f"Удалён старый бэкап: {old_backup.name}")