Автоматизация программирования на Python: конвейеры, графики и код.

Создание рабочего процесса на Python, который выявляет ошибки до запуска в производство.

Использование современных инструментов для выявления дефектов на более ранних этапах жизненного цикла программного обеспечения.

Делиться

2e453891d3f27c8cdee1a132c7f03836

Python — один из тех языков, который позволяет почувствовать себя продуктивным практически мгновенно.

В этом во многом и заключается его популярность. Переход от идеи к работающему коду может быть очень быстрым. Вам не нужно много вспомогательного кода, чтобы просто протестировать идею. Небольшой анализ входных данных, несколько функций, объединение всего этого, и очень часто у вас перед глазами окажется что-то полезное в течение нескольких минут.

Недостаток заключается в том, что Python может быть очень снисходителен в тех случаях, когда иногда этого было бы недостаточно.

Оно с удовольствием будет предполагать наличие ключа в словаре, даже если его нет. Оно позволит вам передавать структуры данных с немного отличающимися характеристиками, пока одна из них, наконец, не сломается во время выполнения. Оно позволит опечатке оставаться в памяти дольше, чем следовало бы. И, возможно, незаметно, оно позволит коду быть «правильным», оставаясь при этом слишком медленным для реального использования.

Поэтому меня больше интересуют рабочие процессы разработки кода в целом, а не какая-либо конкретная методика тестирования.

Когда речь заходит о качестве кода, обычно сразу же поднимается вопрос о тестах. Тесты важны, и я постоянно их использую, но я не думаю, что они должны нести всю нагрузку. Было бы лучше, если бы большинство ошибок выявлялось еще до запуска кода. Возможно, некоторые проблемы следует обнаруживать сразу после сохранения файла с кодом. Другие — при фиксации изменений на GitHub. И если эти тесты пройдут успешно, возможно, стоит запустить серию тестов, чтобы убедиться, что код работает корректно и достаточно хорошо, чтобы выдерживать реальные условия эксплуатации.

В этой статье я хочу рассказать о наборе инструментов, которые можно использовать для создания рабочего процесса на Python, автоматизирующего упомянутые выше задачи. Речь идёт не о гигантской корпоративной системе или сложной платформе DevOps. Просто практичный, относительно простой набор инструментов, который помогает выявлять ошибки в коде до развертывания в продакшене.

Чтобы это стало нагляднее, я приведу небольшой, но реалистичный пример. Представьте, что я создаю модуль на Python, который обрабатывает данные о заказах, вычисляет итоговые суммы и генерирует сводки по последним заказам. Вот намеренно приблизительный первый вариант.

from datetime import datetime import json def normalize_order(order): created = datetime.fromisoformat(order[«created_at»]) return { «id»: order[«id»], «customer_email»: order.get(«customer_email»), «items»: order[«items»], «created_at»: created, «discount_code»: order.get(«discount_code»), } def calculate_total(order): total = 0 discount = None for item in order[«items»]: total += item[«price»] * item[«quantity»] if order.get(«discount_code»): discount = 0.1 total *= 0.9 return round(total, 2) def build_order_summary(order): normalized = normalize_order(order); total = calculate_total(order) return { «id»: normalized[«id»], «email»: normalized[«customer_email»].lower(), «created_at»: normalized[«created_at»].isoformat(), «total»: total, «item_count»: len(normalized[«items»]), } def recent_order_totals(orders): summaries = [] for order in orders: summaries.append(build_order_summary(order)) summaries.sort(key=lambda x: x[«created_at»], reverse=True) return summaries[:10]

В таком коде, когда вы «быстро развиваетесь и ломаете всё подряд», есть много привлекательного. Он короткий и читаемый, и, вероятно, даже заработает на первых нескольких примерах входных данных.

Но есть и несколько скрытых ошибок или проблем с дизайном. Например, если отсутствует customer_email , метод .lower() вызовет AttributeError. Также предполагается, что переменная items всегда содержит ожидаемые ключи. Есть неиспользуемый импорт и оставшаяся переменная, оставшаяся от, по-видимому, неполной рефакторизации. А в финальной функции весь результирующий набор сортируется, хотя нужны только 10 самых последних элементов. Последний момент важен, потому что мы хотим, чтобы наш код был максимально эффективным. Если нам нужны только десять самых первых элементов, следует избегать полной сортировки набора данных, когда это возможно.

