Столкнувшись с исключением, иногда не понимаешь: «divizion by zero» — что это? Начинающие разработчики часто не могут понять причину ошибки из-за неправильного понимания её а русском языке. Опытные разработчики также сталкиваются с неизвестными им исключениями, а часто лезть в переводчик для понимания ошибки не хочется. Сегодня я напишу модуль для быстрого перевода таких ошибок, и все непойманные исключения и предупреждения в Python будут выводиться на русском языке.
Для начала установим библиотеку, которая будет обращаться к Google Translator для перевода всех ошибок и предупреждений в Python:
pip install deep_translator
Также нам потребуется работать с цветами для вывода ошибок, поэтому нам потребуется установить colorama. Вот команда:
pip install colorama
Теперь приступим к написанию кода! Я разбил код модуля на несколько файлов, каждый из которых направлен на выполнение совей задачи. Вот код:
error_translator.py:
import sysimport warningsimport osimport jsonimport timeimport refrom deep_translator import GoogleTranslatorSOURCE_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 = 100000translator = 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 translateddef translate_exc_message(msg): cleaned = re.sub(r'0x[0-9a-fA-F]+', '...', msg) return cached_translate(cleaned)original_excepthook = sys.excepthookdef 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.showwarningdef 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_excepthookwarnings.showwarning = translating_showwarning
run.py:
import sysimport subprocessimport reimport osfrom pathlib import Pathimport coloramacolorama.init()sys.path.insert(0, str(Path(__file__).parent))from error_translator import cached_translatesys.stdout.reconfigure(encoding='utf-8', errors='replace')sys.stderr.reconfigure(encoding='utf-8', errors='replace')RED = colorama.Fore.REDRESET = colorama.Style.RESET_ALLerror_header_pattern = re.compile( r'^(Traceback \(most recent call last\):|Exception in|During handling of|The above exception was)')in_error_block = Falsepending_warning_source = Falsedef 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 linedef 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 для перевода. Переведённый текст ошибки встаёт на место старого английского, после чего программа выводит переведённую на русский ошибку в консоль.
Не стоит забывать, что это работает не только с ошибками, но и с предупреждениями. Создадим скрипт test_warning.py и вставим приведённый ниже код, а затем запускаем translate_errors.py:
import warningswarnings.warn("This is a test warning")
В модуле также предусмотрено автоматические кэширование всех переведённых ошибок на диск (ограничение: до 100000 записей, однако его можно изменять в коде модуля). Для того, чтобы переводчик работал, нужно стабильное интернет-соединение. Если интернет будет отсутствовать, перевод выполняться не будет (за исключением случаев, когда ошибка уже записана на диск и берётся из кэшированных записей)!
Если что-то не будет работать, пишите в комментарии — разберёмся. Ниже можно пройти опрос. Он поможет найти баги в работе проекта и улучшить его для всех. По итогам опроса мы обновим код, исправив все баги и выполним предложения по улучшению. Надеюсь, эта статья была полезной!
ссылка на оригинал статьи https://habr.com/ru/articles/1032158/