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

  • en
    Marketing
    Humor
    News
    AI
    Programming languages
    Frontend
    GameDev

  • Блоги

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

  • Все категории
  • 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 Ответить
  • kirilljsxK
    kirilljsx
    Ошибка ts 1484

    Наверное многие кто только вкатываются в TypeScript встречаются с такой типовой ошибкой как ts 1484:
    User is a type and must be imported using a type-only import when verbatimModuleSyntax is enabled. (ts 1484)

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

    Всего скорее у вас есть файл entities/types.ts где есть interface или type:

    interface User {
      name: string;
      age: number;
    }
    
    export { User }
    

    А импортируете вы его так:

    import { User } from "../entities/types.ts"
    

    И вот как раз тут и кроется вся суть проблемы, TS ожидает что мы импортируем именно типы, а для этого после ключевого слова import необходимо добавить type:

    import type { User } from "../entities/types.ts"
    

    После того как мы подставим ключевое слово type ошибка должна пропасть.

    Также помимо импорта, есть более строгий тип экспорта:

    export type { User }
    

    он запрещает использовать импортированное как значение. Полезно при включенном isolatedModules (Vite, esbuild).


    0 0 0 Ответить
  • hannadevH
    hannadev
    Домены .ru и .рф теперь только через Госуслуги: что делать разработчику

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

    Что изменится

    Раньше ты мог зайти к любому регистратору (REG.RU, Beget, Timeweb), ввести паспортные данные «на глаз» и получить домен. Никто ничего не проверял по-настоящему. С 1 сентября 2026 такая схема перестанет работать.

    Теперь регистратор обязан сверить твои данные через ЕСИА — ту самую систему, которая отвечает за Госуслуги. Без подтверждённой учётной записи домен не зарегистрируют и не продлят.

    Кого это касается

    Всех, у кого есть сайты на .ru, .рф или .su:

    • Личные блоги и портфолио
    • Сайты компаний и интернет-магазины
    • Пет-проекты и стартапы
    • Даже если домен просто лежит и никуда не ведёт

    Что нужно сделать прямо сейчас

    1. Проверь свою учётную запись на Госуслугах. Она должна быть подтверждённой — с паспортными данными. Если у тебя упрощёнка (по номеру телефона или email) — нужно будет подтвердить полную.

    2. Узнай, на кого оформлен домен. Если ты когда-то регистрировал домен на друга, знакомого или бывшего сотрудника — устройство может быть сложным. Переоформить домен на себя лучше до сентября.

    3. Проверь дату истечения домена. Если домен истекает в сентябре-октябре 2026 и у тебя нет подтверждённой записи на Госуслугах — продлить его не дадут. Домен уйдёт в парковку, а потом освободится.

    4. Если домен у иностранного регистратора — его нужно перенести к российскому до сентября. Зарубежные регистраторы больше не смогут работать с российскими доменными зонами.

    Почему это важно для разработчика

    Казалось бы — административная история, при чём тут разработка? А при том, что:

    • Твой пет-проект на .ru может просто исчезнуть, если не продлить домен вовремя
    • Клиентский сайт может лечь, а ты будешь разбираться, почему домен «вдруг» не продлился
    • Если сайт работает на зарубежном DNS или регистраторе — его надо переносить

    Лучше потратить 15 минут сейчас, чем разгребать аврал в сентябре.

    Что в итоге

    Новые правила — это не страшно, если подготовиться заранее. Проверь Госуслуги, проверь свои домены, проверь даты. Всё решается за один вечер.

    А если что-то пойдёт не так — пиши в комменты, разберёмся вместе.


    0 0 0 Ответить
  • AladdinA
    Aladdin
    Zapret2 - способ обхода DPI восстановив работу youtube

    Zapret2 — это мощное open-source решение для противодействия DPI, которое помогает обойти замедление и блокировки сайтов https, в том числе YouTube и Discord. Принцип работы такой: Deep Packet Inspection (система глубокого анализа пакетов провайдера) сканирует первые пакеты соединения в поисках маркеров блокировки, например SNI в TLS-приветствии.

    Windows-версия работает через WinDivert — надёжный драйвер для перехвата сетевого трафика, являющийся аналогом iptables из Linux. Утилита абсолютно автономна и не требует сторонних серверов, поддерживая все ключевые протоколы — TCP, UDP, QUIC до 15 версии, MTProto и даже WireGuard. При этом программа гибко настраивается под любые сетевые нужды — от простого веб-сёрфинга до онлайн-игр. Встроенная поддержка Lua позволяет создавать неограниченное количество собственных пресетов и стратегий обхода с минимальными усилиями.

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

    Скачать последнюю версию (Эта утилита работает в windows) :

    • zapret2-youtube-discord-v1.0.193.7z
    • zapret2-youtube-discord-v1.0.200.7z
    • или в разделе релизов github

    Eсли используете Linux то вам подойдет эти релизы (инструкция) :

    • zapret2-v0.9.5.1.zip
    • zapret2-v0.9.5.2.zip
    • разделе релизов github

    Первый запуск

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

    Нажмите правой кнопкой мыши на файл zapret-console.bat и выберите «Запуск от имени администратора».

    Откроется окно со списком из более чем 70 доступных пресетов, разделённых на категории.

    Выбор пресета

    Перед вами появится список, в котором нужно будет ввести его номер. Вот основные категории, на которые стоит обратить внимание:

    🗺️ Как устроены названия пресетов

    • Метод обхода (multisplit, syndata, fakedsplit): Это основа, определяющая алгоритм. Например, multisplit дробит пакеты на части, чтобы сбить с толку DPI-системы провайдера, а fakedisorder отправляет пакеты в «неправильном» порядке. Названия вроде “Send + Syndata” — это комбинации таких методов.

    • Техника обмана (fake, stun, hostfakesplit): Эта часть названия говорит о том, как именно создается «ложный» трафик. Pежимы FAKE TLS имитируют зашифрованное соединение, чтобы обмануть системы анализа трафика, отправляя подставные пакеты с данными TLS. stun использует маскировку под служебный трафик VoIP.

    • Область применения (game filter, ALL TCP & UDP: Показывает, какой трафик будет обрабатываться. Фильтры для игр (game filter) более щадящие, чтобы не создавать задержки, а ALL UDP может влиять на видеозвонки, голосовые чаты в играх и некоторые онлайн-игры.

    Продвинутое управление через service.bat

    Это ваш «командный центр» для запуски утилиты как службы. При запуске service.bat от имени администратора вы увидите меню со следующими пунктами:

    • Установить Zapret как службу (Скрытый автозапуск): Программа будет запускаться вместе с Windows без лишних окон.

    • Удалить службу: Отключит автозапуск и остановит программу.

    • 4, 5, 6, 7, 8: Эти пункты позволяют запускать или останавливать Zapret в ручном режиме с возможностью выбора пресета.

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

    • Очистка кэша Discord и сброс настроек: Полезно, если Discord работает нестабильно.

    • Диагностика: Поможет найти конфликты с другими программами.

    💡 Полезные советы

    Не бойтесь экспериментировать: Если что-то пошло не так, просто перезапустите нужный ***.bat файл и выберите другой вариант.

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

    Если интернет всё равно тормозит, попробуйте запустить zapret-console.bat и выбрать другой пресет. В случае серьёзных проблем можно перезагрузить компьютер — это гарантированно остановит все процессы Zapret.


    2 0 1 Ответить
  • kirilljsxK
    kirilljsx
    Яндекс.Директ 2026: Smart Bidding с ИИ снижает CPA на 25% без роста бюджетов

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

    Как это работает на практике? ИИ смотрит на пользователя: время суток, устройство, гео, историю поведения, активность конкурентов. Вероятность покупки 15% — ставка 80 руб, 65% — подскакивает до 250 руб. Результат: CPC падает на 15-40%, конверсий растет при том же бюджете. Виртуальные конверсии через инструменты вроде Rivox AI ускоряют обучение модели в 3 раза, давая больше данных о onsite-поведении.

    Ключевые фичи Smart Bidding 2026

    • Dynamic Bid Engine (DBE): Автоматическая корректировка ставок по прогнозу конверсии. Включили на часы пик (18:00-22:00) с порогом 0,75% — ставки растут только там, где реально жди лид.
    • Contextual Relevance Layer (CRL): Оценивает, насколько объявление в теме запроса и устройства. Низкая релевантность — показ отменяется.
    • Regional Demand Forecast (RDF): Прогнозирует спрос по регионам, распределяя бюджет умно, без слива в пустые зоны.
    Фича Эффект на CPA Время обучения
    DBE -20-30% 5-7 дней с виртуальными конверсиями
    CRL +CTR 15% 3-5 дней
    RDF Оптимизация гео Немедленно

    Реальный кейс: B2B с CPA-оптимизацией

    Представьте кампанию на софт для бизнеса. Бюджет 500к руб/мес. До Smart: CPA 2500 руб, 200 лидов. Включили ‘Максимум целевых действий’ с оплатой за конверсии (заявка с сайта). Передавали CRM-сигналы: качество лида, LTV. ИИ обучился за неделю, CPA упал до 1800 руб (-28%), лидов +15% без допрасходов. Семантика: заменили высокочастотники на низкочастотные + регионы, CTR вырос на 22%. Ретаргетинг на теплых — еще -10% к стоимости.

    Пример настройки в Директе:

    1. Выберите стратегию ‘Максимум конверсий’ с целевым CPA.
    2. Свяжите Метрику: передавайте события ‘lead_quality_high’ с атрибутами.
    3. Добавьте виртуальные конверсии: промпт для ИИ-генератора данных — “Сгенерируй 1000 виртуальных сессий: устройство mobile, гео Москва, время 20:00, путь /price -> /callback, конверсия да/нет с вероятностью 60% на основе LTV >50к”.
    4. Тестируйте 7 дней, мониторьте дашборд: CPA, ROMI, конверсия по устройствам.
    Метрика До После Дельта
    CPA 2500 руб 1800 руб -28%
    CPC 45 руб 32 руб -29%
    Лиды 200 230 +15%
    ROI 180% 250% +70%

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

    Идеально для РФ: Яндекс знает нашу аудиторию лучше всех — VK, гео, мобильный трафик. Честный отзыв: топ для e-com и B2B с 100к+ руб бюджетом. Но если данных мало (новая кампания), модель слепнет — CPA взлетит на 20-30% первые дни. Корми алгоритм чистыми сигналами: без мусора в семантике, с минус-словами и ретаргетингом. Плохая посадка? ИИ сольет бюджет филигранно. Для SMB с бюджетом <50к — ручные стратегии надежнее на старте.

    Тестируйте на малом: 10% бюджета на Smart, сравните с контролем. ROI вырастет, если воронка заточена. А вы уже пробовали Smart Bidding в 2026? Какой CPA выбили и на чем споткнулись? Делитесь в коммах, разберем ваши кейсы!


    0 0 0 Ответить
  • GameFishG
    GameFish
    Heroes of the Storm получила патч: рворки героев и шанс на возрождение

    72c749d9-4e3e-47a6-b418-5fccf1fe7dbb-image.jpeg

    Blizzard неожиданно активировала поддержку Heroes of the Storm

    После долгих месяцев затишья Blizzard вернула внимание к Heroes of the Storm серией патчей, которые перестраивают механики популярных героев и вносят системные изменения. Последний крупный патч вышел в конце апреля и принёс рворки Артаса, обновления карт и баланса, что говорит о том, что игра не просто живёт, а медленно восстанавливается после периода запустения.

    Дело не в одном патче - с февраля по апрель 2026 года вышло несколько крупных обновлений с серьёзными изменениями в геймплее. Это может быть знаком того, что Blizzard либо готовит что-то большое, либо просто захотела вернуть игре актуальность перед новым сезоном. Вопрос - хватит ли этого, чтобы вернуть игроков обратно?

    Что конкретно поменялось в героях

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

    Muradin получил полный переделок способности Storm Bolt. Вместо старой механики теперь герой получает стеки за атаки по оглушённым и замедленным врагам - станы дают 2 стека, слоу дают 1. При 50 стеках способность начинает пронизывать сквозь врагов, а атаки Мурадина снижают перезарядку Storm Bolt на 0.5 секунды. При 200 стеках (мифическая награда) диапазон увеличивается на 50%, ширина на 100%, и способность пронизывает всех врагов. Плюс теперь она наносит на 500% больше урона структурам и миньонам.

    Deathwing получил баф в январе-феврале, но в февральском патче его немного утянули назад - увеличили перезарядку Incinerate с 6 до 7 секунд, уменьшили длительность Spell Armor в таланте Death Drop.

    Murky стал живучее: щит от Spawn Egg теперь длится 15 секунд вместо 10, талант Egg Shell увеличивает щит до 50% здоровья Мёрки (было 35%), и щит теперь вечный, пока не разрушат.

    Tyrande и другие герои также получили корректировки, но детали в открытом доступе менее понятны.

    Системные и карточные обновления

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

    • Структуры на картах теперь дают защиту героям, находящимся в их радиусе - 35 броне на 4 секунды при получении урона
    • Карты возвращаются в ротацию: в сентябре вернулись Blackheart’s Bay и Hanamura Temple
    • В апреле вернулись Garden of Terror в кастомные игры и Haunted Mines в быстрые матчи
    • Механика суммонов была переработана - теперь можно спавнить множество миньонов по требованию

    Это свидетельствует о том, что Blizzard меняет не только числа урона, но и саму структуру геймплея.

    Вернётся ли игра или это просто поддержка

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

    • Активное комьюнити и стримеры, которые буду играть
    • Реклама и привлечение новых игроков
    • Конкурентная сцена или турниры
    • Регулярные события, пропасы боевого пропуска

    Что известно: Blizzard точно не забросила проект окончательно, патчи выходят регулярно, баланс пересматривается. Но вернётся ли HotS на уровень популярности MOBA типа League of Legends или Dota 2 - непредсказуемо. Скорее всего, игра остаётся в статусе “поддерживаемый проект для любителей Warcraft/StarCraft/Diablo”.

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


    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
    194

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

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

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

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

  • Проверка стала проще с 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
    595

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

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

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

Статистика:

53

В сети

363

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

2.0k

Темы

3.0k

Сообщения

Категории

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

Контакты

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

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

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

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

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