Именно такой код позволяет эффективно использовать рабочий процесс и окупить себя.

Итак, давайте рассмотрим некоторые инструменты, которые вы можете использовать в процессе разработки кода, чтобы обеспечить его корректность, удобство сопровождения и высокую производительность. Все инструменты, которые я буду обсуждать, можно бесплатно скачать, установить и использовать.

Обратите внимание, что некоторые из упомянутых мной инструментов многофункциональны. Например, некоторые операции форматирования, которые можно выполнить с помощью инструмента «Черный цвет» , также можно выполнить с помощью инструмента «Шерох» . Часто выбор инструментов зависит от личных предпочтений.

Инструмент №1: Читаемый код без лишних символов форматирования.

Первым инструментом, который я обычно устанавливаю, является Black . Black — это форматировщик кода на Python. Его задача очень проста: он берёт ваш исходный код и автоматически применяет к нему единый стиль и формат.

Установка и использование

Установите его с помощью pip или любого другого удобного для вас менеджера пакетов Python. После этого вы можете запустить его следующим образом:

$ black your_python_file.py или $ python -m black your_python_file

Для работы Black требуется Python версии 3.10 или более поздней.

Использование форматировщика кода может показаться чисто декоративным, но я считаю, что форматировщики важнее, чем многие признают. Вам не нужно тратить умственную энергию на то, как должен переноситься вызов функции, где должен стоять перенос строки или достаточно ли хорошо вы отформатировали словарь. Ваш код должен быть согласованным, чтобы вы могли сосредоточиться на логике, а не на оформлении.

Предположим, вы написали эту функцию в спешке.

def build_order_summary(order): normalized=normalize_order(order); total=calculate_total(order) return {«id»:normalized[«id»],»email»:normalized[«customer_email»].lower(),»created_at»:normalized[«created_at»].isoformat(),»total»:total,»item_count»:len(normalized[«items»])}

Это выглядит неряшливо, но Блэк превращает это в вот это.

def build_order_summary(order): normalized = normalize_order(order) total = calculate_total(order) return { «id»: normalized[«id»], «email»: normalized[«customer_email»].lower(), «created_at»: normalized[«created_at»].isoformat(), «total»: total, «item_count»: len(normalized[«items»]), }

Блэк не исправил здесь никакой бизнес-логики. Но он сделал нечто чрезвычайно полезное: он упростил проверку кода. Когда форматирование перестает быть источником неудобств, любые реальные проблемы с кодом становятся гораздо легче обнаружить.

Black можно настроить множеством различных способов, о которых вы можете прочитать в его официальной документации. (Ссылки на документацию и все упомянутые инструменты находятся в конце статьи)

Инструмент №2: Выявление мелких подозрительных ошибок

После обработки форматирования я обычно добавляю Ruff в конвейер. Ruff — это линтер на Python, написанный на Rust. Ruff быстрый, эффективный и отлично справляется со своей задачей.

Установка и использование

Как и Black, Ruff можно установить с помощью любого менеджера пакетов Python.

$ pip install ruff $ # Используется так: $ ruff check your_python_code.py

Проверка синтаксиса полезна, потому что многие ошибки начинаются с мелких подозрительных деталей. Не с серьезных логических ошибок или сложных частных случаев. Просто с незначительной неточностью в коде.

Например, предположим, у нас есть следующий простой код. В нашем примере модуля, например, есть пара неиспользуемых импортов и переменная, которой присваивается значение, но которая на самом деле никогда не нужна:

from datetime import datetime import json def calculate_total(order): total = 0 discount = 0 for item in order[«items»]: total += item[«price»] * item[«quantity»] if order.get(«discount_code»): total *= 0.9 return round(total, 2)

Рафф может это заметить мгновенно:

