Программистика
کانال بسته
Лучший канал про python Ссылка для друга: https://t.me/+Ai6ughKtf5g2ZmFi Купить рекламу: https://telega.in/c/+Ai6ughKtf5g2ZmFi Админ: @JeyRahol По рекламе: @ReivuManager
نمایش بیشتر5 791
مشترکین
-624 ساعت
+157 روز
-4130 روز
آرشیو پست ها
5 790
👩💻 pydantic-settings: Конфигурация приложений без хаоса и костылей
Если ты хранишь настройки в случайных местах: то в
.env, то в yaml, то в переменных окружения, а потом городишь костыли из os.getenv() с дефолтами — пора навести порядок.
❌ Было (типичный хаос):
import os
from dotenv import load_dotenv
load_dotenv() # А если файла нет?
DB_HOST = os.getenv("DB_HOST", "localhost") # Дефолты раскиданы по коду
DB_PORT = int(os.getenv("DB_PORT", 5432)) # Приведение типов вручную
DEBUG = os.getenv("DEBUG", "False") == "True" # Конвертация строки в bool
# А если нужно добавить новую настройку? Ищем все os.getenv() по проекту...
✅ Стало (pydantic-settings):
from pydantic_settings import BaseSettings
from pydantic import Field
class Settings(BaseSettings):
db_host: str = "localhost"
db_port: int = 5432
debug: bool = False
api_key: str = Field(..., env="API_KEY") # Обязательная переменная
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
config = Settings() # Всё! Настройки загружены и проверены
print(f"Подключаемся к {config.db_host}:{config.db_port}")
pydantic-settings — это расширение Pydantic для управления конфигурацией. Оно умеет загружать настройки из кучи источников (env-файлы, переменные окружения, секреты) и валидировать их как обычные Pydantic-модели.
➕ Почему это меняет всё:
🟢 Единый источник правды: Все настройки в одном классе, а не раскиданы по модулям
🟢 Автоматическая загрузка: Ищет переменные в .env, окружении, ini-файлах — в правильном порядке
🟢 Валидация и типы: Pydantic сам преобразует строки в нужные типы и проверяет значения
🟢 Секреты и чувствительные данные: Поддержка docker secrets, vault и других хранилищ
➖ Особенности:
➖ Ещё одна зависимость в проекте (но если уже есть Pydantic, то она минимальна)
➖ Для совсем простых скриптов может быть избыточным
🧪 Пример продвинутой конфигурации:
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
env_prefix="APP_", # APP_DB_HOST, APP_DEBUG
secrets_dir="/run/secrets", # Для Docker Swarm/Kubernetes
)
db_url: str
redis_url: str = "redis://localhost:6379"
log_level: str = "INFO"
# Вложенные конфиги
sentry: dict = {}
# Настройки автоматически загрузятся из APP_DB_URL, APP_REDIS_URL и т.д.
config = Settings()
Установка:
pip install pydantic-settings
# Или если уже есть Pydantic v2:
pip install "pydantic-settings[dotenv]"
💡 Идеально для:
- Микросервисов и контейнеризованных приложений
- Проектов с разными окружениями (dev/stage/prod)
- Команд, где каждый тянет настройки по-своему
- Любого проекта, где устали от os.getenv()
Конфигурация должна быть простой, типобезопасной и предсказуемой. С pydantic-settings она именно такая.
🐱 Ссылка на репозиторий
Программистика || #Library5 790
📂 PyTorch: Custom Ops - когда графы не справляются
Если стандартные операции торчат узким местом, можно их переписать. Не магией, а C++ и CUDA.
1️⃣ В чём суть
Кастомные операции (Custom Ops) позволяют заменить узлы в вычислительном графе PyTorch на свои, оптимизированные реализации.
2️⃣ Практика
# 1. Пишем ядро на C++/CUDA (my_custom_op.cpp)
#include <torch/extension.h>
torch::Tensor my_fast_op(torch::Tensor input) {
// Твоя хрень вместо медленного PyTorch-эквивалента
auto output = input.clone();
// ... CUDA kernel launch или векторная оптимизация ...
return output;
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("my_fast_op", &my_fast_op, "My fast custom op");
}
# 2. Компилируем и загружаем в Python
from torch.utils.cpp_extension import load
custom_ops = load(name="my_ops", sources=["my_custom_op.cpp"])
# 3. Используем в модели
output = custom_ops.my_fast_op(input_tensor)
3️⃣ Почему это важно
🔴 Экзотичные активации 🔴 Специфичные потери (loss functions) 🔴 Квантование/спарсификация на лету 🔴 Аппаратно-специфичные оптимизации 🔴 Градиентные трюки, которых нет в torch.autograd4️⃣ Нюансы
🔴 Нужно писать градиент вручную (forward/backward) 🔴 Отладка - жесть (gdb, CUDA-GDB) 🔴 Совместимость версий PyTorch 🔴 Может сломать JIT-трассировкуПрограммистика || #Статья
5 790
🤔 Mutable 🆚 Immutable: Когда менять, а когда — создавать заново
В Python мир делится на изменяемые (mutable) и неизменяемые (immutable) объекты. Новички часто путают их, создавая баги с побочными эффектами, а сеньоры спорят, что лучше для производительности. Разбираемся, где мутабельность — это сила, а где — источник хаоса.
🧱 Когда `mutable` — это хорошо (и удобно):
Изменяемые объекты (списки, словари, множества) идеальны, когда нужно накапливать или модифицировать данные без создания копий.
1️⃣. Накопление данных в цикле:
# Эффективно и понятно
results = []
for item in data_stream:
processed = process_item(item)
results.append(processed) # Меняем на месте
2️⃣. Кэширование/мемоизация:
cache = {} # Изменяемый словарь
def get_expensive_result(key):
if key not in cache:
cache[key] = calculate_expensive_thing(key)
return cache[key]
3️⃣. Работа с большими данными:
# Избегаем копирования больших структур
def normalize_data(data_list):
for i in range(len(data_list)):
data_list[i] = data_list[i].lower() # Меняем на месте
🧱 Когда `mutable` — это ПЛОХО (опасно):
1. Неожиданные побочные эффекты:
❌ ОПАСНО:
def add_to_list(item, target=[]):
target.append(item)
return target
print(add_to_list(1)) # [1]
print(add_to_list(2)) # [1, 2] ??? Аргумент по умолчанию сохранился!
2. Проблемы в многопоточности:
# Разные потоки меняют один список → состояние гонки
shared_list = []
def worker():
shared_list.append(threading.get_ident()) # Кто успел, тот и записал
3. Сложная отладка:
config = {"debug": True, "level": "info"}
def setup_logging(cfg):
cfg["level"] = "debug" # Неявно меняет глобальный config!
setup_logging(config)
print(config["level"]) # "debug" — а кто менял?
🧱 Когда `immutable` — это безопасность:
Неизменяемые объекты (строки, кортежи, frozenset) защищают от случайных изменений и упрощают логику.
1️⃣. Ключи словаря:
# Только неизменяемые объекты могут быть ключами
valid_cache = {("user", 123): "data"} # Кортеж — OK
invalid_cache = {["user", 123]: "data"} # TypeError
2️⃣. Защита константных данных:
# Координаты, настройки, конфиги
DEFAULT_SETTINGS = ("localhost", 8080) # Никто не сможет изменить
CONFIG = frozenset(["api_key", "timeout"]) # Гарантированно неизменяемо
3️⃣. Предсказуемость в функциональном стиле:
def process_data(data_tuple):
# Можно быть уверенным — data_tuple не изменился
return sum(data_tuple) / len(data_tuple)
💡 Практическое правило:
🟢 Используй mutable, когда:
- Часто меняешь данные - Работаешь с большими объектами - Нужна максимальная производительность🟢 Выбирай immutable, когда:
- Данные константны или редко меняются - Нужна потокобезопасность - Объект используется как ключ - Хочется избежать побочных эффектов🗣 Запомни: Мутабельность — не "хорошо" или "плохо", а инструмент. Как молоток: в нужных руках строит, в неумелых — разбивает. Выбор между
list и tuple, dict и MappingProxyType — это выбор между гибкостью и безопасностью.
🔗 Подробнее в статье
Умные разработчики знают, когда позволить объекту меняться, а когда заморозить его для вечности.
Программистика || #Статья5 790
Почему 90% фронтендеров никогда не выйдут на большие чеки?
Потому что бездумно потребляют «обучающий» контент. И топчутся в говне месяцами, рвут жопу за нищие копейки и терпят даунов-начальников, лишь бы не вылететь на улицу.
Хотя чтобы стать разработчиком, надо в первую очередь научиться думать как разработчик.
Хорошая новость в том, что тебе ничего кардинально менять не нужно, чтобы войти в «золотую десятку» самых успешных. Не надо бросать курсы или увольняться.
Достаточно заменить источник информации:
👉 Фронтенд
👉 Верстка
👉 Node.JS
10–15 минут в день смотри на реальные задачи, живой код, ошибки, подходы и паттерны, которыми топовые фронты пользуются прямо сейчас.
Это самый простой и дешёвый способ стать уважаемым специалистом, так что подписывайся.
5 790
👩💻 От `requirements.txt` до `uv`: эволюция управления зависимостями в Python
Держать в проекте
requirements.txt с версиями ==, >=, ~= и надеяться, что у всех разработчиков будет одинаковое окружение — это как играть в русскую рулетку при каждом pip install. А если добавить pipenv, poetry, pdm и новый uv — голова идёт кругом.
Пора разобраться, какой инструмент когда использовать, чтобы не тратить часы на "у меня работает".
🧱 Проблема: почему старый добрый `requirements.txt` уже недостаточно
requirements.txt — это просто список пакетов, не более. Нет блокировки версий зависимостей (только верхнего уровня), нет разделения dev/prod зависимостей, нет воспроизводимости сборок.
# requirements.txt — классика проблем
Django>=4.2 # Какая версия установится? 4.2? 5.0?
pandas # Без версии — катастрофа воспроизводимости
numpy==1.24.3 # А если другой пакет требует numpy>=1.25?
Результат: "На моей машине работало" — классическая фраза на код-ревью.
📌 Современные инструменты: кто есть кто
1. `pipenv` — первая попытка исправить ситуацию
pipenv install django # Создаёт Pipfile и Pipfile.lock
🟢 Плюсы: Единый файл для всех зависимостей, lock-файл для воспроизводимости.
⚠️ Минусы: Медленный, уже считается устаревшим, проблемы с производительностью.
2. `poetry` — стандарт для библиотек и приложений
# pyproject.toml — всё в одном файле
[tool.poetry]
name = "myproject"
[tool.poetry.dependencies]
python = "^3.9"
django = "^4.2"
pandas = "1.5.3"
[tool.poetry.group.dev.dependencies]
pytest = "^7.0"
black = "^23.0"
🟢 Плюсы: Управление версиями, публикация пакетов, быстрое разрешение зависимостей.
⚠️ Минусы: Своя экосистема команд, иногда сложности с Docker-образами.
3. `pdm` — современная альтернатива с PEP 582
pdm add django # Использует __pypackages__ вместо virtualenv
🟢 Плюсы: Очень быстрый, поддерживает PEP 582 (пакеты рядом с кодом).
⚠️ Минусы: Менее популярен, некоторые инструменты не поддерживают.
4. `uv` — новый игрок от создателей `ruff`
uv pip install django # Невероятно быстро
uv venv # Создание виртуального окружения за миллисекунды
uv pip compile pyproject.toml -o requirements.txt # Генерация lock-файла
🟢 Плюсы: Невероятная скорость (написано на Rust), совместимость с существующими файлами.
⚠️ Минусы: Очень молодой проект, некоторые фичи ещё в разработке.
🧪 Практический пример: миграция с `requirements.txt` на современный стек
Было (хаос):
# requirements.txt
django>=4.2
gunicorn
psycopg2-binary
celery[redis]
django-debug-toolbar
pytest
black
Стало (порядок с `pyproject.toml`):
# pyproject.toml
[project]
name = "myproject"
dependencies = [
"django>=4.2,<5.0",
"gunicorn",
"psycopg2-binary",
"celery[redis]>=5.3",
]
[project.optional-dependencies]
dev = [
"django-debug-toolbar",
"pytest>=7.0",
"black>=23.0",
]
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
И отдельный файл для точных версий:
# Генерируем lock-файл одной командой
uv pip compile pyproject.toml -o requirements.txt
uv pip compile pyproject.toml --extra dev -o requirements-dev.txt
📌Управление зависимостями не должно быть болью. Современные инструменты делают его быстрым, предсказуемым и безопасным. Главное — выбрать подходящий для вашей команды и проекта.
Программистика ||#article5 790
First Step in NLP 2.0
Первый из серии мини-курсов, посвященный введению в Natural Language Processing и основам работы со строками в Python. Обновлен в ноябре 2023 года. Курс проводится преподавателями онлайн-магистратуры "Машинное обучение и высоконагруженные системы" факультета компьютерных наук Высшей школы экономики
➡️ Читать статью
Программистика || #article
5 790
🖥 glom - это библиотека Python, предназначенная для упрощения доступа и преобразования вложенных структур данных! Она предоставляет декларативный подход к извлечению и модификации данных, позволяя разработчикам эффективно работать с комплексными иерархиями объектов и коллекций.
🐱 Ссылка на GitHub
Программистика || #Library
5 790
🌐 Web‑технологии
Вы пользуетесь интернетом. Но знаете ли вы, как создаются сайты и веб‑приложения? Если вы когда‑то слышали слова HTML, HTTP, сервер, API, но не уверены, что они значат — мы поможем разобраться!
В этом мини‑курсе вы в деталях узнаете о том, как устроен веб. Вы поймёте, как браузер получает информацию с сервера, как формируется страница, и почему всё работает почти мгновенно. Курс покажет, как взаимодействуют разные части веб‑приложения и как код превращается в то, что вы видите на экране. Всё это подано просто и доступно, чтобы вы могли сразу попробовать создавать свои первые сайты.
📎 Ссылка
Программистика || #Course
5 790
Ускорьте работу разработки
Cursor — IDE с ИИ, которая помогает писать код по описанию, искать ошибки и приводить проект в порядок быстрее обычного.
Что делает:
• генерирует и дополняет код на естественном языке
• объясняет непонятные участки
• находит ошибки и предлагает фиксы
• подсказывает оптимизации и практики
• работает как полноценная IDE, только быстрее
Для компаний — это реальный способ ускорить команду без найма дополнительных разработчиков: меньше рутины, больше времени на архитектуру и продуктовые задачи.
Попробовать Cursor: https://clck.ru/3QeE4z
Поставщик в России — «Системный софт» (оплата в рублях, закрывающие документы и поддержка при подключении).
Перейти на сайт
#реклама 16+
syssoft.ru
О рекламодателе
5 790
Нейросеть слила самую крупную базу частных паролей ❗️
Появился ИИ, который умеет находить твой пароль среди миллиардов утечек — под угрозой соцсети, банки и все аккаунты
Канал «BezBrain» собирает все важные новости из мира технологий и выложила ссылку на данный ИИ
Заходи и за минуту проверь, не слит ли твой пароль: https://t.me/+ZMIsaHoWamllYjNi
5 790
👩💻 `zip`: Когда нужно итерировать по нескольким спискам одновременно
Многие, когда нужно работать с двумя списками параллельно, до сих пор пишут циклы с индексами:
names = ["Алиса", "Боб", "Карл"]
ages = [25, 30, 35]
for i in range(len(names)):
print(f"{names[i]}: {ages[i]} лет")
🟢 Формально работает, но это хрупко и не по-питонячьи. Стоит изменить один список — и всё сломается.
Что делает `zip`
zip объединяет несколько итерируемых объектов, возвращая кортежи из соответствующих элементов:
names = ["Алиса", "Боб", "Карл"]
ages = [25, 30, 35]
for name, age in zip(names, ages):
print(f"{name}: {age} лет")
Преимущества:
🟢 Нет привязки к индексам — код читается как предложение.
🟢 Работает с любым количеством итерируемых объектов.
🟢 Автоматически останавливается на самом коротком списке (без IndexError).
Практический пример с тремя списками:
products = ["Яблоки", "Бананы", "Апельсины"]
prices = [100, 80, 120]
quantities = [5, 3, 2]
for product, price, qty in zip(products, prices, quantities):
total = price * qty
print(f"{product}: {total} руб.")
Когда списки разной длины (и нужно это обработать)
По умолчанию zip обрезает по самому короткому. Если нужны все значения:
from itertools import zip_longest
names = ["Алиса", "Боб", "Карл", "Дина"]
ages = [25, 30, 35]
for name, age in zip_longest(names, ages, fillvalue="неизвестно"):
print(f"{name}, возраст: {age}")
# Дина, возраст: неизвестно
А если всё-таки нужны индексы?
Используйте enumerate вместе с zip:
for i, (name, age) in enumerate(zip(names, ages), start=1):
print(f"{i}. {name}: {age} лет")
Когда `range(len(...))` может быть уместен?
Только если действительно нужны именно индексы для манипуляций (например, алгоритм сортировки с перемещениями). Но даже тогда часто есть лучшие способы.
Запомни:
🟢 zip — для параллельной итерации
🟢 enumerate — когда нужны и индекс, и значение
🟢 Вместе они заменяют 95% циклов с range(len(...))
Работайте с данными напрямую, а не через их индексы — код станет чище и надёжнее.
Программистика|| #doc5 790
«Сделано по ГОСТу»: как обеспечить защищенный канал
Компании-разработчики редко сталкиваются со строгими требованиями по безопасности. Часто под «безопасностью» понимается именно защита персональных данных (ПД) сотрудников и контрагентов. Остальные требования устанавливают заказчики.
Так, если компания разрабатывает приложение для банка, то заказчик может потребовать, чтобы был соблюден стандарт PCI DSS. Или, например, при разработке сайта с обработкой ПД может понадобиться выполнить требования 152-ФЗ и 21 Приказа ФСТЭК по конкретному уровню защищенности.
Инфраструктура, на базе которой происходит разработка или находится само приложение, тоже должна соответствовать данным стандартам. Более того, если компания начинает оказывать услуги для государственных организаций или организаций, относящихся к КИИ, необходимо задуматься о передаче данных. Именно об этом мы и поговорим в статье.
➡️ Читать статью
Программистика || #article
5 790
👀 Изучаем Python: теория, практика, настройка инструментов
Рассказываем, что изучать после основ: как настраивать инструменты, работать с базами данных, создавать программы с интерфейсом и использовать Python для парсинга. А еще собрали интересные задачи, чтобы можно было закрепить знания.
🌐 СсылкаПрограммистика || #Course
5 790
💻 Вопрос с собеса: Чем отличается list от tuple?
Это один из тех вопросов, который задают почти всем - и все равно на нем спотыкаются. Просто сказать «list изменяемый, а tuple нет» - мало. Надо понимать последствия.
👉 Базовый ответ (которого ждут):
-
list - изменяемый (mutable). Можно менять, добавлять, удалять элементы.
- tuple - неизменяемый (immutable). После создания его нельзя изменить.
👉 Глубокий разбор (который ждут от мидла+):
1. Производительность и память:
Из-за неизменяемости tuple работает быстрее и занимает меньше памяти. Python может кэшировать и оптимизировать их.
import sys
import timeit
lst = [1, 2, 3]
tup = (1, 2, 3)
print(sys.getsizeof(lst)) # 120 байт (может меняться)
print(sys.getsizeof(tup)) # 72 байта
2. Безопасность и хэшируемость:
tuple можно использовать как ключ в словаре, list - нет. Это важно для кэшей, индексов.
valid_cache = {(1, 2): "result"} # Работает
invalid_cache = {[1, 2]: "result"} # TypeError: unhashable type
3. Семантическое различие:
- list - это однородная коллекция (все элементы обычно одного типа).
- tuple - это гетерогенная коллекция, часто используется как запись с полями (как namedtuple или дата-класс).
# Плохо: tuple как list
for item in ("apple", "banana", "cherry"):
print(item)
# Хорошо: tuple как структура
person = ("Alice", 30, "engineer") # имя, возраст, профессия
4. Неизменяемость - только на верхнем уровне:
Важный подвох! Если внутри tuple лежит изменяемый объект - его можно изменить.
danger = (1, 2, [3, 4])
danger[2].append(5) # Опасно! Работает!
print(danger) # (1, 2, [3, 4, 5])
💡 Итоговый ответ для собеса:
1. Основное: list - mutable, tuple - immutable 2. Следствия: tuple быстрее, занимает меньше памяти, хэшируем 3. Семантика: list для однородных данных, tuple для структур 4. Ловушка: неизменяемость только на верхнем уровне 5. Когда что использовать: - tuple для константных данных, ключей словаря - list для динамических коллекцийЗапомни: на джуньорских позициях хватит первого пункта, но сеньоры ждут все пять. Программистика||#jobs
5 790
👩💻 Ruff: Забудь про Flake8, isort и Black — теперь всё в одном
Держать в проекте 5 разных линтеров, форматтеров и сортировщиков импортов - это боль. Конфликты конфигов, разная скорость работы, тонны зависимостей. Пора объединять силы.
Ruff - это не просто линтер. Это невероятно быстрый инструмент, написанный на Rust, который заменяет собой Flake8, isort, pydocstyle, eradicate и даже частично Black. Он работает в 10-100 раз быстрее аналогов.
👉 Как это меняет жизнь:
Раньше (все отдельно):
flake8 .
isort .
black .
pydocstyle .
# ждешь... ждешь...
Теперь (один Ruff):
ruff check . # линтинг
ruff format . # форматирование
ruff check --fix . # автоФИКС!
# готово за секунды
➕ Преимущества:
🟢 Скорость света: На больших кодовых базах разница не минуты, а десятки секунд против часов.
🟢 Всё в одном: Единственный инструмент для проверки стиля, качества, сортировки импортов и форматирования.
🟢 Автофикс: Может сам исправить ~50% найденных проблем. Не просто ругается, а чинит.
🟢 Совместимость: Понимает pyproject.toml, может работать вместе с Black (если пока не готов полностью переходить).
➖ Особенность:
➖ Молодой проект: Не покрывает 100% правил всех legacy-инструментов, но закрывает 95% реальных нужд. Активно развивается.
🧪 Бонус: Базовая конфигурация
Создай pyproject.toml и добавь:
[tool.ruff]
lint.select = ["E", "F", "I", "B", "C90"] # основные категории ошибок
target-version = "py311"
[tool.ruff.format]
quote-style = "double"
💡 Запомни:
🟢 Ruff - это следующий шаг эволюции Python-тулчейна.
🟢 Он экономит не минуты, а часы жизни всей команды.
🟢 Если проект тормозят линтеры - это прямой сигнал попробовать Ruff.
🐱 Ссылка на репозиторий
Программистика|| #doc5 790
👩💻 Black: Форматируй код одним нажатием — забудь о спорах в ревью
Вечные споры в ревью из-за пробелов, переносов строк и запятых? Тратишь минуты (и нервы) на ручное выравнивание кода? Пора остановить это.
Black — это не просто «еще один форматтер». Это бескомпромиссный форматтер кода для Python. Он принимает все решения за тебя. Нет конфигов, нет стилей, нет вариантов. Единственный выбор — использовать его или нет.
👉 Как это выглядит:
До (как у многих):
def very_important_function(template: str, *variables, file: os.PathLike, debug: bool = False):
"""Применяет |template| к |variables| для записи в |file|."""
with open(file, 'w') as f:
# ...
После (магия Black):
def very_important_function(
template: str,
*variables,
file: os.PathLike,
debug: bool = False,
):
"""Применяет |template| к |variables| для записи в |file|."""
with open(file, "w") as f:
# ...
➕ Преимущества:
🟢 Ноль конфигурации: Установил → запустил → забыл. Black сам знает, как должно быть. 🟢 Скорость: Форматирует большие файлы быстрее, чем ты найдешь споры о стиле в документации PEP 8. 🟢 Детерминированность: Один и тот же код всегда форматируется одинаково. В команде у всех будет идентичный стиль. 🟢 Интеграция: Работает из консоли, как плагин для Pre-commit, в CI/CD (например, GitHub Actions).➖ Особенность (не недостаток): ➖ Бескомпромиссность: Не спрашивает мнения. Переносы строк, кавычки, запятые — всё по его правилам. Сначала раздражает, потом вызывает зависимость. 🧪 Бонус: Интеграция с pre-commit Не нужно ничего запускать вручную. Добавь в
.pre-commit-config.yaml:
repos:
- repo: https://github.com/psf/black
rev: 23.12.1
hooks:
- id: black
Теперь код будет форматироваться автоматически перед каждым коммитом.
💡 Запомни:
🟢 Black — это не про вкус, это про согласованность.
🟢 Он экономит время, которое ты тратил на обсуждение стиля.
🟢 Идеальный код по мнению Black может не нравиться лично тебе, но он будет нравиться всей команде и любой системе.
🐱 Ссылка на репозиторий
Программистика|| #doc