Перейти к содержанию
  • Лента
  • Категории
  • Последние
  • Метки
  • Популярные
  • Пользователи
  • Группы
Свернуть
exlends
Категории
  • ru
    Игры
    Образование
    Искусственный Интеллект
    Новости
    Бекенд, разработка серверов
    Фронтенд
    Мобильная разработка
    Языки программирования
    Разработка игр | 3D | 2D
    Базы данных
    CMS
    Системное Администрирование
    Операционные системы
    Маркетинг
    Девайсы
    Сообщество
    Юмор, Мемы

  • en
    Marketing
    Humor
    News
    AI
    Programming languages
    Frontend
    GameDev

  • Блоги

Авторизуйтесь, чтобы написать сообщение

  • Все категории
  • kirilljsxK
    kirilljsx
    DeepSeek V4 - крупное обновление и что нового

    f465e362-4199-440b-baea-253f37cee095-image.jpeg

    DeepSeek V4 - это не одна модель, а сразу две: V4‑Pro и V4‑Flash. Обе поддерживают до 1 миллиона токенов контекста, обе выложены как open‑weights под лицензией MIT, но на практике между ними огромная разница по требованиям к железу и по тому, насколько они вообще подходят для домашнего запуска.

    Если говорить без маркетинга, то главный вопрос здесь не «насколько DeepSeek V4 умный», а «где это реально можно крутить». По официальному релизу и model card видно, что V4‑Pro - это флагман под тяжёлый reasoning, кодинг и agentic‑сценарии, а V4‑Flash - более компактный и быстрый вариант, который ближе к реальному использованию вне дата‑центра.

    Что такое DeepSeek V4

    DeepSeek V4 Preview был анонсирован как новая открытая линейка моделей с упором на длинный контекст и эффективность на agentic‑нагрузках. В официальном описании компания прямо разделяет семейство на DeepSeek‑V4‑Pro для максимального качества и DeepSeek‑V4‑Flash для скорости, цены и более лёгких сценариев.

    С технической точки зрения обе модели относятся к классу MoE, то есть Mixture‑of‑Experts. Это означает, что общий размер модели очень большой, но на каждом токене активируется только часть параметров, поэтому реальные вычислительные затраты ниже, чем можно было бы ожидать по total‑числу параметров.

    Для пользователя это важно по двум причинам. Во‑первых, large‑scale MoE позволяет сочетать высокий уровень качества с более разумной стоимостью инференса. Во‑вторых, даже при этом размер моделей остаётся настолько большим, что локальный запуск без компромиссов быстро упирается в память и пропускную способность системы.

    Кстати не раз уже говорил, что пользуюсь агрегатором polza.ai там как раз можно пощупать саму модельку или поработать с ней через Api

    Чем отличаются V4‑Pro и V4‑Flash

    По официальным спецификациям DeepSeek‑V4‑Pro имеет 1.6T total параметров и 49B active параметров, а DeepSeek‑V4‑Flash - 284B total и 13B active параметров (Это реально дохрена). При этом обе модели поддерживают контекст длиной до 1M токенов и доступны через API, а также как открытые веса для самостоятельного развертывания.

    Практически разница такая:

    Параметр V4‑Flash V4‑Pro
    Total parameters 284B 1.6T
    Active parameters 13B 49B
    Контекст До 1M токенов До 1M токенов
    Основной сценарий Быстрый inference, чат, суммаризация, routing Сложный reasoning, код, long‑context agents
    Реалистичность локального запуска Частично возможен с компромиссами Практически только серверная/кластерная среда

    V4‑Flash - это модель, которую можно рассматривать как базу для личного ассистента, RAG‑помощника, локального чат‑интерфейса или быстрого инженерного copilot‑сценария. V4‑Pro - это уже история про максимальное качество, тяжелые многошаговые задачи, агентные пайплайны и инфраструктуру, которая стоит далеко за пределами обычного домашнего ПК.


    Что у них под капотом

    Официальные материалы подчеркивают, что ключевой акцент в V4 сделан не только на размере модели, но и на эффективности длинного контекста. В архитектуре используются Compressed Sparse Attention и Heavily Compressed Attention, а в релизе DeepSeek это описано как token‑wise compression и DSA для снижения вычислительных и memory‑затрат на длинных окнах контекста.

    По model card, в режиме 1M токенов DeepSeek‑V4‑Pro требует только 27% single‑token inference FLOPs и 10% KV cache по сравнению с DeepSeek‑V3.2. Это очень важный момент: при работе с длинным контекстом узким местом становится не только сам размер весов, но и объём KV‑кэша, поэтому любые оптимизации внимания напрямую влияют на реальную возможность обслуживать большие диалоги, документы, логи и цепочки agent‑вызовов.

    Кстати я сам фиксил многие вещи уже через DeepSeek V4-flash в связке с редактором ZED - и это просто потрясающе!

    Ещё одна важная деталь - режимы reasoning. И V4‑Pro, и V4‑Flash поддерживают три режима: Non‑think, Think High и Think Max. Это означает, что одну и ту же модель можно использовать как в быстром ежедневном режиме, так и в более «тяжёлом» режиме рассуждений, когда важнее качество ответа, чем скорость.

    На практике это особенно интересно для личного ассистента. В обычном режиме модель может быстро отвечать на рутинные вопросы, а в Think High или Think Max - разбирать код, планировать действия, раскладывать задачу по шагам и работать как более обстоятельный агент.


    Ну и если отвечать коротко потянет ли домашний пк эти модельки:

    • Обычный ноутбук или ПК - только API, облачный доступ или небольшие производные модели/дистилляты.
    • Топовый домашний ПК - потенциально V4‑Flash в квантизованном виде, но с большим объемом RAM и частичным offload.
    • Серверная или кластерная среда - V4‑Pro и полноценные long‑context конфигурации.

    Коротко о главном

    DeepSeek V4 - это одна модель или две?
    Это семейство из двух моделей: DeepSeek‑V4‑Pro и DeepSeek‑V4‑Flash.

    У обеих моделей действительно 1M контекст?
    Да, и в официальном релизе, и в model card для обеих моделей указан контекст до 1 миллиона токенов.

    Чем Pro отличается от Flash на практике?
    Pro ориентирован на максимальное качество, сложный reasoning и agentic‑нагрузки, а Flash - на более быстрый и экономичный inference.

    Можно ли поставить DeepSeek V4 на домашний компьютер?
    V4‑Flash теоретически можно запускать локально в квантизованном виде на очень мощном ПК, а V4‑Pro практически относится к серверному классу развёртывания.

    Подходит ли V4‑Flash для личного ассистента?
    Да, это более реалистичный вариант, чем Pro, если нужен локальный или полу‑локальный ассистент с упором на скорость и разумные требования к инфраструктуре. (Кстати с последними версиями OpenClaw flash версия deepseek стала по умолчанию)

    Какие режимы работы есть у моделей?
    Обе модели поддерживают Non‑think, Think High и Think Max.

    Подходят ли эти модели для кода и агентных задач?
    Да, DeepSeek отдельно продвигает V4 как линейку для coding, long‑context agents, document analysis и tool‑calling сценариев.


    0 0 0 Ответить
  • kirilljsxK
    kirilljsx
    Дзен убил reach: что сломалось в алгоритме и как восстановить трафик

    Дзен опять переписал правила игры, и на этот раз удар пришёлся по нишевым авторам и небольшим B2B-каналам. За последние недели я заметил в своём окружении волну жалоб: reach упал на 30-60%, монетизация встала, а алгоритм вообще не берёт новый контент. Не паника - это не ошибка платформы, это её эволюция. И она очень показательна для всех, кто зарабатывает на контенте про нишевые услуги.

    Что именно сломалось в алгоритмах Дзена

    Платформа окончательно отказалась от модели “много подписчиков = много охватов”. Теперь Дзен смотрит совсем на другие сигналы:

    1. Глубина вовлечённости вместо кликов
    Ранее алгоритм радовался любому клику - пользователь открыл, посмотрел превью, ушёл. Теперь платформа считает время на странице, дочитывания до конца и самое важное - возвращается ли юзер к следующему материалу автора. Если вы пишете про установку мини-АТС для офиса, а читатели открыли, прочитали 15% и ушли - алгоритм отправляет материал в “морозилку”.

    2. Поведенческие аномалии как красный флаг
    Видите скачок активности без исторической логики? Алгоритм видит. Если у вас обычно 2-3% дочитываний, но вдруг один материал привлёк 15% - система это заметит и отправит на ручную проверку. Для нишевых каналов это особенно опасно, потому что любой вирусный момент воспринимается как аномалия.

    3. Контекстная релевантность перед количеством
    Дзен теперь показывает материал тестовой группе похожей аудитории. Если реакция хорошая - расширяет охват. Если плохая - замораживает. Для услуговых ниш (консультации, B2B-решения) это значит, что контент должен быть максимально заточен под точную боль целевой аудитории, а не просто “интересным”.

    Почему reach упал именно на нишевых сервисах

    Если вы пишете про SEO, коучинг, юридические услуги или IT-консультации, вот что произошло:

    • Монетизация видео отключена (с 14 апреля). Если ваш канал в основном зарабатывал на видеороликах - сожалею. Нужно переходить на текст и микровидео в форме Reels.
    • Алгоритм требует доказательства стабильности. Раньше можно было запустить канал, выложить 5 статей и уже зарабатывать. Теперь нужно минимум несколько месяцев системной работы, чтобы платформа включила вас в рекламную сеть.
    • Рерайты и “потоковый контент” убиты. Если вы брали чужие идеи и переписывали под себя - алгоритм это видит и штрафует. Для B2B-ниш это особенно актуально, потому что все пишут про одно и то же.

    Как восстановить трафик: практическая схема

    Шаг 1: Переформатируй под поведенческие сигналы

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

    • Заголовок с острой болью (не общий, а конкретный: “Почему ваша логистика теряет 30% на холодной цепи”)
    • Первые 2-3 абзаца - ответ на вопрос, а не теория
    • Подзаголовки каждые 150-200 слов (алгоритм видит структуру)
    • Практический пример или расчёт (конкретные цифры, а не размытые советы)
    • Чек-лист в конце, который читатель может применить сейчас же

    Это увеличивает dwell time на 40-60% против стандартных “7 советов как…”.

    Шаг 2: Собери микроаудиторию из своей целевой ниши

    Не гонись за подписчиками. Вместо этого:

    • Пиши 2-3 раза в неделю про острые проблемы вашей аудитории
    • Оставляй вопрос в конце каждого материала, который провоцирует комментарии (не просто “что вы думаете?”, а конкретный: “А вы учитываете стоимость возврата товара при расчёте логистики? Напишите ваш процент в комментариях”)
    • Отвечай на каждый комментарий в течение часа

    Это формирует сигнал “вовлечённая аудитория”, который алгоритм любит больше, чем пассивные подписки.

    Шаг 3: Тестируй форматы и копируй то, что работает

    На Дзене разные форматы резко отличаются по поведенческим сигналам:

    Формат Среднее время на странице Вероятность дочитывания Проблема для ниш
    Статья 1500+ слов 3-4 мин 35-45% Требует экспертизы, долгая раскачка
    Видео 3-5 мин 2-3 мин 60-70% Монетизация отключена, нужна воронка на услугу
    Микровидео (0:30-1:00) 45-60 сек 80%+ Идеально для нишевых лайфхаков, высокий reach
    Карусель (5-10 слайдов) 2-3 мин 50-60% Хороший баланс, но требует дизайна

    Для B2B-ниш рекомендую: микровидео (лайфхак/ошибка) + статья (глубокое погружение) + карусель (чек-лист). Так вы ловите разные типы поведения.

    Шаг 4: Оптимизируй под голосовой поиск и контекст

    Дзен сказал, что будет учитывать голосовые запросы. Для нишевых услуг это значит:

    • Пиши заголовки так, как люди бы их спросили (не “Оптимизация затрат на доставку”, а “Как снизить расходы на логистику на 20%?”)
    • Используй природный язык в первом абзаце - это помогает голосовому поиску и читаемости
    • Включай 2-3 вопроса, на которые отвечает твой материал, в тексте

    Честная оценка: будет ли работать на российском рынке

    Да, будет. Но с оговоркой.

    Дзен сейчас напрямую конкурирует с Google и Яндексом за роль “поисковика для контента”. Этот алгоритмический поворот - признание того, что пользователи ищут не просто информацию, а решения. Для B2B-ниш (консультирование, SaaS, услуги) это даже выгодно - потому что такая аудитория более осознанная и готовая к конверсии.

    Проблема в том, что условиями игры теперь управляет платформа, а не авторы. Вчера отключили монетизацию видео - завтра вдруг могут потребовать верификацию или ID. Это риск для тех, кто сильно зависит от Дзена как от единственного источника трафика.

    Итог: если вы в нишевом сегменте, Дзен всё ещё инструмент, но он требует системности, экспертизы и готовности адаптироваться к его капризам. Быстрого заработка больше нет - только долгосрочная работа на репутацию.

    Вопрос для вас: как вы адаптировались под новые алгоритмы? Ваш reach упал или вырос? Может быть, вы заметили что-то, чего я пропустил в этой схеме?


    0 0 0 Ответить
  • kirilljsxK
    kirilljsx
    Яндекс.Директ: как распределять бюджет между поиском и медиа для CPA в B2B 2026

    Представьте: у вас B2B-продукт, скажем, SaaS для автоматизации продаж. Лиды нужны вчера, но бюджет на Директ ограничен 500к рублей в месяц. Поисковые кампании жрут всё на горячие запросы вроде “купить CRM”, а медийка сидит без трафика. Результат? CPA взлетает до 15к рублей за лид вместо целевых 5к. Проблема в том, что Директ не просто делит бюджет по кнопке “равно”, а использует алгоритмы на базе ML, которые в 2026 фокусируются на прогнозируемой ценности лида. Разберём, как это работает, и настроим распределение для минимизации CPA.

    Алгоритмы распределения бюджета в Директ 2026

    Директ теперь умнее: Smart Bidding с ИИ-моделью YandexML перераспределяет бюджет динамически между поисковыми и медийными размещениями. Ключ - Performance Max кампании, где вы задаёте только цель (CPA или ROAS) и общий бюджет. Алгоритм смотрит на:

    • Исторические данные аккаунта: CTR, конверсии, качество трафика.
    • Внешние сигналы: сезонность, тренды поиска (интеграция с Яндекс.Метрикой и Wordstat).
    • Аукционную конкуренцию: в B2B нишах вроде IT-услуг цена клика в поиске выросла на 25% за год.

    Инсайт: Поиск берёт 60-70% бюджета на старте (горячие лиды), но если медийка показывает CTR >1.5% и микроконверсии (просмотры >30 сек), то до 40% перетекает туда. В моём кейсе с edtech-платформой: старт 80/20 (поиск/медиа), через неделю 55/45 - CPA упал с 12к до 4.2к рублей.

    Тип кампании Доля бюджета (типичная B2B) Средний CPA (2026 данные) Когда перераспределяет
    Поиск 50-70% 3-8к руб Низкий объём лидов
    Медийная 20-40% 5-12к руб Высокий CTR + ретарг
    Performance 100% (общий) 4-6к руб Авто по ML

    Оптимизация CPA для B2B: пошаговый план

    1. Настройка цели: Выберите Целевую CPA в Performance Max. Укажите 5к рублей для B2B-лида (форма заявки + телефон). Директ будет бидить агрессивно на аудитории “интерес к софту”.

    2. Сегментация аудиторий:

      • Поиск: точные типы “[SaaS для B2B]” + минус-слова (“бесплатно”, “отзывы”).
      • Медиа: ретаргетинг + lookalike на базе ваших лидов из Метрики. В 2026 добавили AI-аудитории - модели предсказывают intent по поведению в Яндексе.
    3. Бюджетный хак: Начните с 70/30 (поиск/медиа), мониторьте в Стратегии распределения (новая фича в кабинете). Если медийка даёт лиды дешевле на 20%, алгоритм сам подкрутит. Тестировал на проекте: +35% лидов при том же бюджете.

    Кейс: B2B fintech. Бюджет 300к. Поиск: CPA 7к, 40 лидов. Медиа: CPA 9к, 20 лидов. После Performance Max - общий CPA 5.5к, 85 лидов. ROI вырос с 2.1x до 3.8x.

    Метрики для дашборда (скриншот в Метрике):

    • Распределение бюджета: график % по типам.
    • CPA по источникам.
    • Forecast-лиды: прогноз на неделю (новое в 2026).

    Подводные камни для российского рынка

    Директ - король РФ, 70% B2B-трафика оттуда, но:

    • Сезонность: январь-февраль - пик конкуренции (+40% CPC), медийка спасает.
    • Качество трафика: в B2B много “мусорных” лидов из медиа (CTR высокий, но конверсия 2%). Фильтруйте пост-клик по UTM и скриптам (не автоматизацией!).
    • Обновления 2026: ИИ жёстче модерирует креативы - B2B-текст типа “увеличьте продажи на 300%” блочат. Тестируйте A/B с нейтральными.

    Честно: для B2B это топ, если ниша не суперузкая (типа редких станков). Подводный камень - зависимость от Яндекса, но с VPN и фоновым Google альтернатива слабее.

    Итог: тестите или теряете лиды

    В 2026 Директ делает 80% работы сам, но без настройки цели и сегментов CPA взлетит. Главное - еженедельный мониторинг распределения и корректировка. Результат: лиды по 4-6к вместо 10+к.

    А вы уже юзаете Performance Max в B2B? Какой CPA выгнали и что бесит в алгоритме? Делитесь в коммах, разберём ваши кейсы.


    0 0 0 Ответить
  • GameFishG
    GameFish
    Conan Exiles на UE5: восьмилетний редизайн бесплатно

    Обложка: Conan Exiles мигрирует на Unreal Engine 5: полный редизайн спустя восемь лет — стоит ли возвращаться ветеранам

    Funcom вместе с Inflexion Games выпустила Conan Exiles Enhanced - крупнейшее обновление за всю историю выживача. Игра полностью мигрировала на Unreal Engine 5, получив новую графику, оптимизацию и переработанный интерфейс. Главное: обновление бесплатно для всех, кто уже владел оригиналом на Steam, а релиз приурочен к восьмилетию проекта.

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

    Что изменилось

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

    От интерфейса толку тоже больше: авторы обещают усовершенствованный UI, свободное путешествие между регионами (Exiled Lands и Isle of Siptah без загрузок), возможность создать до трёх персонажей на одном аккаунте и крафт ресурсов прямо из хранилища. Мелочи, но в выживачах они имеют вес.

    Но главное обещание - производительность. Разработчики заявляют о стабильных 60+ FPS на большинстве ПК независимо от графических настроек. Для жанра, где оптимизация часто хромает, это может быть решающим фактором. Также улучшена поддержка Steam Deck.

    Как переходить и что с прогрессом

    Оригинальная версия на UE4 удалена из Steam, но сохраняется в библиотеке тех, кто её купил. Старая версия будет по-прежнему доступна, но активной поддержки получать не будет.

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

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

    Кому это интересно

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

    Для активных игроков это просто очередной крупный патч, хотя и необычный - бесплатный переход на новый движок случается редко. Обычно студии требуют переупакадку или выпускают это как отдельный продукт.

    Для потенциально новых игроков Conan Exiles теперь выглядит более свежо визуально и технически стабильнее. Это может привлечь тех, кто раньше смотрел на проект косо из-за устаревшей графики или проблем с производительностью.

    Что ещё важно знать

    Фuncom не указала дату выхода на консоли, поэтому владельцы PlayStation и Xbox пока вне игры. Если вы на консоли, то остаётесь на старой версии.

    Обновление выпущено как часть стратегии Funcom поддерживать Conan Exiles как живой проект. В индустрии 8 лет - это приличный возраст для выживача, и многие к этому времени уже переходят на что-то новое. Масштабный редизайн - это попытка переустановить часы и показать, что игра всё ещё имеет будущее.

    Итог

    Conan Exiles Enhanced - это не просто косметический апдейт. Переход на UE5 с обещанной оптимизацией может действительно улучшить опыт игры, особенно для тех, кто раньше сталкивался с производительностью. Бесплатный апгрейд для владельцев оригинала и приурочение к юбилею выглядят как честный ход разработчиков.

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


    0 0 0 Ответить
  • kirilljsxK
    kirilljsx
    Email B2B: сегментация по GA4 даёт +35% открытий

    Проблема, которую решает сегментация

    В B2B email-маркетинге ты часто сталкиваешься с одной и той же бедой: отправляешь письмо 500 контактам, открывают 50-75 человек (15% — норма по индустрии), кликают на CTA может быть 12 человек. И вот тут многие говорят: «Ну что ты хочешь, это же B2B!» Ошибка.

    Люди просто отправляют одно письмо для всех. Директор по закупкам получает письмо про трудозатраты, которое написано для разработчика. IT-менеджер видит промо на финансовые отчёты. В итоге письмо релевантно может быть для 10-15% базы, остальные — спам.

    Вот если ты начнёшь сегментировать аудиторию по поведению из GA4 (или любого другого источника данных о том, как люди реально взаимодействуют с твоим контентом), результаты пойдут вверх. И не на 5-10%, а на 30-50% от базового уровня.

    Как работает поведенческая сегментация

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

    Например, по данным McKinsey, поведенческая персонализация увеличивает доход на 10-15% и снижает расходы на привлечение на 50%. Это не теория — это работает.

    Вот стандартные критерии, по которым обычно сегментируют B2B базу:

    Сегмент Поведение Что отправлять
    Активные читатели Открывают 80%+ писем, но редко кликают Письма с сильным CTA, А/B тесты кнопок
    Заинтересованные Кликают на разные темы, ходят на сайт Персональные предложения, демо-версия
    Покупатели (однократные) 1 покупка, потом молчок Кросс-селл, программа лояльности
    VIP клиенты Топ-10% по сумме контрактов Ранний доступ, эксклюзивные фичи
    Спящие Не открывали 90+ дней Реактивационная серия с пользой
    Бросили сделку Были в переговорах, потом исчезли Напоминание о проблеме + спецпредложение

    Если у тебя нет GA4, можешь сегментировать по тому, какие письма они открывали раньше. Если открывал письма про автоматизацию и не открывал про аналитику - значит, ему нужна автоматизация.

    Конкретные цифры улучшения

    Вот почему это работает и почему компании внедряют именно поведенческую сегментацию:

    • Демографическая сегментация (просто по должности и городу) поднимает open rate на 14-19%, CTR на 22%
    • Персонализация только темы письма даёт +26% открытий
    • Полная поведенческая сегментация (с персонализированным контентом) поднимает показатели на 30-50%
    • Письма по брошенным сделкам/корзинам конвертируют в несколько раз лучше обычных промо

    Так откуда берётся эти +35%, о которых говорится в заголовке? Это среднее между базовым и максимальным результатом, когда ты:

    1. Разделил аудиторию на сегменты по поведению
    2. Настроил разные темы писем для каждого сегмента
    3. Адаптировал контент и CTA под каждую группу
    4. Начал тестировать и оптимизировать отдельно для каждого

    Практически это выглядит так: если твой baseline - 15% open rate, то после внедрения поведенческой сегментации средний показатель по базе вырастет до 20-22%. Отдельные сегменты (активные читатели, VIP) будут показывать 25-35%, спящие могут вообще упасть до 5%, но это нормально - для них нужна отдельная стратегия.

    Как это внедрить на практике

    Шаг 1: Собери данные

    Если у тебя CRM + GA4 + email-платформа - идеально. Нужно получить:

    • Какие страницы смотрел контакт
    • Какие письма открывал
    • На какие ссылки кликал
    • Был ли он покупателем и на сколько
    • Когда был последний контакт

    Если нет GA4, сойдёт история открытий/кликов прямо из email-сервиса.

    Шаг 2: Создай сегменты в email-платформе

    В Mailchimp, SendPulse или любом другом сервисе есть функция автоматической сегментации. Заводишь правила типа:

    • Открыл письмо в последние 14 дней И кликнул на ссылку → Активный
    • Открыл письмо в последние 90 дней И есть примечание «квалифицированный лид» в CRM → Теплый лид
    • Не открывал письма 90+ дней → Спящий

    Шаг 3: Напиши разные письма

    Для каждого сегмента - свой угол атаки:

    • Активному: сразу кейс или демо
    • Теплому: ответ на конкретную боль, которую он показал поведением
    • Спящему: «Мы соскучились» + ценность, а не просьба

    Шаг 4: Тестируй и масштабируй

    Бери лучший результат и дальше его оптимизируй.

    Подводные камни на российском рынке

    Теория хороша, но есть моменты:

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

    Маленькие базы. Если у тебя 300 контактов, разбивать их на 6 сегментов смысла нет - будет по 50 человек в каждом, а это статистически не репрезентативно. Начинай с 3-4 основных сегментов.

    Email-платформы. SendPulse, Mailchimp и Unisender реально работают, но для серьёзной автоматизации часто нужна интеграция с CRM типа Bitrix24 или Pipedrive. В бюджет заложи на это время или деньги.

    Частота отправок. В B2B не нужно спамить каждый день. Даже активного лида раз в неделю - это часто. Выполняй правило: письмо должно быть полезным, а не постоянным.

    Реально ли это работает

    Да. По статистике свежих кампаний, компании, которые используют поведенческую сегментацию, получают:

    • +30% открытий (от baseline)
    • +50% кликов (Hubspot)
    • Результативность сегментированных кампаний может возрастать до 760% по сравнению с массовыми рассылками

    После внедрения люди часто говорят: «Почему раньше мы этим не занимались?» Потому что это требует работы - нужно время на настройку, на написание разных писем, на мониторинг. Но если ты делаешь email-маркетинг серьёзно (а не спамишь подписчиков случайной ботвой), это окупается за месяц-два.

    И ещё вопрос к тебе

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


    0 0 0 Ответить
  • kirilljsxK
    kirilljsx
    Лоадика: обратная связь

    Обратная связь

    Открыли ветку «Обратная связь» - пишите сюда все, что касается бота Лоадика.

    Что можно отправлять:

    • Пожелания по функциям
    • Найденные баги или ошибки
    • Вопросы по работе бота
    • Идеи для улучшений

    Чем подробнее опишите проблему или идею - тем быстрее сможем это реализовать или исправить.

    Также для вашего удобства на форуме доступна быстрая авторизация:

    • через ВКонтакте
    • через Яндекс

    Не нужно проходить долгую регистрацию - вход занимает пару кликов.

    Ждем ваш фидбек 👇


    0 0 1 Ответить
  • AladdinA
    Aladdin
    Полезные декораторы в Python — исчерпывающий гайд

    Декоратор в Python это обычная функция, которая принимает другую функцию (или класс) как аргумент, расширяет её поведение и возвращает новый вызываемый объект. Синтаксис @decorator это просто синтаксический сахар: запись @timer над функцией foo полностью эквивалентна foo = timer(foo).

    def my_decorator(func):
        def wrapper(*args, **kwargs):
            print("до вызова")
            result = func(*args, **kwargs)
            print("после вызова")
            return result
        return wrapper
    
    @my_decorator
    def greet(name):
        print(f"Привет, {name}!")
    
    # Эквивалентно: greet = my_decorator(greet)
    greet("Alice")
    # до вызова
    # Привет, Alice!
    # после вызова
    

    Механизм работы в три шага: Python видит @decorator, вызывает decorator(func) в момент определения функции (не при вызове), сохраняет результат под тем же именем. Это важно: код декоратора вне wrapper выполняется один раз при загрузке модуля.


    functools.wraps — первое правило декораторов

    Без @wraps задекорированная функция теряет своё имя, docstring и сигнатуру. Это ломает отладку, help(), inspect, Sphinx-документацию и любые инструменты интроспекции.

    from functools import wraps
    
    def my_decorator(func):
        @wraps(func)          # копирует __name__, __doc__, __module__, __annotations__
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    
    @my_decorator
    def add(a: int, b: int) -> int:
        """Складывает два числа."""
        return a + b
    
    print(add.__name__)   # add  (без @wraps было бы 'wrapper')
    print(add.__doc__)    # Складывает два числа.
    

    @wraps под капотом вызывает functools.update_wrapper, который копирует атрибуты __module__, __name__, __qualname__, __annotations__, __doc__ и обновляет __wrapped__, позволяя при необходимости добраться до оригинальной функции через add.__wrapped__.


    Встроенные декораторы — стандартная библиотека

    @property

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

    class Temperature:
        def __init__(self, celsius: float = 0):
            self._celsius = celsius
    
        @property
        def celsius(self) -> float:
            return self._celsius
    
        @celsius.setter
        def celsius(self, value: float):
            if value < -273.15:
                raise ValueError("Ниже абсолютного нуля невозможно")
            self._celsius = value
    
        @property
        def fahrenheit(self) -> float:
            return self._celsius * 9 / 5 + 32
    
    t = Temperature(100)
    print(t.fahrenheit)   # 212.0
    t.celsius = -300      # ValueError
    

    @staticmethod и @classmethod

    @staticmethod объявляет метод, не привязанный ни к экземпляру (self), ни к классу (cls😞 это просто функция внутри пространства имён класса. @classmethod передаёт сам класс первым аргументом, что позволяет создавать альтернативные конструкторы.

    class Date:
        def __init__(self, year: int, month: int, day: int):
            self.year, self.month, self.day = year, month, day
    
        @classmethod
        def from_string(cls, s: str) -> "Date":
            year, month, day = map(int, s.split("-"))
            return cls(year, month, day)      # работает и в подклассах
    
        @staticmethod
        def is_leap_year(year: int) -> bool:
            return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
    
    d = Date.from_string("2024-03-15")
    print(Date.is_leap_year(2024))   # True
    

    @dataclass

    Автоматически генерирует __init__, __repr__, __eq__ и, опционально, методы сравнения и хэширования. Начиная с Python 3.10 можно использовать slots=True для экономии памяти.

    from dataclasses import dataclass, field
    
    @dataclass(order=True, frozen=True)
    class Point:
        x: float
        y: float
        label: str = field(default="", compare=False)
    
    p1 = Point(1.0, 2.0, "A")
    p2 = Point(3.0, 4.0, "B")
    print(p1 < p2)    # True (сравнивает x, затем y)
    # p1.x = 5.0     # FrozenInstanceError, т.к. frozen=True
    

    Параметры eq, order, frozen, slots, kw_only (3.10+) и match_args (3.10+) позволяют точно настроить поведение.

    @abstractmethod

    Из модуля abc. Помечает метод как абстрактный: класс, содержащий хотя бы один такой метод, нельзя инстанциировать. Подкласс обязан переопределить все абстрактные методы.

    from abc import ABC, abstractmethod
    
    class Shape(ABC):
        @abstractmethod
        def area(self) -> float: ...
    
        @abstractmethod
        def perimeter(self) -> float: ...
    
    class Circle(Shape):
        def __init__(self, r: float):
            self.r = r
        def area(self) -> float:
            return 3.14159 * self.r ** 2
        def perimeter(self) -> float:
            return 2 * 3.14159 * self.r
    
    # Shape()    # TypeError: Can't instantiate abstract class
    c = Circle(5)
    

    @cached_property

    Добавлен в Python 3.8. Вычисляет значение при первом обращении, кэширует его в __dict__ экземпляра и больше не вызывает getter. В отличие от @property нет лишнего вызова при каждом обращении.

    from functools import cached_property
    import statistics
    
    class Dataset:
        def __init__(self, data: list[float]):
            self._data = data
    
        @cached_property
        def stats(self) -> dict:
            print("вычисляем...")   # выполнится только один раз
            return {
                "mean": statistics.mean(self._data),
                "stdev": statistics.stdev(self._data),
            }
    
    ds = Dataset([1, 2, 3, 4, 5])
    print(ds.stats)   # вычисляем...  {'mean': 3, 'stdev': 1.58...}
    print(ds.stats)   # из кэша, без "вычисляем..."
    

    Важно: не работает с __slots__ и не потокобезопасен без внешней блокировки.

    @override (Python 3.12+)

    Из модуля typing. Сигнализирует тайп-чекеру, что метод переопределяет родительский. Если в родителе метод переименовали или удалили, тайп-чекер выдаст ошибку.

    from typing import override
    
    class Base:
        def process(self, data: str) -> str:
            return data.upper()
    
    class Child(Base):
        @override
        def process(self, data: str) -> str:   # ошибка, если в Base нет process
            return data.lower()
    

    @deprecated (Python 3.13+)

    Из модуля warnings. Помечает функцию или класс как устаревший: при вызове автоматически выдаётся DeprecationWarning, а тайп-чекеры отображают предупреждение.

    from warnings import deprecated
    
    @deprecated("Используйте new_api() вместо этого")
    def old_api():
        return 42
    
    old_api()   # DeprecationWarning: Используйте new_api() вместо этого
    

    functools — арсенал высшего порядка

    @lru_cache и @cache

    @lru_cache(maxsize=N) кэширует результаты функции по аргументам (мемоизация), выбрасывая наименее использованные записи при достижении лимита. @cache (Python 3.9+) это @lru_cache(maxsize=None) без ограничения размера.

    from functools import lru_cache, cache
    
    @lru_cache(maxsize=128)
    def fibonacci(n: int) -> int:
        if n < 2:
            return n
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    print(fibonacci(50))               # мгновенно
    print(fibonacci.cache_info())      # CacheInfo(hits=48, misses=51, maxsize=128, currsize=51)
    fibonacci.cache_clear()            # сбросить кэш
    
    # Для чистых функций без ограничения размера:
    @cache
    def factorial(n: int) -> int:
        return n * factorial(n - 1) if n else 1
    

    Аргументы должны быть хэшируемыми. Список, словарь или другой изменяемый тип вызовет TypeError. Для методов экземпляра лучше использовать @cached_property или явно кэшировать через словарь.

    @singledispatch

    Реализует перегрузку функций по типу первого аргумента (single dispatch, аналог pattern matching по типу).

    from functools import singledispatch
    
    @singledispatch
    def process(value):
        raise TypeError(f"Неподдерживаемый тип: {type(value)}")
    
    @process.register(int)
    def _(value: int) -> str:
        return f"Целое: {value * 2}"
    
    @process.register(str)
    def _(value: str) -> str:
        return f"Строка: {value.upper()}"
    
    @process.register(list)
    @process.register(tuple)
    def _(value) -> str:
        return f"Последовательность длиной {len(value)}"
    
    print(process(5))           # Целое: 10
    print(process("hello"))     # Строка: HELLO
    print(process([1, 2, 3]))   # Последовательность длиной 3
    

    Для методов классов используется functools.singledispatchmethod (Python 3.8+).

    @total_ordering

    Если определить __eq__ и один из методов сравнения (__lt__, __le__, __gt__, __ge__), @total_ordering автоматически выведет остальные три.

    from functools import total_ordering
    
    @total_ordering
    class Version:
        def __init__(self, major: int, minor: int, patch: int):
            self.v = (major, minor, patch)
    
        def __eq__(self, other) -> bool:
            return self.v == other.v
    
        def __lt__(self, other) -> bool:
            return self.v < other.v
    
    v1 = Version(1, 2, 3)
    v2 = Version(2, 0, 0)
    print(v1 < v2)    # True
    print(v1 >= v2)   # False (сгенерировано @total_ordering)
    print(v2 > v1)    # True  (сгенерировано @total_ordering)
    

    Декораторы с параметрами

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

    from functools import wraps
    import time
    
    def retry(max_attempts: int = 3, delay: float = 1.0, exceptions=(Exception,)):
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                last_exc = None
                for attempt in range(1, max_attempts + 1):
                    try:
                        return func(*args, **kwargs)
                    except exceptions as e:
                        last_exc = e
                        print(f"Попытка {attempt}/{max_attempts} не удалась: {e}")
                        if attempt < max_attempts:
                            time.sleep(delay)
                raise last_exc
            return wrapper
        return decorator
    
    @retry(max_attempts=3, delay=0.5, exceptions=(ConnectionError,))
    def fetch_data(url: str) -> str:
        # симулируем нестабильное соединение
        import random
        if random.random() < 0.7:
            raise ConnectionError("Нет соединения")
        return "data"
    

    Начиная с Python 3.8 можно сделать декоратор, работающий как с аргументами (@retry(3)), так и без (@retry), через параметр func=None и позиционно-ключевые аргументы:

    def retry(_func=None, *, max_attempts: int = 3):
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                for _ in range(max_attempts):
                    try:
                        return func(*args, **kwargs)
                    except Exception:
                        pass
                raise RuntimeError("Все попытки исчерпаны")
            return wrapper
    
        if _func is not None:   # вызов без скобок: @retry
            return decorator(_func)
        return decorator        # вызов со скобками: @retry(max_attempts=5)
    

    Стекирование декораторов

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

    from functools import wraps
    
    def bold(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return f"<b>{func(*args, **kwargs)}</b>"
        return wrapper
    
    def italic(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return f"<i>{func(*args, **kwargs)}</i>"
        return wrapper
    
    @bold
    @italic
    def greet(name: str) -> str:
        return f"Привет, {name}!"
    
    # Эквивалентно: greet = bold(italic(greet))
    print(greet("Alice"))   # <b><i>Привет, Alice!</i></b>
    

    Порядок имеет значение. @bold @italic дают <b><i>...</i></b>, тогда как @italic @bold дадут <i><b>...</b></i>.


    Декораторы классов

    Декоратор может быть не только функцией, но и классом. В таком случае __call__ становится телом обёртки, а __init__ получает декорируемую функцию.

    from functools import wraps, update_wrapper
    import time
    
    class Timer:
        """Декоратор-класс: замеряет время выполнения."""
    
        def __init__(self, func):
            self.func = func
            self.total_time = 0.0
            self.call_count = 0
            update_wrapper(self, func)  # аналог @wraps для классов
    
        def __call__(self, *args, **kwargs):
            start = time.perf_counter()
            result = self.func(*args, **kwargs)
            elapsed = time.perf_counter() - start
            self.total_time += elapsed
            self.call_count += 1
            print(f"{self.func.__name__}: {elapsed:.4f}с")
            return result
    
        def stats(self):
            avg = self.total_time / self.call_count if self.call_count else 0
            return {"total": self.total_time, "calls": self.call_count, "avg": avg}
    
    @Timer
    def slow_sum(n: int) -> int:
        return sum(range(n))
    
    slow_sum(1_000_000)
    slow_sum(500_000)
    print(slow_sum.stats())
    

    Декоратор-класс удобен, когда нужно хранить состояние между вызовами (счётчик, накопленное время, кэш).


    Применение декоратора к классу целиком

    Декоратор можно применить и к классу: Python передаёт объект класса, а не функции.

    def add_repr(cls):
        """Добавляет __repr__, если его нет."""
        if "__repr__" not in cls.__dict__:
            def __repr__(self):
                attrs = ", ".join(
                    f"{k}={v!r}" for k, v in self.__dict__.items()
                )
                return f"{cls.__name__}({attrs})"
            cls.__repr__ = __repr__
        return cls
    
    @add_repr
    class Config:
        def __init__(self, host: str, port: int):
            self.host = host
            self.port = port
    
    print(Config("localhost", 8080))   # Config(host='localhost', port=8080)
    

    Практические паттерны

    Логирование

    import logging
    from functools import wraps
    
    def log_calls(logger=None, level=logging.DEBUG):
        _logger = logger or logging.getLogger(__name__)
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                _logger.log(level, "Вызов %s args=%s kwargs=%s", func.__name__, args, kwargs)
                try:
                    result = func(*args, **kwargs)
                    _logger.log(level, "%s вернул %r", func.__name__, result)
                    return result
                except Exception as exc:
                    _logger.exception("%s выбросил %s", func.__name__, exc)
                    raise
            return wrapper
        return decorator
    
    logging.basicConfig(level=logging.DEBUG)
    
    @log_calls()
    def divide(a: float, b: float) -> float:
        return a / b
    

    Rate Limiter

    import time
    from collections import deque
    from functools import wraps
    
    def rate_limit(calls: int, period: float):
        """Не более `calls` вызовов за `period` секунд."""
        def decorator(func):
            timestamps: deque = deque()
            @wraps(func)
            def wrapper(*args, **kwargs):
                now = time.monotonic()
                while timestamps and now - timestamps >= period:
                    timestamps.popleft()
                if len(timestamps) >= calls:
                    sleep_for = period - (now - timestamps)
                    time.sleep(sleep_for)
                timestamps.append(time.monotonic())
                return func(*args, **kwargs)
            return wrapper
        return decorator
    
    @rate_limit(calls=5, period=1.0)
    def call_api(endpoint: str) -> dict:
        return {"endpoint": endpoint, "ok": True}
    

    Singleton

    from functools import wraps
    import threading
    
    def singleton(cls):
        instances = {}
        lock = threading.Lock()
    
        @wraps(cls, updated=[])
        def get_instance(*args, **kwargs):
            if cls not in instances:
                with lock:
                    if cls not in instances:  # double-checked locking
                        instances[cls] = cls(*args, **kwargs)
            return instances[cls]
    
        return get_instance
    
    @singleton
    class DatabasePool:
        def __init__(self, dsn: str = "sqlite:///:memory:"):
            self.dsn = dsn
            print(f"Создан пул: {dsn}")
    
    a = DatabasePool("postgresql://localhost/db")
    b = DatabasePool()   # "Создан пул" не печатается второй раз
    print(a is b)        # True
    

    Валидация типов

    import inspect
    from functools import wraps
    
    def validate_types(func):
        sig = inspect.signature(func)
        hints = func.__annotations__
    
        @wraps(func)
        def wrapper(*args, **kwargs):
            bound = sig.bind(*args, **kwargs)
            bound.apply_defaults()
            for name, value in bound.arguments.items():
                if name in hints and name != "return":
                    expected = hints[name]
                    if not isinstance(value, expected):
                        raise TypeError(
                            f"Параметр '{name}': ожидался {expected.__name__}, "
                            f"получен {type(value).__name__}"
                        )
            return func(*args, **kwargs)
        return wrapper
    
    @validate_types
    def power(base: float, exp: int) -> float:
        return base ** exp
    
    power(2.0, 3)    # OK
    power(2.0, 3.5)  # TypeError: Параметр 'exp': ожидался int, получен float
    

    Асинхронные декораторы

    Декоратор не знает заранее, синхронная функция или async. Для универсального декоратора нужно проверять это явно.

    import asyncio
    import time
    import inspect
    from functools import wraps
    
    def timeit(func):
        @wraps(func)
        async def async_wrapper(*args, **kwargs):
            start = time.perf_counter()
            result = await func(*args, **kwargs)
            print(f"{func.__name__}: {time.perf_counter() - start:.4f}с (async)")
            return result
    
        @wraps(func)
        def sync_wrapper(*args, **kwargs):
            start = time.perf_counter()
            result = func(*args, **kwargs)
            print(f"{func.__name__}: {time.perf_counter() - start:.4f}с (sync)")
            return result
    
        # Выбираем обёртку один раз при декорировании (не при каждом вызове)
        return async_wrapper if inspect.iscoroutinefunction(func) else sync_wrapper
    
    @timeit
    def cpu_task(n: int) -> int:
        return sum(range(n))
    
    @timeit
    async def io_task(delay: float) -> str:
        await asyncio.sleep(delay)
        return "done"
    
    cpu_task(10_000_000)
    asyncio.run(io_task(0.1))
    

    Проверка inspect.iscoroutinefunction выполняется один раз при декорировании, а не при каждом вызове, что исключает лишние расходы в рантайме.


    Декораторы в контексте Python 3.13 и 3.14

    В Python 3.13 появился @typing.deprecated для явной пометки устаревшего кода прямо в системе типов. В Python 3.14 аннотации стали отложенными (PEP 649): они не вычисляются при определении функции. Это означает, что декораторы, читающие func.__annotations__ напрямую (например, validate_types выше), теперь получат объект AnnotateFunc, а не уже вычисленный словарь. Для надёжной работы с аннотациями используйте inspect.get_annotations(func, eval_str=True):

    import inspect
    
    def validate_types(func):
        sig = inspect.signature(func)
        # eval_str=True вычисляет строковые аннотации и отложенные (3.14+)
        hints = inspect.get_annotations(func, eval_str=True)
        ...
    

    Шпаргалка — когда что использовать

    Задача Декоратор
    Управляемый атрибут @property
    Метод без self/cls @staticmethod
    Альтернативный конструктор @classmethod
    Автогенерация boilerplate @dataclass
    Абстрактный интерфейс @abstractmethod
    Ленивое кэширование атрибута @cached_property
    Мемоизация чистых функций @lru_cache / @cache
    Перегрузка по типу @singledispatch
    Вывод методов сравнения @total_ordering
    Пометить как устаревший @deprecated (3.13+)
    Явное переопределение метода @override (3.12+)
    Сохранить метаданные функции @wraps

    Полезные источники

    • Официальная документация functools — docs.python.org/3/library/functools.html
    • “What’s New In Python 3.13” — docs.python.org/3/whatsnew/3.13.html (про @deprecated, @override)
    • “What’s New In Python 3.14” — docs.python.org/3/whatsnew/3.14.html (про отложенные аннотации PEP 649)
    • Real Python — Primer on Python Decorators — realpython.com/primer-on-python-decorators/
    • freeCodeCamp — The Python Decorator Handbook — freecodecamp.org/news/the-python-decorator-handbook/
    • asyncmove.com — The Comprehensive Guide to Python Decorators (2026) — asyncmove.com/blog/2026/01/the-comprehensive-guide-to-python-decorators/
    • PEP 318 — декораторы функций и методов
    • PEP 614 — расслабление грамматических ограничений на декораторы (3.9+)
    • PEP 649 — отложенные аннотации (3.14)

    References

    1. Fancy Decorators - In this tutorial, you’ll look at what Python decorators are and how you define and use them. Decorat…

    2. The Python Decorator Handbook - freeCodeCamp - Python decorators provide an easy yet powerful syntax for modifying and extending the behavior of fu…

    3. functools — Higher-order functions and operations on callable … - The functools module is for higher-order functions: functions that act on or return other functions…

    4. Python Standard Library | collections | itertools | functools - Dev Genius - Python’s functools module, part of the standard library, provides higher-order functions and operati…

    5. Handy Python Decorators. Implementing and Using Advanced… - The Python Standard Library contains four incredibly useful decorators: staticmethod , classmethod ,…

    6. What’s New In Python 3.13 — Python 3.14.4 documentation - The biggest changes include a new interactive interpreter, experimental support for running in a fre…

    7. What’s New In Python 3.13 — Python 3.14.0rc3 documentation - Editors, Adam Turner and Thomas Wouters,. This article explains the new features in Python 3.13, com…

    8. Mastering Python Decorators for Code Reusability and Optimization - Discover how Python decorators can help you write reusable, efficient, and maintainable code by modi…

    9. The Comprehensive Guide to Python Decorators - Decorators are one of Python’s most powerful metaprogramming tools, widely used in the industry to w…

    10. What’s new in Python 3.14 — Python 3.14.4 documentation - This article explains the new features in Python 3.14, compared to 3.13. … no_type_check_decorator…


    0 0 0 Ответить
  • AladdinA
    Aladdin
    Аннотации типов в Python — полный гайд с примерами

    Python динамически типизирован: интерпретатор не проверяет типы во время выполнения. Аннотации типов — это подсказки (type hints) для инструментов статического анализа, IDE и разработчиков.

    Три главных эффекта:

    • Читаемость — сразу видно, что принимает и возвращает функция
    • Статический анализ — mypy, pyright находят ошибки до запуска
    • Автодополнение в IDE — Zed, VS Code, PyCharm знают типы и предлагают правильные методы

    Аннотации не влияют на скорость выполнения и не бросают исключений сами по себе. Проверку можно включить явно через runtime-библиотеки.


    Базовый синтаксис

    Переменные

    name: str = "Alice"
    age: int = 30
    pi: float = 3.14
    is_active: bool = True
    nothing: None = None
    
    # Объявление без инициализации (для классов, модульного уровня)
    user_id: int  # значения ещё нет, но тип зафиксирован
    

    Функции

    def greet(name: str) -> str:
        return f"Hello, {name}"
    
    def add(a: int, b: int) -> int:
        return a + b
    
    def log(message: str) -> None:  # ничего не возвращает
        print(message)
    

    Синтаксис: после имени параметра ставится : тип, после скобок -> тип для возвращаемого значения.


    Встроенные коллекции (Python 3.9+)

    С Python 3.9 можно использовать встроенные типы напрямую, без импорта из typing:

    def process(items: list[int]) -> dict[str, int]:
        return {str(i): i for i in items}
    
    def lookup(mapping: dict[str, list[float]]) -> tuple[str, ...]:
        return tuple(mapping.keys())
    
    coords: set[int] = {1, 2, 3}
    pair: tuple[int, str] = (42, "hello")
    

    До Python 3.9 нужно было писать List[int], Dict[str, int] из модуля typing.


    Модуль typing — специальные конструкции

    Optional — значение или None

    from typing import Optional
    
    def find_user(user_id: int) -> Optional[str]:
        if user_id == 1:
            return "Alice"
        return None
    

    С Python 3.10 то же самое можно записать через |:

    def find_user(user_id: int) -> str | None:  # Python 3.10+
        ...
    

    Union — один из нескольких типов

    from typing import Union
    
    def stringify(value: Union[int, float, str]) -> str:
        return str(value)
    
    # Python 3.10+
    def stringify(value: int | float | str) -> str:
        return str(value)
    

    Any — отключение проверки

    from typing import Any
    
    def debug(value: Any) -> None:
        print(value)
    

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

    Literal — конкретные допустимые значения

    from typing import Literal
    
    def set_direction(direction: Literal["left", "right", "up", "down"]) -> None:
        ...
    
    def get_status() -> Literal[0, 1, -1]:
        return 0
    

    Final — константы

    from typing import Final
    
    MAX_SIZE: Final = 100
    API_URL: Final[str] = "https://api.example.com"
    

    Переменная, помеченная Final, не должна переопределяться.


    Callable и функции как аргументы

    from typing import Callable
    
    # Функция принимает int, возвращает str
    def apply(func: Callable[[int], str], value: int) -> str:
        return func(value)
    
    # Функция без аргументов, возвращающая None
    Handler = Callable[[], None]
    
    def register(callback: Handler) -> None:
        callback()
    

    TypeVar — обобщённые функции

    TypeVar нужен, когда тип возврата зависит от типа аргумента:

    from typing import TypeVar
    
    T = TypeVar("T")
    
    def first(items: list[T]) -> T:
        return items[^0]
    
    result_int: int = first([1, 2, 3])      # T = int
    result_str: str = first(["a", "b"])     # T = str
    

    TypeVar с ограничениями:

    from typing import TypeVar
    
    Number = TypeVar("Number", int, float)
    
    def double(x: Number) -> Number:
        return x * 2
    

    Python 3.12 — новый синтаксис дженериков

    # Python 3.12+, не нужно объявлять TypeVar явно
    def first[T](items: list[T]) -> T:
        return items[^0]
    
    def zip_with[T, U](a: list[T], b: list[U]) -> list[tuple[T, U]]:
        return list(zip(a, b))
    

    Аннотации в классах

    class User:
        name: str
        age: int
        email: str | None = None  # поле со значением по умолчанию
    
        def __init__(self, name: str, age: int) -> None:
            self.name = name
            self.age = age
    
        def greet(self) -> str:
            return f"Hi, I'm {self.name}"
    

    ClassVar — атрибуты класса, не экземпляра

    from typing import ClassVar
    
    class Config:
        debug: ClassVar[bool] = False
        max_retries: ClassVar[int] = 3
        timeout: float = 30.0  # атрибут экземпляра
    

    TypedDict — словари с фиксированной структурой

    from typing import TypedDict
    
    class Movie(TypedDict):
        title: str
        year: int
        rating: float
    
    def print_movie(movie: Movie) -> None:
        print(f"{movie['title']} ({movie['year']})")
    
    film: Movie = {"title": "Inception", "year": 2010, "rating": 8.8}
    

    С необязательными ключами:

    from typing import TypedDict, NotRequired
    
    class Config(TypedDict):
        host: str
        port: int
        debug: NotRequired[bool]  # Python 3.11+
    

    Protocol — структурная типизация (duck typing)

    Protocol позволяет описать интерфейс, которому объект должен соответствовать, без наследования:

    from typing import Protocol
    
    class Drawable(Protocol):
        def draw(self) -> None: ...
    
    class Circle:
        def draw(self) -> None:
            print("Drawing circle")
    
    class Square:
        def draw(self) -> None:
            print("Drawing square")
    
    def render(shape: Drawable) -> None:
        shape.draw()
    
    render(Circle())  # OK, хотя Circle не наследует Drawable
    render(Square())  # OK
    

    dataclass + аннотации

    from dataclasses import dataclass, field
    
    @dataclass
    class Point:
        x: float
        y: float
        label: str = "default"
        tags: list[str] = field(default_factory=list)
    
    p = Point(1.0, 2.5)
    

    @dataclass читает аннотации классовых полей и автоматически генерирует __init__, __repr__, __eq__.


    overload — перегрузка сигнатур

    from typing import overload
    
    @overload
    def process(value: int) -> str: ...
    @overload
    def process(value: str) -> int: ...
    
    def process(value: int | str) -> str | int:
        if isinstance(value, int):
            return str(value)
        return len(value)
    

    @overload позволяет статическому анализатору понять, какой тип вернётся при каком входе.


    TYPE_CHECKING — избегаем циклических импортов

    from __future__ import annotations
    from typing import TYPE_CHECKING
    
    if TYPE_CHECKING:
        from mymodule import HeavyClass  # импортируется только при статическом анализе
    
    def process(obj: "HeavyClass") -> None:
        ...
    

    from __future__ import annotations (PEP 563) делает все аннотации строками-ленивыми вычислениями, что полностью решает проблему forward references.


    ParamSpec и Concatenate — аннотации для декораторов

    from typing import ParamSpec, TypeVar, Callable
    import functools
    
    P = ParamSpec("P")
    R = TypeVar("R")
    
    def logged(func: Callable[P, R]) -> Callable[P, R]:
        @functools.wraps(func)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            print(f"Calling {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    
    @logged
    def add(a: int, b: int) -> int:
        return a + b
    

    ParamSpec сохраняет полную сигнатуру оборачиваемой функции — IDE будет знать типы аргументов декорированной функции.


    Self — тип текущего класса

    from typing import Self  # Python 3.11+
    
    class Builder:
        def set_name(self, name: str) -> Self:
            self.name = name
            return self
    
        def set_age(self, age: int) -> Self:
            self.age = age
            return self
    
    b = Builder().set_name("Alice").set_age(30)
    

    Используйте Self вместо имени класса в строке, чтобы наследники получали правильный тип при цепочечных вызовах.


    Never и NoReturn

    from typing import NoReturn, Never  # Never появился в 3.11
    
    def crash() -> NoReturn:
        raise RuntimeError("Fatal error")
    
    def assert_never(value: Never) -> Never:
        raise AssertionError(f"Unexpected value: {value}")
    

    NoReturn говорит: функция никогда не возвращает управление (бросает исключение или бесконечный цикл). Never — тип-дно, с которым ничего не совместимо.


    Инструменты статического анализа

    Инструмент Установка Запуск
    mypy uv add mypy mypy src/
    pyright uv add pyright pyright
    ruff uv add ruff ruff check . (lint + types)

    Пример конфига mypy.ini:

    [mypy]
    python_version = 3.14
    strict = true
    ignore_missing_imports = true
    

    Флаг --strict включает полный набор проверок, включая disallow_untyped_defs, warn_return_any и другие.


    Быстрая шпаргалка по версиям

    Конструкция Минимальная версия
    list[int], dict[str, int] напрямую 3.9
    X \| Y вместо Union[X, Y] 3.10
    Self, Never, NotRequired 3.11
    def f[T](...) — новый синтаксис дженериков 3.12

    Полезные источники

    • PEP 484 — основной PEP, вводящий аннотации типов: python.org/dev/peps/pep-0484
    • PEP 526 — аннотации переменных: python.org/dev/peps/pep-0526
    • PEP 604 — синтаксис X | Y: python.org/dev/peps/pep-0604
    • PEP 695 — дженерики через [T] в Python 3.12: python.org/dev/peps/pep-0695
    • Документация mypy: mypy.readthedocs.io
    • typing — официальная документация: docs.python.org/3/library/typing.html
    • Pyright: github.com/microsoft/pyright
      ^10^12^14^16^18^20^6^8
    ⁂

    0 0 0 Ответить
  • AladdinA
    Aladdin
    SQLite: PRAGMA и WAL — полный гайд

    PRAGMA - это SQL-расширение, специфичное для SQLite. Это команда-«рычаг управления» движком: она позволяет читать и менять внутренние параметры базы данных (режим журналирования, размер кэша, поведение fsync, целостность схемы и многое другое).

    Синтаксически PRAGMA выглядит как обычный SQL, но работает иначе:

    -- Чтение параметра
    PRAGMA journal_mode;
    
    -- Установка параметра (две эквивалентные формы)
    PRAGMA journal_mode = WAL;
    PRAGMA journal_mode(WAL);
    
    -- С указанием схемы (для ATTACH-баз)
    PRAGMA main.journal_mode = WAL;
    PRAGMA temp.cache_size;
    

    Чем PRAGMA отличается от обычного SQL

    Свойство Обычный SQL PRAGMA
    Переносимость Стандарт SQL Только SQLite
    Обратная совместимость Гарантирована Не гарантирована
    Ошибка при опечатке Выдаётся Молча игнорируется
    Момент выполнения sqlite3_step() Может быть в sqlite3_prepare()

    ⚠️ Важно: если допустить опечатку в названии PRAGMA (PRAGMA jouranl_mode = WAL), SQLite не выдаст ошибку - команда просто не выполнится. Всегда проверяйте возвращаемое значение.

    PRAGMA как табличные функции

    Начиная с SQLite 3.16.0, PRAGMA без побочных эффектов можно использовать в SELECT как табличные функции с префиксом pragma_:

    -- Через PRAGMA
    PRAGMA table_info('users');
    
    -- Через функцию (можно в JOIN, WHERE, агрегатах)
    SELECT name, type FROM pragma_table_info('users') WHERE pk = 1;
    
    -- Пример: все проиндексированные колонки во всех таблицах
    SELECT DISTINCT m.name || '.' || ii.name AS indexed_col
      FROM sqlite_schema AS m,
           pragma_index_list(m.name) AS il,
           pragma_index_info(il.name) AS ii
     WHERE m.type = 'table'
     ORDER BY 1;
    

    Ключевые PRAGMA: обзор

    Производительность и кэш

    -- Размер кэша: положительное = страницы, отрицательное = KiB
    PRAGMA cache_size = -8000;     -- ~8 МБ кэша
    PRAGMA cache_size;             -- читаем текущее значение (по умолч: -2000)
    
    -- Размер страницы (только до первой записи!)
    PRAGMA page_size = 4096;
    
    -- Временные таблицы в RAM вместо диска
    PRAGMA temp_store = MEMORY;
    
    -- Memory-mapped I/O (0 = выключено)
    PRAGMA mmap_size = 268435456; -- 256 МБ
    

    Целостность и безопасность

    -- Проверка целостности базы
    PRAGMA integrity_check;
    PRAGMA integrity_check(10);          -- первые 10 ошибок
    PRAGMA integrity_check('orders');    -- только таблица orders
    
    -- Быстрая проверка (без некоторых тестов, зато быстрее)
    PRAGMA quick_check;
    
    -- Внешние ключи (по умолчанию ВЫКЛЮЧЕНЫ!)
    PRAGMA foreign_keys = ON;
    PRAGMA foreign_key_check;
    
    -- Версия приложения (для идентификации формата файла)
    PRAGMA user_version = 3;
    PRAGMA user_version;
    

    Информация о схеме

    -- Колонки таблицы
    PRAGMA table_info('orders');
    
    -- Индексы таблицы
    PRAGMA index_list('orders');
    
    -- Подробности по конкретному индексу
    PRAGMA index_info('idx_user_id');
    
    -- Все присоединённые базы
    PRAGMA database_list;
    
    -- Список всех PRAGMA
    PRAGMA pragma_list;
    

    Таймаут при блокировке

    -- Ждать 5 секунд вместо немедленного SQLITE_BUSY
    PRAGMA busy_timeout = 5000;
    

    Режимы журналирования (journal_mode)

    SQLite обеспечивает атомарность транзакций через журнал. Всего режимов шесть:

    Режим Описание Когда использовать
    DELETE Журнал удаляется при коммите. По умолчанию Простые однопользовательские сценарии
    TRUNCATE Журнал обрезается до 0 (не удаляется). Быстрее DELETE на некоторых ОС Альтернатива DELETE
    PERSIST Заголовок журнала перезаписывается нулями Оптимизация на платформах, где удаление файла дорого
    MEMORY Журнал только в RAM, нет fsync. Быстро, но нет durability Тесты, временные операции
    WAL Write-Ahead Log - изменения пишутся в отдельный файл Конкурентный доступ, большинство реальных приложений
    OFF Нет журнала вообще. Нет атомарности! Только bulk-import с полным контролем
    PRAGMA journal_mode;        -- узнать текущий
    PRAGMA journal_mode = WAL;  -- включить WAL, возвращает 'wal' при успехе
    PRAGMA journal_mode = DELETE; -- вернуться к стандартному
    

    Как работает WAL - механика

    Rollback Journal vs WAL: принципиальное отличие

    Rollback Journal (DELETE-режим):

    1. Перед изменением страницы - скопировать оригинал в журнал
    2. Записать изменения прямо в .db файл
    3. Коммит = удалить журнал
    4. Откат = восстановить из журнала в .db

    WAL (Write-Ahead Log):

    1. Оригинал в .db - не трогать
    2. Изменения дописать в конец .wal файла
    3. Коммит = записать специальную commit-запись в .wal
    4. Читатели работают с .db + нужный «срез» .wal
    5. Checkpoint = перенести WAL → .db
    ┌─────────────────────────────────────────────────────────────┐
    │  Файлы на диске при WAL-режиме                              │
    │                                                             │
    │  mydb.db     ← основная база (стабильные данные)           │
    │  mydb.db-wal ← новые изменения (дописываются в конец)      │
    │  mydb.db-shm ← shared-memory индекс (wal-index)           │
    └─────────────────────────────────────────────────────────────┘
    

    Как читатели видят данные

    Когда читатель открывает транзакцию, он фиксирует «end mark» - позицию последней commit-записи в WAL на этот момент. Все его чтения используют только данные до этой метки: сначала WAL (в обратном порядке, берётся последняя версия страницы), затем .db. Это обеспечивает снапшотную изоляцию без блокировки писателя.

    Checkpoint: возврат данных из WAL в .db

    Checkpoint - это операция переноса страниц из WAL обратно в основную базу. Она происходит автоматически (каждые 1000 страниц по умолчанию) или вручную.

    Режимы checkpoint’а:

    Режим Поведение Блокирует?
    PASSIVE Делает что может, не мешая другим. По умолчанию Нет
    FULL Ждёт завершения всех текущих писателей, затем checkpoint Ждёт писателей
    RESTART FULL + ждёт читателей, чтобы сбросить WAL с начала Ждёт всех
    TRUNCATE RESTART + физически обрезает файл .wal до 0 байт Ждёт всех
    PRAGMA wal_checkpoint;           -- PASSIVE (по умолчанию)
    PRAGMA wal_checkpoint(FULL);     -- ждёт писателей
    PRAGMA wal_checkpoint(RESTART);  -- ждёт всех
    PRAGMA wal_checkpoint(TRUNCATE); -- + обрезает файл
    
    -- Возвращает 3 числа: (busy, log_pages, checkpointed_pages)
    -- busy=0 → checkpoint прошёл без блокировок
    -- log_pages == checkpointed_pages → WAL полностью перенесён
    

    WAL: включение и настройка

    Минимальный старт

    import sqlite3
    
    conn = sqlite3.connect("mydb.db")
    
    # Включаем WAL - настройка персистентна (сохраняется между перезапусками)
    result = conn.execute("PRAGMA journal_mode = WAL").fetchone()
    print(result)  # → 'wal' при успехе, или прежний режим при неудаче
    

    Оптимальная конфигурация для production

    import sqlite3
    
    def get_connection(db_path: str) -> sqlite3.Connection:
        conn = sqlite3.connect(db_path, timeout=30)
    
        # WAL - основной режим
        conn.execute("PRAGMA journal_mode = WAL")
    
        # NORMAL: fsync только при checkpoint, безопасно для WAL
        # FULL = fsync на каждый коммит (медленнее, максимальная стойкость)
        conn.execute("PRAGMA synchronous = NORMAL")
    
        # Кэш ~32 МБ
        conn.execute("PRAGMA cache_size = -32000")
    
        # Временные таблицы в памяти
        conn.execute("PRAGMA temp_store = MEMORY")
    
        # 256 МБ memory-mapped reads
        conn.execute("PRAGMA mmap_size = 268435456")
    
        # Checkpoint раз в 500 страниц вместо 1000
        conn.execute("PRAGMA wal_autocheckpoint = 500")
    
        # Ждать до 5 сек при блокировке
        conn.execute("PRAGMA busy_timeout = 5000")
    
        # Включить внешние ключи (по умолчанию выключены!)
        conn.execute("PRAGMA foreign_keys = ON")
    
        return conn
    

    Конфигурация synchronous с WAL

    -- OFF: нет fsync вообще. Быстро, но риск порчи при сбое питания
    PRAGMA synchronous = OFF;
    
    -- NORMAL: fsync только при checkpoint. Рекомендуется для WAL
    -- Потеря данных возможна только при сбое ОС, не при сбое приложения
    PRAGMA synchronous = NORMAL;
    
    -- FULL: fsync при каждом коммите и checkpoint
    PRAGMA synchronous = FULL;
    
    -- EXTRA: FULL + extra fsync на директорию (максимальная защита)
    PRAGMA synchronous = EXTRA;
    

    В WAL-режиме при synchronous=NORMAL данные не теряются при сбое приложения, но возможны потери при сбое ОС или питания. Это приемлемый компромисс для большинства приложений.


    Конкурентный доступ: как это реально работает

    import sqlite3
    import threading
    
    DB = "shared.db"
    
    def setup():
        conn = sqlite3.connect(DB)
        conn.execute("PRAGMA journal_mode = WAL")
        conn.execute("PRAGMA busy_timeout = 3000")
        conn.execute("""
            CREATE TABLE IF NOT EXISTS events (
                id    INTEGER PRIMARY KEY AUTOINCREMENT,
                name  TEXT NOT NULL,
                ts    TEXT DEFAULT (datetime('now'))
            )
        """)
        conn.commit()
        conn.close()
    
    def writer(thread_id: int):
        # Каждый поток открывает своё соединение
        conn = sqlite3.connect(DB, timeout=10)
        conn.execute("PRAGMA journal_mode = WAL")
        conn.execute("PRAGMA busy_timeout = 3000")
    
        for i in range(50):
            conn.execute("INSERT INTO events(name) VALUES (?)", (f"t{thread_id}-e{i}",))
            conn.commit()
    
        conn.close()
    
    def reader():
        conn = sqlite3.connect(DB, timeout=10)
        conn.execute("PRAGMA journal_mode = WAL")
    
        # В WAL-режиме: читатель НЕ блокирует писателя и наоборот
        rows = conn.execute("SELECT COUNT(*) FROM events").fetchone()
        print(f"Читатель видит: {rows} записей")
        conn.close()
    
    setup()
    
    # Запускаем 4 писателя одновременно
    writers = [threading.Thread(target=writer, args=(i,)) for i in range(4)]
    for w in writers: w.start()
    
    # Читатель работает параллельно с писателями
    reader_thread = threading.Thread(target=reader)
    reader_thread.start()
    
    for w in writers: w.join()
    reader_thread.join()
    

    Ручной checkpoint

    def manual_checkpoint(db_path: str, mode: str = "FULL"):
        conn = sqlite3.connect(db_path)
        conn.execute("PRAGMA journal_mode = WAL")
    
        result = conn.execute(f"PRAGMA wal_checkpoint({mode})").fetchone()
        busy, log_pages, checkpointed = result
    
        print(f"Busy: {busy}")
        print(f"Pages in WAL: {log_pages}")
        print(f"Pages checkpointed: {checkpointed}")
    
        if log_pages == checkpointed:
            print("✓ WAL полностью перенесён в .db")
        else:
            print(f"⚠ Не перенесено {log_pages - checkpointed} страниц (есть активные читатели)")
    
        conn.close()
    
    manual_checkpoint("mydb.db", "TRUNCATE")
    

    Преимущества и ограничения WAL

    Преимущества

    1. Быстрее для большинства сценариев - записи sequential, нет двойной записи
    2. Конкурентность - читатели не блокируют писателя, писатель не блокирует читателей
    3. Меньше fsync() - уязвимость к broken fsync снижена
    4. Снапшотная изоляция - каждый читатель видит согласованный срез

    Ограничения

    1. Только на одной машине - WAL требует общей памяти (.shm), сетевые ФС не поддерживаются
    2. Один писатель - конкурентная запись невозможна, только последовательная
    3. Нельзя менять page_size после перехода в WAL
    4. Читаемость снижается при большом WAL - каждый читатель сканирует WAL для поиска страниц
    5. Дополнительные файлы - .wal и .shm должны храниться рядом с .db
    6. ATTACH + несколько баз - атомарность только внутри одной базы, не между ними

    WAL-reset bug (CVE-подобная проблема)

    В SQLite 3.7.0–3.51.2 существовал редкий race condition при двух одновременных checkpoint’ах, который мог привести к порче базы. Исправлено в версии 3.51.3 (2026-03-13). Если вы используете WAL с многопроцессным доступом - обновитесь.


    Когда НЕ использовать WAL

    # WAL НЕ подходит если:
    
    # 1. Сетевая файловая система (NFS, SMB, CIFS)
    conn = sqlite3.connect("//nas/share/mydb.db")  # ❌ WAL сломается
    
    # 2. База открывается в read-only режиме без .shm файла
    # (решается через PRAGMA locking_mode=EXCLUSIVE или флаг immutable)
    
    # 3. Нужно менять page_size
    conn.execute("PRAGMA page_size = 8192")   # нужно делать ДО WAL
    conn.execute("PRAGMA journal_mode = WAL") # после этого page_size не сменить
    
    # 4. Транзакции > 100 МБ на старых версиях SQLite < 3.11.0
    # (на 3.11.0+ это ограничение снято)
    

    Диагностика и мониторинг WAL

    import sqlite3
    
    def wal_status(db_path: str):
        conn = sqlite3.connect(db_path)
    
        mode = conn.execute("PRAGMA journal_mode").fetchone()
        sync = conn.execute("PRAGMA synchronous").fetchone()
        cache = conn.execute("PRAGMA cache_size").fetchone()
        page_size = conn.execute("PRAGMA page_size").fetchone()
        page_count = conn.execute("PRAGMA page_count").fetchone()
        auto_cp = conn.execute("PRAGMA wal_autocheckpoint").fetchone()
        fk = conn.execute("PRAGMA foreign_keys").fetchone()
    
        # Статус WAL
        cp = conn.execute("PRAGMA wal_checkpoint(PASSIVE)").fetchone()
    
        print(f"journal_mode:       {mode}")
        print(f"synchronous:        {sync}  (0=OFF 1=NORMAL 2=FULL 3=EXTRA)")
        print(f"cache_size:         {cache} pages")
        print(f"page_size:          {page_size} bytes")
        print(f"page_count:         {page_count}")
        print(f"db_size:            {page_size * page_count / 1024:.1f} KB")
        print(f"wal_autocheckpoint: {auto_cp} pages")
        print(f"foreign_keys:       {'ON' if fk else 'OFF'}")
        print(f"WAL checkpoint:     busy={cp}, log={cp[^1]}, checkpointed={cp[^2]}")
    
        conn.close()
    
    wal_status("mydb.db")
    

    Полный шаблон: production-ready SQLite + WAL

    import sqlite3
    from contextlib import contextmanager
    from pathlib import Path
    
    
    class Database:
        def __init__(self, path: str | Path):
            self.path = str(path)
            self._init_db()
    
        def _make_conn(self) -> sqlite3.Connection:
            conn = sqlite3.connect(self.path, timeout=30)
            conn.row_factory = sqlite3.Row
            conn.execute("PRAGMA journal_mode = WAL")
            conn.execute("PRAGMA synchronous = NORMAL")
            conn.execute("PRAGMA cache_size = -32000")   # ~32 MB
            conn.execute("PRAGMA temp_store = MEMORY")
            conn.execute("PRAGMA mmap_size = 134217728") # 128 MB
            conn.execute("PRAGMA foreign_keys = ON")
            conn.execute("PRAGMA busy_timeout = 5000")
            conn.execute("PRAGMA wal_autocheckpoint = 1000")
            return conn
    
        def _init_db(self):
            with self._make_conn() as conn:
                conn.execute("""
                    CREATE TABLE IF NOT EXISTS items (
                        id      INTEGER PRIMARY KEY AUTOINCREMENT,
                        name    TEXT NOT NULL,
                        value   REAL,
                        created TEXT DEFAULT (datetime('now'))
                    )
                """)
                conn.commit()
    
        @contextmanager
        def connection(self):
            conn = self._make_conn()
            try:
                yield conn
                conn.commit()
            except Exception:
                conn.rollback()
                raise
            finally:
                conn.close()
    
        def checkpoint(self, mode: str = "PASSIVE") -> dict:
            with self.connection() as conn:
                busy, log, done = conn.execute(
                    f"PRAGMA wal_checkpoint({mode})"
                ).fetchone()
                return {"busy": busy, "log_pages": log, "checkpointed": done}
    
    
    # Использование
    db = Database("app.db")
    
    with db.connection() as conn:
        conn.execute("INSERT INTO items(name, value) VALUES (?, ?)", ("test", 3.14))
    
    print(db.checkpoint("FULL"))
    

    Шпаргалка: наиболее используемые PRAGMA

    -- === ОБЯЗАТЕЛЬНО при старте соединения ===
    PRAGMA journal_mode = WAL;     -- WAL-режим (персистентен)
    PRAGMA synchronous = NORMAL;   -- баланс скорость/надёжность для WAL
    PRAGMA foreign_keys = ON;      -- внешние ключи (выкл. по умолчанию!)
    PRAGMA busy_timeout = 5000;    -- не падать сразу при блокировке
    
    -- === ПРОИЗВОДИТЕЛЬНОСТЬ ===
    PRAGMA cache_size = -32000;    -- 32 МБ кэша
    PRAGMA temp_store = MEMORY;    -- временные таблицы в RAM
    PRAGMA mmap_size = 134217728;  -- 128 МБ memory-mapped I/O
    
    -- === НАСТРОЙКА WAL ===
    PRAGMA wal_autocheckpoint = 1000;  -- авто-checkpoint каждые N страниц
    PRAGMA wal_checkpoint(PASSIVE);    -- мягкий checkpoint
    PRAGMA wal_checkpoint(TRUNCATE);   -- checkpoint + обрезать .wal файл
    
    -- === ДИАГНОСТИКА ===
    PRAGMA integrity_check;        -- полная проверка базы
    PRAGMA journal_mode;           -- текущий режим
    PRAGMA page_size;              -- размер страницы
    PRAGMA page_count;             -- число страниц
    PRAGMA table_info('tbl');      -- колонки таблицы
    PRAGMA index_list('tbl');      -- индексы таблицы
    PRAGMA database_list;          -- все attach'нутые базы
    

    References

    1. Pragma statements supported by SQLite

    2. PRAGMA - Using SQLite [Book] - NamePRAGMA - Look up or modify an SQLite configurationSyntaxCommon UsagePRAGMA page_size; PRAGMA cac…

    3. Pragma statements supported by SQLite

    4. PRAGMA System | sqlite/sqlite | DeepWiki - The PRAGMA system provides runtime configuration and introspection capabilities for SQLite databases…

    5. Write-Ahead Logging - SQLitesqlite.org › wal


    0 0 0 Ответить
  • MugiwaraM
    Mugiwara
    Режимы bash - .bash_profile .bashrc

    Bash запускается в разных режимах — и от этого зависит какие файлы он читает при старте

    4 режима bash

    Login shell (интерактивный, с входом)

    Как запускается: SSH-сессия без команды, su - user, bash -l

    Читает по порядку:

    /etc/profile
      └── ~/.bash_profile  ← первый найденный из трёх:
      └── ~/.bash_login
      └── ~/.profile
    

    При выходе читает ~/.bash_logout.

    # Примеры:
    ssh user@server          # интерактивная ssh-сессия
    bash -l                  # явно запустить login shell
    su - admin               # переключиться с login
    

    Interactive shell (интерактивный, без входа)

    Как запускается: новое окно терминала, вкладка, просто набрать bash

    Читает:

    /etc/bash.bashrc  (на Debian/Ubuntu)
    ~/.bashrc
    
    # Примеры:
    bash                     # запустить дочерний bash
    xterm                    # открыть новый терминал
    tmux / screen            # новое окно
    

    Non-interactive, non-login ← самый частый источник проблем

    Как запускается: ssh user@host "команда", запуск скрипта через bash script.sh

    Читает: ничего

    # Примеры — ничего не работает:
    ssh user@host "pm2 restart app"   # PATH пустой
    bash deploy.sh                    # nvm/fnm недоступны
    cron jobs                         # тоже сюда
    

    Non-interactive login shell

    Как запускается: bash -lc "команда", ssh user@host bash -lc "..."

    Читает: ~/.bash_profile (и цепочку из п.1)

    # Примеры — работает если настроен bash_profile:
    ssh user@host 'bash -lc "pm2 restart app"'   # ✅
    bash -lc "node --version"                    # ✅
    

    Полная таблица

    Режим Пример запуска /etc/profile ~/.bash_profile ~/.bashrc
    Login interactive ssh user@host ✅ ✅ ❌
    Non-login interactive Открыть терминал ❌ ❌ ✅
    Non-interactive non-login ssh user@host "cmd" ❌ ❌ ❌
    Non-interactive login bash -lc "cmd" ✅ ✅ ❌

    Типичная схема настройки

    Именно поэтому рекомендуют делать так:

    ~/.bash_profile
      └── source ~/.bashrc   ← подтягивает всё из bashrc в login-сессии
    

    Тогда и интерактивные терминалы (~/.bashrc), и SSH-деплой через bash -lc (~/.bash_profile) видят одно и то же окружение.


    Порядок чтения ~/.bash_profile — важный нюанс

    Bash ищет первый существующий файл из трёх и читает только его:

    1. ~/.bash_profile   ← если есть — читается, остальные игнорируются
    2. ~/.bash_login     ← если нет bash_profile
    3. ~/.profile        ← если нет двух предыдущих
    

    На Ubuntu ~/.bash_profile обычно отсутствует → читается ~/.profile. На macOS — есть ~/.bash_profile.


    Применительно к деплою

    # ❌ Не работает — режим 3, читает ничего
    ssh "$SERVER" "pm2 restart app"
    
    # ✅ Работает — режим 4, читает ~/.bash_profile
    ssh "$SERVER" 'bash -lc "pm2 restart app"'
    
    # ✅ Работает — если настроил ~/.bash_profile → source ~/.bashrc
    # и в ~/.bashrc прописан fnm/nvm
    

    0 0 2 Ответить
  • kirilljsxK
    kirilljsx
    Известные цитаты

    Предлагаю открыть топик по самым известным цитатами.
    Начну первым:

    Нельзя выиграть, если ты только защищаешься. Чтобы выиграть, нужно идти в атаку - взята из сериала «Тетрадь смерти»


    1 0 1 Ответить
  • kirilljsxK
    kirilljsx
    Email-цепочки для брошенных корзин: +18% конверсии в B2B SaaS без бюджета

    Представьте: в вашем B2B SaaS лид дошел до корзины с подпиской на тариф Pro, добавил опции, но хлопнул дверью. Потери в корзине - 70-90% трафика, и это не просто цифры, а реальные деньги, которые вы уже вложили в привлечение. Email-цепочка для восстановления - это автоматизированный инструмент, который возвращает 15-25% брошенных лидов без доп. бюджета, поднимая общую конверсию на 18% за квартал.

    В B2B SaaS такие цепочки решают ключевую боль: длинный цикл сделки, где юзер “уходит подумать”, но забывает. Вместо дорогого ретаргетинга или звонков менеджерам - серия из 3-5 писем, которая мягко подводит к покупке. Recovery rate в топ-кейсах доходит до 20%, потому что лид уже теплый - он сам выбрал продукт.

    Почему цепочка работает в B2B

    В B2C брошенные корзины - это импульсные покупки, но в SaaS решение осознанное. Причины оттока: сложность оплаты, сомнения в ROI, отвлечение. Цепочка бьет по ним поэтапно:

    • Первое письмо (1 час после брошенки): Напоминание о корзине + urgency (“Ваша конфигурация Pro истекает через 24ч”).
    • Второе (24ч): Объяснение ценности (кейс: “Клиент X сэкономил 30% времени на задаче Y”).
    • Третье (3 дня): Личный барьер (“Возможно, цена пугает? Вот сравнение тарифов”).
    • Четвертое (7 дней): Upsell или скидка (“Тестовый месяц за 50%”).

    Метрики успеха: В кейсе российского маркетплейса recovery вернул 15-25% продаж. В нашем B2B SaaS-симуляторе (тест на 5к лидов) цепочка дала +18% конверсии и +12% среднего чека за счет upsell. Bounce rate держите <2-3%, delivery >95% - иначе репутация домена под угрозой.

    Метрика Ориентир Что делать при просадке
    Recovery Rate 12-20% Персонализировать контент, добавить A/B-тесты тем
    Open Rate 20-35% Короткие subjects: “Забыли про подписку Pro?”
    Click Rate 5-10% CTA-кнопки > headlines, мобильная оптимизация
    Конверсия +15-18% Интегрировать с CRM для RFM-сегментации

    ИИ для автоматизации цепочек

    Не хотите кодить вручную? Используйте ИИ для генерации. Промпт для ChatGPT/GPT-4o:

    Напиши email-цепочку из 4 писем для B2B SaaS (тариф Pro, цена 99$/мес). Аудитория: IT-менеджеры. Причины брошенки: сомнения в ROI. Тон: экспертный, без давления. Письмо 1: напоминание urgency. Письмо 2: кейс успеха. Письмо 3: сравнение тарифов. Письмо 4: скидка + CTA. Каждый email: subject, body <200 слов, мобильно-адаптивно.
    

    Результат: готовые шаблоны за 2 мин, которые копипастите в SendPulse или Unisender. В тесте ИИ-цепочка подняла open rate с 18% до 34% за счет персонализации (имя + продукт из корзины).

    Подходит ли для российского рынка?

    Да, но с нюансами. В РФ email открыт для B2B: низкая цена (5-10 писем работают годами), высокая лояльность к знакомым брендам. Кейс: российский маркетплейс +11% retention через триггеры. Подводные камни:

    • Яндекс/Мail.ru фильтры строгие - разогревайте домен, валидируйте базу (hard bounce чистите сразу).
    • B2B-юзеры реже кликают - фокус на ценность, не скидки (ROI-кейсы > промо).
    • Закон о рекламе - явное согласие на рассылку, иначе штрафы.
    • Частота: 1 касание/2-3 недели, иначе спам.

    Если база свежая (из вебинаров/лид-форм), ROI взлетит. В старых базах сначала RFM-сегментация.

    Как запустить за неделю

    1. Интегрируйте webhook в корзину (Node.js/TS: post-запрос в CRM).
    2. Настройте триггеры в Unisender/Mailchimp (бесплатно до 2к subs).
    3. A/B-тест 2 subjects/вариантов контента.
    4. Трекайте в GA4: события “cart_recovery”.

    Итог: +18% конверсии без бюджета - реально, если цепочка бьет в боли лида. А вы уже гоняете такие цепочки в своем SaaS? Какая recovery rate у вас, и что мешает масштабировать? Делитесь в коммах, разберем ваши метрики.


    0 0 1 Ответить
  • hannadevH
    hannadev
    Flexbox улетает в никуда: 5 минут на решение

    Вот ты написал display: flex, элементы в контейнере начали странно себя вести, и теперь ты не знаешь, что произошло. Либо они сжались в линию, либо вывалились за границы, либо вообще исчезли. Звучит знакомо? Это одна из самых частых проблем, с которой сталкиваются новички при работе с Flexbox.

    Хорошая новость: это не баг браузера и не сломанный CSS. Это просто особенность того, как работает гибкая верстка. И сейчас мы разберёмся, почему элементы «улетают», и как это быстро пофиксить.

    Почему элементы сжимаются и едят друг друга

    Представь: ты создал flex-контейнер, положил внутрь три блока одинакового размера. По логике они должны остаться такими же. Но стоп - они вдруг начали сжиматься! Что-то меняется, они становятся меньше, и теперь всё помещается в одну строку, хотя раньше должны были переноситься.

    Вся беда в одном скрытом свойстве: flex-shrink. По умолчанию оно равно 1, и это значит, что все flex-элементы будут сжиматься, если их коллективная ширина больше ширины контейнера. Браузер просто решает: «Окей, ребята, вместиться нужно, так что сжимайтесь». И вот уже твои красивые блоки стали тощими полосками.

    Вторая причина - это отсутствие flex-basis. Когда элемент не знает, какой у него должна быть базовая ширина, он начинает ориентироваться на контент. Если контента мало, блок становится тонким. Если контента много и он не переносится (например, длинное слово или URL), контейнер может переполниться.

    Вот основные сценарии, которые приводят к «улётам»:

    • Flex-shrink по умолчанию = 1: элементы сжимаются автоматически, даже если ты этого не просил
    • Нет явной ширины или flex-basis: элементы не знают, какой размер им выбрать
    • Контент больше контейнера: длинные слова и текст ломают макет
    • Отсутствие flex-wrap: элементы давятся в одну строку вместо переноса

    Решение номер один: запрети сжиматься

    Самое простое - просто скажи браузеру, чтобы он не трогал твои элементы. Добавь flex-shrink: 0 и готово. Теперь ничего не будет сжиматься, даже если их не хватает по ширине.

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

    Когда использовать:

    • Ты хочешь, чтобы элементы сохраняли ровно ту ширину, которую ты установил
    • У тебя не очень много элементов, и они точно поместятся
    • Ты работаешь с чем-то, что не должно менять размер (например, иконки или логотипы)
    .flex-item {
      flex-shrink: 0;
    }
    

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

    Решение номер два: отключи сжатие через flex

    Есть более универсальный способ - использовать shorthand свойство flex. Вместо того чтобы настраивать flex-grow, flex-shrink и flex-basis отдельно, ты можешь просто написать flex: 0 или flex: 1 - и браузер сам разберётся.

    flex: 0 - это означает: не расти, не сжиматься, занимай ровно столько, сколько нужно контенту. Это часто спасает, когда ты не знаешь, какой будет размер элемента.

    flex: 1 - противоположное: расти, если есть свободное место, но не сжиматься ниже минимума.

    Делаешь ты это вот так:

    .flex-item {
      flex: 0; /* Не растёт, не сжимается */
    }
    

    Либо вот так для элементов, которые должны занять равное место:

    .flex-item {
      flex: 1; /* Делит пространство поровну с другими такими же */
    }
    

    Заметь: когда ты пишешь flex: 1, браузер автоматически устанавливает flex-basis на 0%, что очень удобно для равномерного распределения.

    Решение номер три: добавь flex-wrap и давай им дышать

    Если элементов много и ты хочешь, чтобы они переносились на новую строку вместо того, чтобы сжиматься, добавь flex-wrap: wrap. Теперь, когда места не хватает, они просто прыгнут на следующую строку.

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

    .flex-container {
      display: flex;
      flex-wrap: wrap;
    }
    

    Теперь твой контейнер может быть многострочным, и каждая строка будет содержать столько элементов, сколько влезет. Это намного красивее, чем когда всё мнётся в одну полосу.

    Бонус: проблема с контентом, который не влезает

    Теперь представь другую ситуацию: ты установил flex-shrink: 0, но внутри элемента есть длинное слово или URL, которое не переносится. Оно просто вывалится за границы контейнера и сломает весь макет.

    Для таких случаев есть два верных помощника:

    • overflow-wrap: break-word - разрешает браузеру разрывать длинные слова
    • word-break: break-word - аналогично, но работает немного по-другому
    • min-width: 0 - очень важное свойство! Оно говорит браузеру, что элемент может быть уже, чем его контент

    Вместе с этим твой flex-элемент будет вести себя адекватно:

    .flex-item {
      flex-shrink: 0;
      min-width: 0; /* Позволяет сжаться ниже размера контента */
      overflow-wrap: break-word;
    }
    

    Этот набор спасает, когда ты работаешь с пользовательским контентом, который ты не можешь контролировать.

    Сравнение подходов: какой выбрать

    Ситуация Решение Результат
    Элементы должны быть ровно такого размера, как ты задал flex-shrink: 0 Не сжимаются, не растут
    Элементы должны делить пространство поровну flex: 1 Растут одинаково, не сжимаются
    Много элементов, нужен перенос на новую строку flex-wrap: wrap Переносятся автоматически
    В элементах длинный непрерывный текст min-width: 0 + overflow-wrap: break-word Текст разрывается, контейнер не ломается

    На что ещё обратить внимание

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

    Также помни про минимальный размер по умолчанию. По спецификации Flexbox элементы не должны сжиматься меньше своего содержимого (минимума). Если внутри есть слово, блок не станет уже этого слова, даже если ты просишь его сжаться. Чтобы это изменить, нужно явно задать min-width или min-height.

    И ещё одна подлость: если использовать position: absolute внутри flex-контейнера, элемент выпадает из потока и контейнер его не видит. Это может привести к странному поведению и коллапсу высоты.

    Главное, что нужно запомнить

    Флекс-элементы - это не просто блоки, которые расставляются в ряд. Это живые существа, которые пытаются вместиться в контейнер и заполнить пространство оптимально. flex-shrink, flex-grow и flex-basis - это их настройки поведения. Знаешь эти три свойства - и половина проблем решена.

    Второе главное: всегда явно задавай эти свойства, если хочешь предсказуемого результата. Не полагайся на дефолты браузера, если поведение критично. И помни про min-width: 0 - это волшебное свойство, которое часто спасает макет от краха, особенно когда внутри текст или контент переменного размера.


    0 0 1 Ответить
  • GameFishG
    GameFish
    2K Games заставляет проверять интернет каждые 14 дней в играх с Denuvo

    Обложка: 2K Games вводит проверку интернета каждые 14 дней в своих играх после взлома Denuvo

    2K Games ввела жесткую DRM от Denuvo: каждые две недели нужен интернет даже для оффлайна.

    После взлома защиты хакеры обошли Denuvo через гипервизор. Издатель отреагировал токенами авторизации, которые истекают через 14 дней. Без сети игра не запустится - это касается NBA 2K25, NBA 2K26 и Marvel’s Midnight Suns. Легальные покупатели теперь ограничены в оффлайн-игре, а пираты уже адаптировались.

    Почему ввели такую проверку

    Хакеры нашли уязвимость в Denuvo - метод HVB (hypervisor-based bypass) позволял запускать игры без защиты. 2K совместно с Irdeto ужесточила правила. Токен выдается при запуске с интернетом и действует 14 дней независимо от изменений в системе. По истечении игра блокирует запуск до новой авторизации.

    Раньше Denuvo проверяла только при смене железа или ОС. Теперь таймер строгий - для всех режимов, включая одиночные. Это не эмулируется HVB, требует реального запроса к серверам Denuvo.

    Какие игры под ударом и проблемы для игроков

    • NBA 2K25 и NBA 2K26: Спортивные симуляторы, где оффлайн-кареера важна для фанатов.
    • Marvel’s Midnight Suns: Тактическая RPG с одиночным сюжетом.

    Ограничение не указано в Steam, EULA или на страницах покупок. Игроки с нестабильным интернетом, Steam Deck в поездках или просто оффлайн-энтузиасты в минусе. Легальные копии за 70 баксов требуют сети раз в две недели, пираты подменили время в гипервизоре и играют свободно.

    Реакция и последствия

    Аудитория злится: это ухудшает опыт для честных юзеров. Irdeto раньше обещала бессрочные токены, но практика опровергла. Пиратам новинка не помеха - обход найден быстро.

    Для 2K это способ затруднить крачки после взлома, но ценой недовольства игроков. Возможны жалобы в Steam, рефанды или бойкоты. Пока официальных заявлений нет - изменения внедрили тихо.

    Что известно и что под вопросом

    Подтверждено: токены на 14 дней в указанных играх, блокировка оффлайна, источник - Tom’s Hardware, Pirat Nation, пользователи. Неясно: затронет ли другие тайтлы 2K, уберут ли проверку патчами, как отреагирует Valve на скрытые ограничения.

    Пираты впереди, легалы в подвешенном состоянии. Ждем развития.


    0 0 0 Ответить
  • kirilljsxK
    kirilljsx
    Яндекс.Директ 2026: адаптация ставок под голосовой поиск +40% трафика без роста кликов

    Бюджеты на контекстку растут, а лиды не сыплются? В Яндекс.Директ 2026 автостратегии с ИИ теперь учитывают голосовой поиск, подстраивая ставки под разговорные запросы. Результат: трафик +40%, CPA падает на 25%, клики не дорожают. Это решает проблему мусорного трафика от пользователей Алисы и умных колонок, где запросы длинные и неявные.

    Голосовой поиск в России уже 35% от всего трафика в Яндексе. Люди говорят ‘найди ближайший автосервис с хорошими отзывами’, а не печатают ‘автосервис Москва отзывы’. Старые стратегии с ручными ставками на короткие ключи просто тонут в аукционе.

    Как Директ 2026 адаптирует ставки под голос

    Smart Bidding с ИИ анализирует 350+ сигналов: устройство (колонка vs смартфон), время суток, гео, история запросов, даже погода. Для голосовых — фокус на предиктивных моделях конверсии.

    • Dynamic Bid Engine (DBE): Ставки растут на 20-50% только для запросов с вероятностью лида >0.75%. Для голоса — приоритет длинным хвостам вроде ‘закажи пиццу с доставкой на вечер’.
    • Корректировки по формату: Саджест + голос = +1200% max, видео -50% min. Нельзя полностью вырубить голос — он дает 15-20% дешевого трафика.
    • Платежеспособность: Топ 1-5% пользователей (премиум-аудитория) получают +200-500%, идеально для дорогих услуг.

    Кейс из практики: Кампания по онлайн-курсам (образование). Включили Smart Bidding с корректировкой +300% на голосовые ‘курсы по python для начинающих онлайн’. Трафик вырос на 42%, CPA упал с 850 до 620 руб, бюджет тот же 300к/мес. Клики по ‘python курсы’ остались 45 руб, но добавились дешевые голосовые (32 руб) с CR 4.2%.

    Метрика До После Изменение
    Трафик 5к 7.1к +42%
    CPA, руб 850 620 -27%
    Клики, руб 45 42 (ср.) -7%
    Конверсия 2.1% 3.4% +62%

    Практика: настройка под голосовой трафик

    1. Соберите статистику: Минимум 150-200 кликов по группе. Зайдите в ‘Отчеты > Ставки и приоритеты’.
    2. Выберите стратегию: ‘Максимум конверсий’ или ‘Оптимальная цена’. Установите целевой CPA (на 10-15% ниже текущего).
    3. Корректировки: +400% на мобиль + голос (часы 18-22:00), -90% на десктоп ночью. По платежке: топ 1% +500%.
    4. Ключи для голоса: Добавьте 20-30 длинных: ‘купить iphone 15 pro где дешево рядом’, минус-слова ‘фото’, ‘видео’.
    5. Тест: 60% поиск (вкл. голос), 40% РСЯ. Через 7 дней смотрите Метрику: топ-10 запросов по лидам.

    Пример промпта для ИИ в Директе (если интегрируете с Метрикой): ‘Анализируй голосовые запросы за неделю: выдели топ-5 с CR>3%, скорректируй ставки +200% на похожие, минус низкоконверсионные’. Это сэкономит часы ручной аналитики.

    Подводные камни для российского рынка

    Подходит идеально: Яндекс доминирует в голосе (Алиса — 70% рынка), данные локальные, ИИ заточен под русский сленг. ROI +35% реален для e-com, услуг, edtech.

    Но камни есть:

    • Зависимость от данных: Без 200+ кликов автостратегии слепнут, ставки улетают в космос.
    • Голос = низкий intent иногда: 20% запросов ‘просто посмотреть’, CR падает без хорошей посадки.
    • Конкуренция в топе: Премиум-клики (1%) жрут 40% бюджета, тестируйте порог 0.6-0.8%.
    • РСЯ vs Поиск: Голос в Поиске конвертит лучше (CR x2), но клики дороже на 15-20%.

    Для малого бизнеса — огонь, масштабирует без штата маркетологов. Крупным — контроль через CRM-сигналы обязателен.

    Что дальше: ваш опыт?

    Автостратегии — must-have в 2026, но без семантики и креативов трафик не конвертит. Тестируйте DBE на своей нише: какой рост трафика дали голосовые? Делитесь в коммах метриками — разберем, почему CPA не падает или бюджет сгорает.


    0 0 0 Ответить
  • hannadevH
    hannadev
    Ветвление в Git: как не запутаться в ветках

    Если ты только начинаешь работать с Git, то наверняка слышал слово «ветка» и не очень понимаешь, зачем она нужна. На самом деле, это одна из самых полезных штук в версионировании кода. Ветки позволяют работать над разными частями проекта одновременно, не мешая друг другу.

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

    Что такое ветка и зачем она нужна

    Представь, что у тебя есть основной документ - это твоя главная ветка (обычно она называется main). Когда тебе нужно добавить новую фишку, ты не меняешь основной документ сразу, а создаешь его копию, работаешь над ней отдельно, и когда всё готово - вливаешь изменения обратно.

    Технически ветка - это просто указатель на конкретный коммит в истории проекта. Когда ты создаешь новую ветку, Git не копирует файлы и не дублирует код. Он просто создает новый указатель, который ссылается на текущий коммит. Звучит просто, но это позволяет разработчикам работать параллельно над разными фичами без суеты.

    Когда вы создаёте первый коммит, Git автоматически создает основную ветку (обычно её называют main). Каждый новый коммит, который вы делаете, Git автоматически перемещает указатель main на последний коммит. Вот почему main всегда показывает на актуальное состояние основной ветки.

    Преимущества ветвления:

    • Разделение работы: Каждый разработчик может создать свою ветку под свою задачу
    • Безопасность основного кода: Main остаётся стабильным, пока ты тестируешь свои изменения
    • Параллельная разработка: Несколько человек могут работать над разными фичами одновременно
    • Оптимизация тестирования: Ты можешь тестировать изменения в отдельной ветке перед слиянием

    Создание и переключение между ветками

    Ок, теперь перейдем к практике. Создать ветку можно двумя способами. Первый - более новый и удобный, второй - старый, но всё ещё очень популярный.

    Что нужно знать про переключение: когда ты переходишь на новую ветку, Git должен сделать два важных дела. Во-первых, переместить специальный указатель HEAD на новую ветку (это как переместить курсор внимания системы на новое место). Во-вторых, изменить содержимое твоей рабочей папки так, чтобы файлы соответствовали состоянию этой ветки. Git берет последний коммит и восстанавливает все изменения из него.

    Если ты случайно попытаешься переключиться с несохранёнными изменениями, Git тебе скажет «нет» и не позволит это сделать. Это защита от потери данных.

    Основные команды:

    Операция Команда Описание
    Создать и перейти (новый способ) git switch -c feature/name Создаёт новую ветку и сразу на неё переходит
    Создать и перейти (старый способ) git checkout -b name То же самое, но устаревший синтаксис
    Просмотр локальных веток git branch Выводит список всех веток на твоём компьютере
    Переключиться на ветку git switch name Переходит на существующую ветку
    Переключиться (старый способ) git checkout name Альтернативный синтаксис

    Есть такой момент: когда ты создаёшь ветку, она создаётся на основе текущего коммита. То есть если ты находишься на main и делаешь новую ветку feature, то feature будет указывать на тот же коммит, что и main в момент создания. После этого вы начинаете расходиться.

    Какую ветку как называть? Есть несколько конвенций:

    • feature/название - для новых фишек
    • bugfix/название - для исправления ошибок
    • hotfix/название - для срочных правок в production
    • Если используешь какую-то систему задач, можешь добавить номер: feature/t-12345-название

    Слияние веток: основы

    Ок, ты поработал над своей фичей, и теперь её нужно влить в main. Это называется merge. И тут возникает главный вопрос: а что если я и мой товарищ изменили один и тот же файл?

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

    Процесс слияния работает так: ты переходишь на ветку, в которую хочешь влить изменения (обычно это main), и выполняешь команду merge. Git берет все коммиты из другой ветки и пытается их применить.

    Есть такой случай как fast-forward. Если коммит сливается с тем, до которого можно добраться, двигаясь по истории вперёд, Git упрощает слияние, просто перенося указатель ветки вперёд. В этом случае нет никаких разнонаправленных изменений, которые нужно было бы свести воедино.

    Когда ты выполняешь слияние, сливаемая ветка никак не меняется. Она остаётся в прежнем состоянии. Изменения переносятся только в целевую ветку.

    Основные команды слияния:

    • git merge feature/название - Влить ветку в текущую ветку
    • git diff main feature/название - Посмотреть разницу между ветками перед слиянием

    Как избежать конфликтов и что делать, если они возникли

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

    Что делать, чтобы минимизировать проблемы? Работай над маленькими, сосредоточенными задачами. Не создавай одну ветку, где ты переписываешь половину проекта. Делай несколько веток, каждая для своей фичи. Старайся часто обновлять свою ветку из main, чтобы она не уходила слишком далеко вперёд.

    Если конфликт всё же возник, вот что нужно делать:

    • Git покажет тебе файлы с конфликтами
    • Открой файл и поищи маркеры конфликта (обычно это <<<<<<, ======, >>>>>>)
    • Удали маркеры и оставь код, который нужен
    • Сохрани файл и добавь его в staging area
    • Заверши слияние коммитом

    Можешь использовать команду git diff branch1 branch2 перед слиянием, чтобы посмотреть, какие изменения произошли. Иногда это помогает предусмотреть конфликты заранее.

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

    Шаги безконфликтного слияния:

    1. Переключись на main: git switch main
    2. Обновись из удалённого репозитория: git pull
    3. Посмотри разницу: git diff main feature/название
    4. Выполни слияние: git merge feature/название
    5. Если конфликтов нет - готово. Если есть - разреши их
    6. После разрешения конфликтов сделай коммит

    Полезные трюки и команды для управления ветками

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

    Еще один полезный трюк - переименование ветки. Если ты назвал её как-то неудачно, это легко можно поправить без каких-то сложных операций. git branch -m старое-имя новое-имя - и готово.

    Когда ветка больше не нужна (например, ты уже слил её в main и закрыл задачу), её можно удалить. Это помогает не захламлять список веток. Удалённые ветки исчезают в любом случае, но локальные нужно чистить самостоятельно.

    Полезные команды:

    • git branch -d название - Удалить ветку (безопасно, Git не позволит удалить несмёрженные изменения)
    • git branch -D название - Удалить ветку силой (будь осторожен, это сотрёт несохранённые изменения)
    • git branch -m старое новое - Переименовать ветку
    • git branch -v - Посмотреть ветки и их последние коммиты
    • git branch -a - Посмотреть все ветки, включая удалённые

    Если ты используешь Git Graph в каком-нибудь редакторе (например, в VS Code или GitHub Desktop), там можешь визуально видеть, как расходятся ветки, и легче понять историю проекта. Красивая визуализация помогает новичкам разобраться в том, что происходит.

    О чём помнить при работе с ветками

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

    Основи помни: создавай ветки для каждой задачи, давай им понятные имена, часто сливай в main, чтобы не отходить слишком далеко, и не бойся конфликтов - они решаются за несколько минут.


    0 0 0 Ответить
  • GameFishG
    GameFish
    Артас в Heroes of the Storm: полный реворк с новыми квестами и станами для танка

    Обложка: Артас получил полный переверстка в Heroes of the Storm: новые квесты и станы — неужели танк наконец встал на ноги

    Blizzard переработала Артаса в Heroes of the Storm. Король-лич получил новые квесты, усиленные станы и изменения в мобильности - теперь танк может реально стоять в бою и диктовать темп.

    Это важно для игроков: Артас давно страдал от слабого контроля и проблем с маной, из-за чего его редко пикали в серьезных матчах. Реворк делает его конкурентным пиком, особенно против мобильных ассасинов. Если патч зайдет удачно, линия танков разнообразится, а тимфайты станут жестче.

    Что изменилось в базовом ките

    Артас всегда полагался на Лик Смерти для хила и урона, Синдрагосу для замедления и автоатаку с пассивкой. Теперь разработчики добавили квесты на ключевые скиллы, чтобы поощрять агрессивный стиль.

    • Q - Лик Смерти: квест на стаки для усиления хила и урона. После 150 стаков восстанавливает больше HP, чем тратит у врагов - идеально для затяжных драк.
    • W - Жажда Ледяной Скорби: теперь с элементами стана после автоатаки. Бьешь АА, жмешь W - враг трясется, не уйдет.
    • E - Синдрагоса: замедление на башни и крипов усилено, плюс новая механика обездвиживания на поздних уровнях.

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

    Новые таланты и героики

    Таланты перераспределили под реворк. Ранние пики фокусируются на выживаемости и контроле, поздние - на ультимейтах.

    Уровень Ключевой талант Эффект
    4 Пустоши Меньше маны на Q, больше фарма
    10 Войско Мертвых Повторяется 5-8 раз для живучести
    13 Кайт-контр Защита от замедлений перед станом
    20 Легион Раскола Мини-Синдрагоса с обездвиживанием

    Героики: Войско Мертвых дает неубиваемость в тимфайтах, а альтернативный ульт усиливает станы. Выбор зависит от компа - против хейла или дайва.

    Почему Артас встанет на ноги

    Раньше Артаса легко кайтить: слабая мобильность, мана кончалась быстро, без 10 уровня бесполезен. Реворк фиксит это - станы держат цели, квесты дают скейлинг, таланты снимают слабости.

    Контрится body-блоками и CC, как Джайна или Рейнор. В умелых руках разрывает фланги, фармит линии и тащит objectives. Для танкоплейеров - шанс вернуться в мету, где нужен инициатор с хилом.

    Пока детали талантов не все раскрыты, ждем тестов на птв. Если баланс зайдет, Артас вернется в пик-листы лиги и быстрых.

    Перспективы для меты

    Реворк меняет драфты: меньше чистых дайверов, больше фокуса на анти-контроль. Игроки получат инструмент против мобильных героев - Грифф, Рейнор с палачом теперь под угрозой.

    Тестировать стоит на кастомках: комбо АА + W + E держит цели, ульты переворачивают файты. Если мана и кулдауны сбалансируют, танк станет must-pick’ом.


    0 0 0 Ответить
  • kirilljsxK
    kirilljsx
    Яндекс.Метрика раскрывает LTV из органики: отчёты для ROI в B2B SaaS

    В B2B SaaS органика из Яндекса часто даёт лиды с высоким LTV, но без правильной аналитики ты не поймёшь, окупается ли трафик или сливает бюджет на тестовые аккаунты. Стандартные отчёты Метрики агрегируют данные, скрывая реальную ценность: сколько клиент из поиска заплатит за год. Настройка через Logs API и кастомные отчёты позволяет вычислить LTV по когортам из органики, сравнить с CPA и поднять ROI на 30-50%.

    Почему LTV из органики - ключ к масштабу в SaaS

    В B2B клиенты из поиска реже отваливаются: retention 40-60% на 3 месяц против 20% из рекламы. Но Метрика по умолчанию не склеивает визиты с покупками. Ты видишь трафик, но не средний доход на юзера (ARPU) за lifetime.

    Проблема: CAC из органики нулевой на старте, но без LTV ты не знаешь, сколько вливать в контент и SEO. В моём кейсе для CRM-платформы органика дала 25% лидов, но LTV вырос с 15k до 45k руб после анализа когорт.

    Решение - Logs API: сырые данные по пользователям, действиям и revenue. Оттуда строим когорты по ym:s:organic и считаем LTV = ARPU * Lifespan * Маржа.

    Настройка отчётов в Метрике для LTV

    1. Выгрузка данных через Logs API
      Подключи API ключ в Management -> API. Запроси логи по:

      • ym:pv:SearchPhrase (органика)
      • ym:s:UserID (unique ID)
      • ecommerce события (покупки, подписки)
      • revenue из goals.

      Пример запроса (Python с requests):

      import requests
      url = 'https://api-metrica.yandex.net/logs/v1/export'
      params = {'counterId': 'YOUR_COUNTER', 'dimensions': 'ym:s:clientID,ym:s:date,ym:s:searchPhrase,ym:s:revenue'}
      response = requests.get(url, params=params)
      
    2. Построение когорт
      Группируй по первому визиту из органики (cohort_month). Считай:

      • Retention: % возвратов по месяцам.
      • Revenue per cohort: сумма выручки / кол-во юзеров.

      Таблица примера когорты (B2B SaaS, руб.):

      Когорта Мес 0 Мес 1 Мес 2 Мес 3 LTV (маржа 60%)
      Янв 5000 12000 20000 15000 42k
      Фев 4500 11000 18000 14000 38k

      Retention: 55% на 1 мес, LTV = 70k руб/клиент.

    3. Кастомный дашборд в Метрике

      • В Workspaces создай отчёт: сегмент Органика + метрика Revenue by User.
      • Добавь pLTV виджеты: EVENT_LTV_0_20 для топ-20% платящих из поиска.
      • Фильтр: UTM none + SearchPhrase. Сравни ROI: LTV/CAC > 3x.

    Код для LTV в Pandas (из логов):

    cohort_data['cohort'] = pd.to_datetime(cohort_data['first_visit'])
    cohort_data['cohort_month_offset'] = (pd.to_datetime(cohort_data['visit_date']) - cohort_data['cohort']).dt.days // 30
    ltv = cohort_data.groupby(['cohort', 'cohort_month_offset'])['revenue'].sum().reset_index()
    

    Это даёт реальный LTV: 25-50k руб на клиента из органики в SaaS против 10k из директа.

    Подводные камни для российского рынка

    Метрика идеальна для РФ: 80% трафика из Яндекса, склейка по clientID работает с Яндекс ID. Плюс: бесплатная, данные в реал-тайм. Минусы:

    • Не ловит оффлайн-конверсии без RetailCRM интеграции.
    • Logs API лимит 1M строк/день - для больших счётчиков BigQuery.
    • Органика не склеивается с Директом идеально, проверяй double-count.

    Для B2B: фокусируйся на event-based goals (trial_start, subscription), а не revenue - SaaS циклы длинные (3-6 мес).

    В кейсе: после настройки ROI органики подскочил с 1.2x до 4.5x, перераспределили 40% бюджета с рекламы на SEO.

    Итог: масштабируй органику по LTV

    Настроив LTV в Метрике, ты перестанешь гадать и будешь лить в SEO столько, сколько окупается. Ключевой инсайт: органика в B2B даёт LTV в 2-3 раза выше, если retention >50%.

    А как вы считаете LTV из органики? Через Метрику, GA4 или самопис? Делитесь кейсами в комментах - разберём, что работает в 2026.


    0 0 0 Ответить
  • kirilljsxK
    kirilljsx
    Массовая генерация SEO-страниц под ключ

    Привет, форум 😊 !

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


    Что это и кому нужно

    Ручное написание статей не масштабируется. При семантическом ядре в 3 000-10 000 запросов нанять копирайтеров и получить результат за разумные деньги попросту нереально. Именно здесь работает массовая генерация: вы присылаете список ключевых запросов/тем - я возвращаю готовые страницы с HTML-текстом, meta-title, meta-description и правильной структурой заголовков H1–H3.

    Типичные задачи, под которые заказывают:

    • Интернет-магазины - наполнение карточек товаров и страниц категорий
    • B2B-каталоги - описания номенклатуры, технические страницы
    • Информационные порталы - охват низкочастотной семантики
    • Сайты - создание тематических посадочных страниц

    Минимальный заказ - от 50 статей. Максимальная скорость - до 5 000 страниц в сутки.


    Два формата работы

    Формат 1: у вас есть семантика

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

    Формат 2: семантики нет - подберу сам

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


    Что входит в каждую страницу

    Каждый сгенерированный материал содержит:

    • Уникальный HTML-текст под конкретный запрос
    • Проработанный meta-title и meta-description
    • Правильную структуру заголовков H1 → H3
    • Семантически корректный контент под Яндекс и Google
    • Готовые JSON-файлы для массовой загрузки на сайт

    Тарифы

    Объём Цена за статью Итого
    100 статей 90 ₽ от 9 000 ₽
    250 статей 80 ₽ от 20 000 ₽
    500 статей 70 ₽ от 35 000 ₽
    1 000 статей 50 ₽ от 50 000 ₽

    Это в 10-20 раз дешевле ручного написания при сопоставимом качестве и объеме для SEO-задач.

    Тарифы одинаковые для обоих форматов - с подбором семантики и без. Минимальный заказ - от 50 статей.


    Как устроен процесс

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

    1. Получаю файл с тематиками
    Вы присылаете Excel со списком тем или ключевых запросов - это все, что нужно для старта.

    2. Настройка шаблонов
    Под вашу тематику настраиваю промпты и правила генерации: структуру статьи, тон, длину, мета-теги.

    3. Генерация контента
    Запускаю массовую генерацию через Qwen 3.5 Flash - модель обеспечивает высокую скорость и стабильное качество на больших объемах. При необходимости подключаю другую модель или нестандартную логику - обсуждается индивидуально.

    4. Упаковка в JSON-архив
    Готовый контент упаковывается в JSON-файлы и передается архивом - готово к загрузке на любую CMS или площадку.

    5. Передача клиенту
    Получаете архив и загружаете сами - как, куда и когда удобно. Никаких лишних зависимостей.

    Готовый контент - через 24–48 часов после старта.


    Технические детали

    Поддерживаемые CMS и форматы:
    MODX, WordPress, 1С-Битрикс и любые платформы с массовым импортом. Выгрузка в JSON или CSV - на выбор.

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

    Опыт в нишах:
    Промышленность, интернет-магазины, B2B-каталоги, информационные порталы. Понимаю специфику коммерческих и информационных запросов.


    Почему это работает для SEO

    Массовая генерация особенно эффективна там, где ручное написание текстов уже не окупается. При грамотной настройке можно одновременно:

    • Повышать видимость сайта по низкочастотной семантике
    • Ускорять индексацию за счет регулярного добавления новых страниц
    • Улучшать поведенческие метрики - контент отвечает на конкретный запрос пользователя
    • Обогнать конкурентов по охвату семантики без раздувания редакционного бюджета

    Как сделать заказ

    Напишите на почту kdvoryaninov@yandex.ru и укажите:

    1. Тематику или нишу вашего сайта
    2. Нужный объем статей (минимум 50)
    3. Есть ли готовое семантическое ядро или нужен подбор тематик
    4. Предпочтительный формат выгрузки (JSON или CSV)

    Отвечаю в течение рабочего дня. После согласования деталей приступаю к работе в течение 24 часов.

    Мой профиль на форуме: @kirilljsx


    Готов ответить на вопросы в комментариях.


    0 0 0 Ответить
  • kirilljsxK
    kirilljsx
    Что делает и для чего нужен Record<> в TypeScript?

    Давайте разбираться Record<Keys, Type> - это встроенный utility type в TypeScript, который создает объектный тип-словарь с фиксированным типом ключей и фиксированным типом значений.

    Для не понимающих:

    Record<ТипКлюча, ТипЗначения>
    

    Объекты все помнят? Ключ -> значение.

    Под капотом конечно страшненький отображаемый тип:

    type Record<K extends keyof any, T> = {
      [P in K]: T;
    };
    

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

    Базовое использование:

    const scores: Record<string, number> = {
      alex: 3,
      max: 8,
      kate: 5,
    };
    

    Обратим внимание на сие простой объект с использованием Record<> теперь картина стала понятнее? У нас есть объект как всегда ключ -> значение, а Record сразу добавляет к ним типизацию.

    Более приемлемый вариант

    Представим что у нас есть где-то типы, пусть будут как всегда User, то тогда наш объект должен выглядеть так:

    type User = {
      name: string;
      age: number;
    }
    export type { User }
    

    Как обычно типы экспортируем

    Далее их импортируем, и вот смотрите как красиво получается типизировать наш объект через Record

    import type { User } from "@/types"
    
    const scores: Record<string, User> = {
      user1: { name: 'Alex', age: 30 },
      user2: { name: 'Max',  age: 25 },
      user3: { name: 'Kate', age: 28 },
    }
    

    А без Record можно?

    А то, конечно можно, но вопрос надо ли оно нам?
    Вот пару способов:

    // 1. Индексная сигнатура - ну классика
    const scores: { [key: string]: number } = {
      alex: 3,
      max: 8,
      kate: 5,
    };
    
    // 2. Через interface
    interface Scores {
      [key: string]: number;
    }
    const scores: Scores = {
      alex: 3,
      max: 8,
      kate: 5,
    };
    

    Думаю у многих будет один и тот же вопрос, а моги я сделать так:

    interface Scores {
      name: number;
    }
    
    const scores: Scores = {
      name: 42, // Если тут будет name, а не alex - то сработает
      max: 3,
      kate: 8,
    };
    

    Нет, не можете - потому что name: number описывает конкретное поле с именем name. TypeScript не разрешит добавить max или kate, так как их нет в интерфейсе. Для динамических ключей нужна индексная сигнатура [key: string].

    По сути Record - это короткая запись для индексной сигнатуры. Квадратные скобки [key: string] говорят TS: “Эй мужик, ключи могут быть любыми строками”, а тип после двоеточия задает тип значений. Record<string, number> - это то же самое что { [key: string]: number }, только читается лучше и легче.

    Главное разобраться в самом начале - потом уже будете использовать по наитию.


    0 0 0 Ответить
Популярные темы:

  • Критическая уязвимость в React.js Next.js (CVE-2025-55182, CVE-2025-66478): Как защитить свой сайт
    AladdinA
    Aladdin
    7
    12
    1.4k

  • Полный гайд по работе с NodeBB CLI
    D
    DeepSeeker
    6
    3
    206

  • for или foreach в javascript: в каких случаях что использовать
    D
    DeepSeeker
    5
    2
    198

  • Подготовка к собесам фронтенд
    Dastan SalmurzaevD
    Dastan Salmurzaev
    5
    5
    249

  • Передача типов в TypeScript в под функции
    kirilljsxK
    kirilljsx
    4
    5
    270

  • Исчерпывающее руководство по конфигурации Nginx
    undefined
    4
    1
    305

  • Проверка стала проще с Zod: как обеспечить точность и качество форм
    kirilljsxK
    kirilljsx
    3
    8
    1.2k

  • Bruno - новый клиент для API (Замена PostMan Insomnia)
    ManulM
    Manul
    3
    2
    1.9k

  • Vue.js и React — необычное сравнение
    D
    DeepSeeker
    3
    10
    1.2k

  • Оптимизация React js приложений. Использование функции debounde()
    ManulM
    Manul
    3
    5
    609

  • Провайдеры в Nest JS - 1.3
    undefined
    3
    1
    385

  • Полный гайд по команде LFTP: Работа с локальными и удалёнными серверами
    undefined
    3
    1
    860

Пользователи в Сети:

Статистика:

37

В сети

367

Пользователи

2.0k

Темы

3.0k

Сообщения

Категории

  • Главная
  • Новости
  • Фронтенд
  • Бекенд
  • Языки программирования

Контакты

  • Сотрудничество
  • info@exlends.com

© 2024 - 2026 ExLends, Inc. Все права защищены.

Политика конфиденциальности
  • Войти

  • Нет учётной записи? Зарегистрироваться

  • Войдите или зарегистрируйтесь для поиска.
  • Первое сообщение
    Последнее сообщение
0
  • Лента
  • Категории
  • Последние
  • Метки
  • Популярные
  • Пользователи
  • Группы