$ ruff check test1.py F401 [*] `datetime.datetime` импортирован, но не используется —> test1.py:1:22 | 1 | from datetime import datetime | ^^^^^^^^ 2 | import json | help: Удалить неиспользуемый импорт: `datetime.datetime` F401 [*] `json` импортирован, но не используется —> test1.py:2:8 | 1 | from datetime import datetime 2 | import json | ^^^^ 3 | 4 | def calculate_total(order): | help: Удалить неиспользуемый импорт: `json` F841 Локальная переменная `discount` назначена, но никогда не используется —> test1.py:6:5 | 4 | def calculate_total(order): 5 | total = 0 6 | discount = 0 | ^^^^^^^^ 7 | 8 | for item in order[«items»]: | help: Удалить присваивание неиспользуемой переменной `discount` Обнаружено 3 ошибки. [*] 2 можно исправить с помощью параметра `—fix` (1 скрытое исправление можно включить с помощью параметра `—unsafe-fixes`).

Инструмент №3: Python начинает казаться гораздо безопаснее.

Форматирование и проверка синтаксиса помогают, но ни то, ни другое на самом деле не решает основную проблему Python: предположения о данных.

Вот тут-то и пригодится mypy . Mypy — это статический верификатор типов для Python.

Установка и использование

Установите его с помощью pip, а затем запустите следующим образом.

$ pip install mypy $ # Для запуска используйте это $ mypy test3.py

Mypy выполнит проверку типов вашего кода (без его фактического выполнения). Это важный шаг, поскольку многие ошибки в Python на самом деле связаны с некорректным представлением данных. Вы предполагаете, что поле существует. Вы предполагаете, что значение является строкой, или что функция возвращает одно, тогда как на самом деле она иногда возвращает другое.

Чтобы увидеть это в действии, давайте добавим несколько типов в наш пример заказа.

from datetime import datetime from typing import NotRequired, TypedDict class Item(TypedDict): price: float quantity: int class RawOrder(TypedDict): id: str items: list[Item] created_at: str customer_email: NotRequired[str] discount_code: NotRequired[str] class NormalizedOrder(TypedDict): id: str customer_email: str | None items: list[Item] created_at: datetime discount_code: str | None class OrderSummary(TypedDict): id: str email: str created_at: str total: float item_count: int

Теперь мы можем аннотировать наши функции.

def normalize_order(order: RawOrder) -> NormalizedOrder: return { «id»: order[«id»], «customer_email»: order.get(«customer_email»), «items»: order[«items»], «created_at»: datetime.fromisoformat(order[«created_at»]), «discount_code»: order.get(«discount_code»), } def calculate_total(order: RawOrder) -> float: total = 0.0 for item in order[«items»]: total += item[«price»] * item[«quantity»] if order.get(«discount_code»): total *= 0.9 return round(total, 2) def build_order_summary(order: RawOrder) -> OrderSummary: normalized = normalize_order(order) total = calculate_total(order) return { «id»: normalized[«id»], «email»: normalized[«customer_email»].lower(), «created_at»: normalized[«created_at»].isoformat(), «total»: total, «item_count»: len(normalized[«items»]), }

Теперь скрыть ошибку стало гораздо сложнее. Например,

$ mypy test3.py test.py:36: ошибка: У элемента «None» из «str | None» отсутствует атрибут «lower» [union-attr] Обнаружена 1 ошибка в 1 файле (проверен 1 исходный файл)

Параметр customer_email берется из order.get(“customer_email”) , что означает, что он может отсутствовать и, следовательно, принимает значение None. Mypy отслеживает это значение asstr | None и корректно отклоняет вызов метода .lower() без предварительной обработки случая None.

На первый взгляд это может показаться простым делом, но я считаю это большим достижением. Mypy заставляет вас быть более честными в отношении структуры данных, с которыми вы на самом деле работаете. Он превращает неясные неожиданности во время выполнения в раннюю, более понятную обратную связь.

Инструмент №4: Тестирование, тестирование 1..2..3

В начале этой статьи мы выявили три проблемы в нашем коде обработки заказов: сбой при отсутствии customer_email , непроверенные предположения о ключах товаров и неэффективная сортировка, к которой мы вернемся позже. Блэк, Рафф и Mypy уже помогли нам решить первые две проблемы структурным путем. Но инструменты статического анализа кода имеют свои ограничения. В какой-то момент необходимо убедиться, что код действительно работает корректно при выполнении. Для этого и существует pytest.

