У Python можна перехоплювати те, що виводиться на екран через print().
Це корисно для тестів, налагодження, логування або для придушення «шумного» виводу.
Я покажу, як зробити це елегантно — використовуючи лише стандартну бібліотеку та кілька рядків коду.
In Questo Articolo
🧩 Розуміння Context Managers у Python
Що таке Context Manager?
Context manager керує ресурсами безпечно та організовано.
Він визначає поведінку ініціалізації та очищення навколо блоку коду.
Приклад: робота з файлами
with open("file.txt", "w") as f:
f.write("Привіт, світ!")
✅ Автоматичне очищення: файл буде закритий навіть у разі виникнення винятку.
Навіщо Context Managers корисні
- Автоматичне очищення – ресурси звільняються самі
- Безпека при помилках – очищення виконується навіть якщо трапиться виняток
- Читабельність коду – логіка setup/teardown в одному місці
- Повторне використання – підходить для файлів, locks, мережевих з’єднань або stdout
Використання @contextmanager з contextlib
Декоратор @contextmanager дозволяє створювати context managers за допомогою генераторів:
from contextlib import contextmanager
@contextmanager
def my_context():
print("Входимо в контекст")
resource = "якийсь ресурс"
try:
yield resource
finally:
print("Виходимо з контексту")
with my_context() as res:
print("В середині контексту:", res)
Вивід:
Входимо в контекст
В середині контексту: якийсь ресурс
Виходимо з контексту
💡 Пояснення:
- Код перед
yield→ виконується при вході - Значення з
yield→ доступне якas res - Код після
yield→ виконується при виході, навіть якщо стався виняток
Це ідеально для тимчасової зміни глобального стану, наприклад, перехоплення stdout.
⚡ Підсумок: Context Managers для stdout/stderr
| Завдання / Сценарій | Рекомендований Context Manager |
|---|---|
Перехопити тільки print | capture_stdout |
| Перехопити тільки помилки/винятки | capture_stderr |
| Перехопити stdout і stderr одночасно | capture_output |
| Перехопити і одночасно показувати | capture_stdout_tee / capture_output_tee |
📦 Перехоплення stdout
from contextlib import contextmanager
import sys, io
@contextmanager
def capture_stdout():
old_stdout = sys.stdout
sys.stdout = buffer = io.StringIO()
try:
yield buffer
finally:
sys.stdout = old_stdout
Приклад:
with capture_stdout() as out:
print("Цей вивід був перехоплений")
print("Перехоплено:", out.getvalue())
🔧 Перехоплення stderr або обох потоків
Перехоплення stderr
@contextmanager
def capture_stderr():
old_stderr = sys.stderr
sys.stderr = buffer = io.StringIO()
try:
yield buffer
finally:
sys.stderr = old_stderr
Перехоплення stdout + stderr
@contextmanager
def capture_output():
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = sys.stderr = buffer = io.StringIO()
try:
yield buffer
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
🔀 Режим Tee: перехоплювати І показувати
class Tee(io.StringIO):
def __init__(self, original):
super().__init__()
self.original = original
def write(self, text):
self.original.write(text)
super().write(text)
def flush(self):
self.original.flush()
Context managers:
@contextmanager
def capture_stdout_tee():
old_stdout = sys.stdout
sys.stdout = tee = Tee(old_stdout)
try:
yield tee
finally:
sys.stdout = old_stdout
@contextmanager
def capture_output_tee():
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = sys.stderr = tee = Tee(old_stdout)
try:
yield tee
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
Приклад:
with capture_stdout_tee() as out:
print("Видно і перехоплено")
print("Вміст Tee:", out.getvalue())
📦 Повний файл: stdout_capture.py
"""
Утиліта для перехоплення stdout і stderr за допомогою context managers.
Підтримує:
- перехоплення stdout
- перехоплення stderr
- комбіноване перехоплення
- режим Tee
"""
import sys, io
from contextlib import contextmanager
@contextmanager
def capture_stdout():
old_stdout = sys.stdout
sys.stdout = buffer = io.StringIO()
try: yield buffer
finally: sys.stdout = old_stdout
@contextmanager
def capture_stderr():
old_stderr = sys.stderr
sys.stderr = buffer = io.StringIO()
try: yield buffer
finally: sys.stderr = old_stderr
@contextmanager
def capture_output():
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = sys.stderr = buffer = io.StringIO()
try: yield buffer
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
class Tee(io.StringIO):
def __init__(self, original):
super().__init__()
self.original = original
def write(self, text):
self.original.write(text)
super().write(text)
def flush(self):
self.original.flush()
@contextmanager
def capture_stdout_tee():
old_stdout = sys.stdout
sys.stdout = tee = Tee(old_stdout)
try: yield tee
finally: sys.stdout = old_stdout
@contextmanager
def capture_output_tee():
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = sys.stderr = tee = Tee(old_stdout)
try: yield tee
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
✅ Приклади використання
from stdout_capture import capture_stdout, capture_output, capture_stdout_tee
def test_func():
print("Привіт з функції")
# Перехоплення stdout
with capture_stdout() as out:
test_func()
print("stdout перехоплено:", out.getvalue())
# Перехоплення stdout + stderr
try:
with capture_output() as out:
print("Щось")
raise Exception("Ой!")
except Exception:
pass
print("Перехоплений вивід:", out.getvalue())
# Перехоплення і одночасне відображення
with capture_stdout_tee() as out:
print("Видно І перехоплено")
print("Вміст Tee:", out.getvalue())
⚠️ Практичні поради та застереження
1️⃣ Дуже великий вивід
io.StringIOможе споживати багато пам’яті при великому обсязі.- Використовуйте тимчасовий файл:
import sys, tempfile
with tempfile.TemporaryFile(mode='w+') as tmp:
old_stdout = sys.stdout
sys.stdout = tmp
try:
print("Багато даних...")
tmp.seek(0)
captured = tmp.read()
finally:
sys.stdout = old_stdout
2️⃣ Вивід підпроцесів
capture_stdout()не перехоплює вивід зовнішніх процесів.- Використовуйте
subprocess.PIPE:
import subprocess
result = subprocess.run(["ls", "-la"], capture_output=True, text=True)
print(result.stdout)
3️⃣ Безпека у Threads
sys.stdoutіsys.stderrглобальні → уникайте одночасного перехоплення у кількох потоках.
4️⃣ Коли використовувати Tee
- Дебаг функцій із логуванням
- Інтерактивні CLI інструменти
- Інтеграційні тести із виводом у термінал
5️⃣ Відновлення потоків
- Завжди відновлюйте
sys.stdout/sys.stderrуfinally
6️⃣ Уникайте занадто глибоких вкладень
- Багаторазові перехоплення можуть змішати вивід. Використовуйте один контекст для одного сценарію.
7️⃣ Best Practices у тестах
- Unit tests →
capture_stdout()абоcapture_output() - Ручне налагодження → режим Tee
- Перевіряйте вміст буфера, а не побічні ефекти