Создание Python-библиотеки для перевода исключений на русский язык

Страницы:  1

Ответить
 

Professor Seleznov


Столкнувшись с исключением, иногда не понимаешь: "divizion by zero" — что это? Начинающие разработчики часто не могут понять причину ошибки из-за неправильного понимания её а русском языке. Опытные разработчики также сталкиваются с неизвестными им исключениями, а часто лезть в переводчик для понимания ошибки не хочется. Сегодня я напишу модуль для быстрого перевода таких ошибок, и все непойманные исключения и предупреждения в Python будут выводиться на русском языке.
Для начала установим библиотеку, которая будет обращаться к Google Translator для перевода всех ошибок и предупреждений в Python:
pip install deep_translator
Также нам потребуется работать с цветами для вывода ошибок, поэтому нам потребуется установить colorama. Вот команда:
pip install colorama
Теперь приступим к написанию кода! Я разбил код модуля на несколько файлов, каждый из которых направлен на выполнение совей задачи. Вот код:
error_translator.py:
import sys
import warnings
import os
import json
import time
import re
from deep_translator import GoogleTranslator
SOURCE_LANG = 'auto'
TARGET_LANG = 'ru'
CACHE_DIR = os.path.join(os.path.dirname(__file__), 'error_cache')
CACHE_FILE = os.path.join(CACHE_DIR, 'translations.json')
MAX_CACHE_ENTRIES = 100000
translator = GoogleTranslator(source=SOURCE_LANG, target=TARGET_LANG)
def _load_cache():
if not os.path.exists(CACHE_FILE):
return {}
try:
with open(CACHE_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except:
return {}
def _save_cache(cache):
os.makedirs(CACHE_DIR, exist_ok=True)
if len(cache) > MAX_CACHE_ENTRIES:
sorted_items = sorted(cache.items(), key=lambda x: x[1].get('timestamp', 0))
cache = dict(sorted_items[-MAX_CACHE_ENTRIES:])
with open(CACHE_FILE, 'w', encoding='utf-8') as f:
json.dump(cache, f, ensure_ascii=False, indent=2)
def cached_translate(text):
if not text:
return text
cache = _load_cache()
if text in cache:
return cache[text]['translation']
try:
translated = translator.translate(text)
except Exception:
return text
cache[text] = {'translation': translated, 'timestamp': time.time()}
_save_cache(cache)
return translated
def translate_exc_message(msg):
cleaned = re.sub(r'0x[0-9a-fA-F]+', '...', msg)
return cached_translate(cleaned)
original_excepthook = sys.excepthook
def translating_excepthook(exc_type, exc_value, exc_tb):
if exc_value is not None:
translated = translate_exc_message(str(exc_value))
try:
new_exc = exc_type(translated)
except Exception:
new_exc = exc_value
else:
new_exc = exc_value
original_excepthook(exc_type, new_exc, exc_tb)
original_showwarning = warnings.showwarning
def translating_showwarning(message, category, filename, lineno, file=None, line=None):
translated = translate_exc_message(str(message))
original_showwarning(translated, category, filename, lineno, file, line)
sys.excepthook = translating_excepthook
warnings.showwarning = translating_showwarning
run.py:
import sys
import subprocess
import re
import os
from pathlib import Path
import colorama
colorama.init()
sys.path.insert(0, str(Path(__file__).parent))
from error_translator import cached_translate
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
RED = colorama.Fore.RED
RESET = colorama.Style.RESET_ALL
error_header_pattern = re.compile(
r'^(Traceback \(most recent call last\):|Exception in|During handling of|The above exception was)'
)
in_error_block = False
pending_warning_source = False
def translate_line(line):
global in_error_block, pending_warning_source
if pending_warning_source:
pending_warning_source = False
if line.startswith(' ') or line.startswith('\t'):
return RED + line.rstrip('\n') + RESET + '\n'
else:
in_error_block = False
return line
if error_header_pattern.match(line):
in_error_block = True
return RED + line.rstrip('\n') + RESET + '\n'
if not in_error_block and line.startswith(' File '):
in_error_block = True
return RED + line.rstrip('\n') + RESET + '\n'
warning_match = re.search(r'(:\d+ \s*(\w+Warning):\s*(.*)', line)
if warning_match:
if not in_error_block:
in_error_block = True
pending_warning_source = True
prefix = line[:warning_match.start(1)]
lineno = warning_match.group(1)
category = warning_match.group(2)
msg = warning_match.group(3)
if msg:
translated_msg = cached_translate(msg)
return f"{RED}{prefix}{lineno} {category}: {translated_msg}{RESET}\n"
else:
return RED + line.rstrip('\n') + RESET + '\n'
if in_error_block:
if line.startswith(' File ') or line.startswith(' ') or line.startswith('\t'):
return RED + line.rstrip('\n') + RESET + '\n'
m = re.match(r'^(\s*(?:\w+\.)*\w*(?:Error|Warning|Exception)):\s*(.*)', line)
if m:
exc_part, msg = m.groups()
if msg:
translated = f"{RED}{exc_part}: {cached_translate(msg)}{RESET}\n"
else:
translated = RED + line.rstrip('\n') + RESET + '\n'
in_error_block = False
return translated
if line.strip() == '':
return RED + '\n'
in_error_block = False
return line
return line
def run_target(target_script):
env = os.environ.copy()
env['PYTHONUNBUFFERED'] = '1'
p = subprocess.Popen(
[sys.executable, target_script],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
encoding='utf-8',
errors='replace',
env=env,
bufsize=1
)
for line in p.stdout:
sys.stdout.write(translate_line(line))
sys.stdout.flush()
p.wait()
sys.exit(p.returncode)
translate_errors.py:
TARGET = "test.py"; from run import run_target; run_target(TARGET)
Работает это так: при запуске модуля translate_errors.py он запускает наш основной скрипт, название которого мы указали в константе TARGET в отдельном процессе. После выполнения скрипта программиста наш модуль проверяет каждую строчку вывода. Как только модуль натыкается на ошибку, он отправляет её в Google для перевода. Переведённый текст ошибки встаёт на место старого английского, после чего программа выводит переведённую на русский ошибку в консоль.
pic
Пример вывода исключения в Python на русском языке
Не стоит забывать, что это работает не только с ошибками, но и с предупреждениями. Создадим скрипт test_warning.py и вставим приведённый ниже код, а затем запускаем translate_errors.py:
import warnings
warnings.warn("This is a test warning")
pic
Переведённое предупреждение
В модуле также предусмотрено автоматические кэширование всех переведённых ошибок на диск (ограничение: до 100000 записей, однако его можно изменять в коде модуля). Для того, чтобы переводчик работал, нужно стабильное интернет-соединение. Если интернет будет отсутствовать, перевод выполняться не будет (за исключением случаев, когда ошибка уже записана на диск и берётся из кэшированных записей)!
Если что-то не будет работать, пишите в комментарии — разберёмся. Ниже можно пройти опрос. Он поможет найти баги в работе проекта и улучшить его для всех. По итогам опроса мы обновим код, исправив все баги и выполним предложения по улучшению. Надеюсь, эта статья была полезной!-Источник
 
Loading...
Error