Установка и использование

$ pip install pytest $ $ # Запустите с помощью $ pytest your_test_file.py

Pytest обладает широкими функциональными возможностями, но его самая простая и полезная функция — это также и самая прямая связь с другими инструментами: директива assert . Если проверяемое условие ложно, тест завершается неудачей. Вот и всё. Не нужно изучать сложные фреймворки, чтобы написать что-то полезное.

Предположим, у нас теперь есть версия кода, которая корректно обрабатывает отсутствующие электронные письма, а также пример функции base_order . Вот тест, который защищает логику скидки:

import pytest @pytest.fixture def base_order(): return { «id»: «order-123», «customer_email»: «[email protected]», «created_at»: «2025-01-15T10:30:00», «items»: [ {«price»: 20, «quantity»: 2}, {«price»: 5, «quantity»: 1}, ], } def test_calculate_total_applies_10_percent_discount(base_order): base_order[«discount_code»] = «SAVE10» total = calculate_total(base_order) subtotal = (20 * 2) + (5 * 1) expected = subtotal * 0.9 assert total == expected

А вот тесты, которые защищают обработку электронной почты, в частности, от ошибки, которую мы выявили в начале, когда вызов метода `.lower()` для отсутствующего адреса электронной почты приводил к сбою всей функции:

def test_build_order_summary_returns_valid_email(base_order): summary = build_order_summary(base_order) assert «email» in summary assert summary[«email»].endswith(«@example.com») def test_build_order_summary_when_email_missing(base_order): base_order.pop(«customer_email») summary = build_order_summary(base_order) assert summary[«email»] == «»

Второй тест тоже важен. Без него отсутствие адреса электронной почты — это неявное предположение: код, который отлично работает в режиме разработки, выдает ошибку AttributeError при первом же поступлении реального заказа без этого поля. С ним же предположение становится явным и проверяется каждый раз при запуске набора тестов.

Вот такое разделение труда стоит помнить. Ruff обнаруживает неиспользуемые импорты и мертвые переменные. Mypy выявляет некорректные предположения о типах данных. Pytest обнаруживает нечто иное: он защищает поведение. Когда вы меняете способ обработки отсутствующих полей в функции build_order_summary или проводите рефакторинг функции calculate_total , именно pytest сообщает вам, сломали ли вы что-то, что раньше работало. Это другой вид защиты, и он работает на другом уровне, чем все, что было до него.

Инструмент №5: Потому что ваша память — ненадежная система контроля качества.

Даже при наличии хорошего набора инструментов есть один очевидный недостаток: его можно забыть запустить. Вот тут-то и пригодится такой инструмент, как pre-commit. Pre-commit — это фреймворк для управления и поддержки многоязычных хуков, например, тех, которые запускаются при отправке кода в GitHub или push в ваш репозиторий.

Установка и использование

Стандартная настройка включает в себя установку через pip, добавление файла .pre-commit-config.yaml и запуск команды pre-commit install , чтобы хуки запускались автоматически перед каждым коммитом в вашу систему контроля версий, например, GitHub.

Простая конфигурация может выглядеть так:

репозитории: — репозиторий: https://github.com/psf/black ревизия: 24.10.0 хуки: — id: black — репозиторий: https://github.com/astral-sh/ruff-pre-commit ревизия: v0.11.13 хуки: — id: ruff — id: ruff-format — репозиторий: local хуки: — id: mypy имя: mypy запись: mypy язык: system типы: [python] этапы: [pre-push] — id: pytest имя: pytest запись: pytest язык: system pass_filenames: false этапы: [pre-push]

Теперь запустите его с помощью…

$ pre-commit install pre-commit installed at .git/hooks/pre-commit $ pre-commit install —hook-type pre-push pre-commit installed at .git/hooks/pre-push

С этого момента проверки запускаются автоматически при изменении и фиксации/отправке вашего кода.

  • git commit → triggers black, ruff, ruff-format
  • git push → запускает mypy и pytest

Вот пример.

Допустим, у нас есть следующий код на Python в файле test1.py.

from datetime import datetime import json def calculate_total(order): total = 0 discount = 0 for item in order[«items»]: total += item[«price»] * item[«quantity»] if order.get(«discount_code»): total *= 0.9 return round(total, 2)

Создайте файл с именем .pre-commit-config.yaml, содержащий приведенный выше YAML-код. Теперь, если файл test1.py отслеживается Git, вот какой вывод следует ожидать после его фиксации.

$ git commit test1.py [INFO] Инициализация среды для https://github.com/psf/black. [INFO] Инициализация среды для https://github.com/astral-sh/ruff-pre-commit. [INFO] Установка среды для https://github.com/psf/black. [INFO] После установки эта среда будет использована повторно. [INFO] Это может занять несколько минут… [INFO] Установка среды для https://github.com/astral-sh/ruff-pre-commit. [INFO] После установки эта среда будет использована повторно. [INFO] Это может занять несколько минут… black…………………………………………………………..Сбой — идентификатор хука: black — файлы были изменены этим хуком. Переформатировано test1.py Все готово! ✨ 🍰 ✨ 1 файл переформатирован. ruff (устаревший псевдоним)………………………………………………Сбой — идентификатор хука: ruff — код завершения: 1 test1.py:1:22: F401 [*] `datetime.datetime` импортирован, но не используется | 1 | from datetime import datetime | ^^^^^^^^ F401 2 | import json | = help: Удалить неиспользуемый импорт: `datetime.datetime` test1.py:2:8: F401 [*] `json` импортирован, но не используется | 1 | from datetime import datetime 2 | import json | ^^^^ F401 | = help: Удалить неиспользуемый импорт: `json` test1.py:7:5: F841 Локальная переменная `discount` назначена, но никогда не используется | 5 | def calculate_total(order): 6 | total = 0 7 | discount = 0 | ^^^^^^^^ F841 8 | 9 | for item in order[«items»]: | = help: Remove assignment to unused variable `discount` Found 3 errors. [*] 2 fixable with `—fix` (1 hidden fix can be enabled with `—unsafe-fixes` option).

Инструмент №6: Потому что «правильный» код всё ещё может быть неисправен.

Есть еще одна категория проблем, которую, как мне кажется, недооценивают при разработке кода: производительность. Функция может быть логически корректной, но при этом оказаться неправильной на практике, если она слишком медленная или потребляет слишком много памяти.

Мне нравится использовать для этого инструмент профилирования под названием py-spy. Py-spy — это инструмент для выборочного профилирования программ на Python. Он может профилировать Python без перезапуска процесса или изменения кода. Этот инструмент отличается от других, которые мы обсуждали, тем, что обычно его не используют в автоматизированных конвейерах. Вместо этого, это скорее разовый процесс, который запускается для кода, уже отформатированного, проверенного на синтаксис, типизированного и протестированного.

Установка и использование

$ pip install py-spy

Теперь вернёмся к примеру с «десятью лучшими». Вот ещё раз исходная функция:

Вот снова исходная функция:

def recent_order_totals(orders): summaries = [] for order in orders: summaries.append(build_order_summary(order)) summaries.sort(key=lambda x: x[«created_at»], reverse=True) return summaries[:10]

Если в памяти у меня хранится только несортированная коллекция, то да, всё равно потребуется некоторая логика упорядочивания, чтобы определить, какие десять элементов являются самыми последними. Суть не в том, чтобы полностью избежать упорядочивания, а в том, чтобы избежать полной сортировки всего набора данных, если мне нужны только десять лучших элементов. Профайлер помогает достичь этого более точного уровня.

С помощью py-spy можно выполнить множество различных команд для профилирования кода. Пожалуй, самая простая из них:

$ py-spy top python test3.py Сбор образцов из 'python test3.py' (python v3.11.13) Всего образцов 100 GIL: 22.22%, Активные: 51.11%, Потоки: 1 %Собственное %Общее собственное время Общее время Функция (имя файла) 16.67% 16.67% 0.160s 0.160s _path_stat () 13.33% 13.33% 0.120s 0.120s get_data () 7.78% 7.78% 0.070s 0.070s _compile_bytecode () 5.56% 6.67% 0.060s 0.070s _init_module_attrs () 2.22% 2.22% 0.020s 0.020s _classify_pyc () 1.11% 1.11% 0.010s 0.010s _check_name_wrapper () 1.11% 51.11% 0.010s 0.490s _load_unlocked () 1.11% 1.11% 0.010s 0.010s cache_from_source () 1.11% 1.11% 0.010s 0.010s _parse_sub (re/_parser.py) 1.11% 1.11% 0.010s 0.010s (importlib/metadata/_collections.py) 0.00% 51.11% 0.010s 0.490s _find_and_load () 0.00% 4.44% 0.000s 0.040s (pygments/formatters/__init__.py) 0.00% 1.11% 0.000s 0.010s _parse (re/_parser.py) 0.00% 0.00% 0.000s 0.010s _path_importer_cache () 0.00% 4.44% 0.000s 0.040s (pygments/formatter.py) 0.00% 1.11% 0.000s 0.010s compile (re/_compiler.py) 0.00% 50.00% 0.000s 0.470s (_pytest/_code/code.py) 0.00% 27.78% 0.000s 0.250s get_code () 0.00% 1.11% 0.000s 0.010s <модуль> (importlib/metadata/_adapters.py) 0.00% 1.11% 0.000s 0.010s <модуль> (email/charset.py) 0.00% 51.11% 0.000s 0.490s <модуль> (pytest/__init__.py) 0.00% 13.33% 0.000s 0.130s _find_spec (<замороженный importlib._bootstrap>) Нажмите Control-C для выхода или ? для справки.

Функция `top` позволяет в режиме реального времени отслеживать, какие функции занимают больше всего времени, что делает её самым быстрым способом сориентироваться перед выполнением более детальных действий.

Как только мы поймем, что может возникнуть проблема, мы можем рассмотреть альтернативные варианты реализации нашего кода. В нашем примере одним из вариантов было бы использование heapq.nlargest в нашей функции:

from datetime import datetime from heapq import nlargest def recent_order_totals(orders): return nlargest( 10, (build_order_summary(order) for order in orders), key=lambda x: datetime.fromisoformat(x[«created_at»]), )

Новый код по-прежнему выполняет сравнения, но избегает полной сортировки каждого сводного отчета только для того, чтобы отбросить почти все из них. В моих тестах на больших входных данных версия с использованием heapq оказалась в 2–3 раза быстрее, чем исходная функция. А в реальной системе наилучшей оптимизацией часто является вообще не решать эту задачу на Python. Если данные поступают из базы данных, я обычно предпочитаю запрашивать у базы данных 10 самых последних строк напрямую.

Я поднимаю этот вопрос потому, что советы по повышению производительности очень быстро становятся расплывчатыми. «Сделайте это быстрее» — бесполезно. «Избегайте сортировки всего, когда мне нужно всего десять результатов» — полезно. Профайлер помогает достичь более точного уровня.

Ресурсы

Вот официальные ссылки на GitHub для каждого инструмента:

+————+———————————————+ | Инструмент | Официальная страница | +————+———————————————+ | Ruff | https://github.com/astral-sh/ruff | | Black | https://github.com/psf/black | | mypy | https://github.com/python/mypy | | pytest | https://github.com/pytest-dev/pytest | | pre-commit | https://github.com/pre-commit/pre-commit | | py-spy | https://github.com/benfred/py-spy | +————+———————————————-+

Следует также отметить, что многие современные IDE, такие как VSCode и PyCharm, имеют плагины для этих инструментов, которые предоставляют обратную связь по мере ввода текста, что делает их еще более полезными.

Краткое содержание

Главное преимущество Python — скорость, с которой вы можете перейти от идеи к работающему коду, — это также то, что делает инвестиции в дисциплинированный инструментарий оправданными. Язык не помешает вам делать предположения о структуре данных, оставлять неиспользуемый код или писать функцию, которая идеально работает на тестовых входных данных, но дает сбой в продакшене. Это не критика Python. Это просто компромисс, на который вы идете.

Инструменты, описанные в этой статье, помогают восстановить часть этой безопасности без ущерба для скорости.

Black обрабатывает форматирование, так что вам больше никогда не придется об этом думать. Ruff выявляет мелкие подозрительные детали — неиспользуемые импорты, присвоенные, но игнорируемые переменные — прежде чем они незаметно проникнут в релиз. Mypy заставляет вас честно оценивать структуру передаваемых данных, превращая неясные сбои во время выполнения в раннюю, конкретную обратную связь. Pytest защищает поведение, так что, когда вы что-то меняете, вы сразу знаете, что сломали. Pre-commit делает все это автоматическим, устраняя самый большой недостаток любого ручного процесса: необходимость помнить о его запуске.

Py-spy немного отличается от остальных. Вы не запускаете его при каждом коммите. Вы обращаетесь к нему, когда что-то правильное все еще работает слишком медленно — когда вам нужно перейти от «ускорения» к чему-то достаточно точному, чтобы действительно предпринять какие-либо действия.

Ни один из этих инструментов не заменит тщательного анализа вашего кода. Они лишь уменьшают количество мест, где могут спрятаться ошибки. А в таком либеральном языке, как Python, это очень ценно.

Обратите внимание, что существует несколько инструментов, которые могут заменить любой из упомянутых выше, поэтому, если у вас есть любимый линтер, отличный от Ruff, например, вы можете смело использовать его в своей работе.

Источник: towardsdatascience.com

✅ Найденные теги: Python, новости, ошибки, производство, Рабочий Процесс, Создание

ОСТАВЬТЕ СВОЙ КОММЕНТАРИЙ

Каталог бесплатных опенсорс-решений, которые можно развернуть локально и забыть о подписках

галерея

Карта памяти SanDisk Extreme PRO 2TB на столе перед черным кейсом.
Космонавт работает на борту космической станции, окружённый оборудованием и проводами.
Древний океан с причудливыми существами эдиакарского периода на дне.
Капли дождя падают на землю, образуя брызги на мокрой почве.
Капли дождя падают на землю, создавая брызги на мокрой почве.
Вид на Землю из космоса через иллюминатор с силуэтом наблюдающего человека.
Робот-гуманоид Tesla с черной головой и белым туловищем на фоне.
Два персонажа сражаются световыми мечами на темном фоне сцены из фильма.
Археологическая находка: каменная гробница и скелет в древнем сооружении.
Image Not Found
Карта памяти SanDisk Extreme PRO 2TB на столе перед черным кейсом.

Карта памяти SanDisk Extreme Pro UHS-II на 2 Тбайта оценена в $2000

SanDisk незаметно выпустила более ёмкостную версию своей карты памяти Extreme Pro UHS-II, но привлекла она внимание не возможностями, которые мы всё же затронем, а ценником, который выглядит крайне неприятно. Согласно страничке на Amazon, новинка имеет интерфейс SDXC…

Апр 9, 2026
Вид на Землю из космоса через иллюминатор с силуэтом наблюдающего человека.

Сегодня астронавты миссии «Артемида-2» установят новый рекорд расстояния от Земли.

Во время полета космического корабля «Орион» вокруг Луны экипаж «Артемиды II» побьет рекорд, установленный «Аполлоном-13» в 1970 году. Эндрю Лишевски, старший репортер отдела новостей. Публикации этого автора будут добавляться в вашу ежедневную рассылку по электронной почте и…

Апр 9, 2026
Археологическая находка: каменная гробница и скелет в древнем сооружении.

Исследование древнего индивида из Переславля-Залесского указало на его генетически смешанное происхождение

саркофаг V и погребение: А – вид с востока; Б – саркофаг V после снятия погребения и поздней плиты, вид сверху. © ИОГен РАН Археогенетическое исследование погребения из саркофага XIV-XV века в Спасо-Преображенском соборе в Переславле-Залесском показало,…

Апр 9, 2026
Два человека пожимают руки на фоне синего логотипа компании.

Intel присоединяется к проекту Илона Маска по производству чипов Terafab.

Вкратце Источник изображения: Intel (откроется в новом окне) Компания Intel присоединится к SpaceX и Tesla в стремлении построить новый завод по производству полупроводников в США, в штате Техас, хотя масштабы ее вклада пока неясны. «Наша способность проектировать,…

Апр 8, 2026

Впишите свой почтовый адрес и мы будем присылать вам на почту самые свежие новости в числе самых первых