{
  "$schema": "./glossary.schema.json",
  "schema_version": 2,
  "entries": [
    {
      "id": "бинарный-поиск",
      "title": "Бинарный поиск",
      "kind": "term",
      "summary": {
        "ru": "Поиск в отсортированном массиве за O(log n). Каждый шаг делит область поиска пополам.",
        "en": "A search in a sorted array in O(log n). Every step halves the range being searched."
      },
      "body": {
        "ru": "Главная ловушка — данные обязаны быть отсортированы: на неотсортированном списке поиск не падает, а тихо возвращает неверный ответ. Ради одной проверки сортировать невыгодно: O(n log n) на сортировку дороже обычного оператора in за O(n), выигрыш появляется, когда по одному и тому же массиву ищут многократно. Писать его руками обычно незачем — bisect_left вернёт позицию вставки, останется сравнить элемент на этой позиции с искомым; переполнения (lo + hi), из-за которого этот алгоритм годами ломался в C и Java, в Python не бывает.",
        "en": "The core trap is that the data must already be sorted: on an unsorted list the search does not raise anything, it just quietly returns a wrong answer. Sorting for the sake of one lookup does not pay off — O(n log n) costs more than a plain in check at O(n); the win appears when the same array is searched many times. You rarely need to hand-roll it: bisect_left gives the insertion point and you only compare the item sitting there with the target, and the (lo + hi) overflow that broke this algorithm for years in C and Java cannot happen in Python."
      },
      "syntax": "def binary_search(arr, target):",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/bisect.html",
      "version": "",
      "section": "Алгоритмы и структуры данных",
      "subcat": "поиск",
      "color_group": "op",
      "aliases": [
        "двоичный поиск",
        "поиск делением пополам",
        "поиск в отсортированном массиве"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def binary_search(arr, target):",
        "lo, hi = 0, len(arr) - 1",
        "while lo <= hi:",
        "mid = (lo + hi) // 2",
        "if arr[mid] == target:",
        "return mid",
        "elif arr[mid] < target:",
        "lo = mid + 1",
        "else:",
        "hi = mid - 1",
        "return -1",
        "print(binary_search([1,3,5,7,9,11], 7))  # → 3",
        "print(binary_search([1,3,5,7,9,11], 6))  # → -1",
        "# bisect — стандартная библиотека",
        "import bisect",
        "arr = [1,3,5,7,9]",
        "print(bisect.bisect_left(arr, 5))  # → 2 (позиция для вставки)",
        "print(bisect.bisect_right(arr, 5))  # → 3",
        "# Рекурсивный вариант",
        "def bs_rec(arr, target, lo=0, hi=None):",
        "if hi is None: hi = len(arr)-1",
        "if lo > hi: return -1",
        "mid = (lo+hi)//2",
        "if arr[mid] == target: return mid",
        "elif arr[mid] < target: return bs_rec(arr, target, mid+1, hi)",
        "else: return bs_rec(arr, target, lo, mid-1)",
        "print(bs_rec([2,4,6,8,10], 6))  # → 2",
        "# Поиск первого вхождения дубликата",
        "def first_occurrence(arr, target):",
        "lo, hi, result = 0, len(arr)-1, -1",
        "while lo <= hi:",
        "mid = (lo+hi)//2",
        "if arr[mid] == target:",
        "result = mid; hi = mid-1",
        "elif arr[mid] < target: lo = mid+1",
        "else: hi = mid-1",
        "return result",
        "print(first_occurrence([1,2,2,2,3], 2))  # → 1",
        "# Бинарный поиск ответа",
        "def min_pages(pages, students):",
        "lo, hi = max(pages), sum(pages)",
        "def can_split(limit):",
        "count, cur = 1, 0",
        "for p in pages:",
        "if cur + p > limit: count+=1; cur=0",
        "cur += p",
        "return count <= students",
        "while lo < hi:",
        "mid = (lo+hi)//2",
        "if can_split(mid): hi=mid",
        "else: lo=mid+1",
        "return lo",
        "print(min_pages([10,20,30,40], 2))  # → 60",
        "# bisect.insort — вставка в отсортированный список",
        "import bisect",
        "arr2 = [1,3,5,9]",
        "bisect.insort(arr2, 6)",
        "print(arr2)  # → [1,3,5,6,9]"
      ],
      "related": [
        "бинарный-поиск-через-bisect",
        "bisect.bisect_left",
        "два-указателя-two-pointers"
      ],
      "related_errors": []
    },
    {
      "id": "битовые-операции",
      "title": "Битовые операции",
      "kind": "term",
      "summary": {
        "ru": "& (AND), | (OR), ^ (XOR), ~ (NOT), << (сдвиг влево), >> (сдвиг вправо). Используются для масок, оптимизации, криптографии.",
        "en": "& (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (right shift). Used for masks, optimization and cryptography."
      },
      "body": {
        "ru": "Целые в Python не ограничены по разрядности: << никогда не переполняется, >> сохраняет знак, а ~x — это ровно -x-1 (~5 == -6), потому что отрицательные ведут себя как бесконечное дополнение до двух. Битовые операторы связывают сильнее сравнений, но слабее арифметики, поэтому x > 0 & mask читается как x > (0 & mask) — в смешанных выражениях скобки обязательны. И & — не and: короткого замыкания нет, оба операнда вычисляются всегда.",
        "en": "Python ints are unbounded, so << never overflows, >> keeps the sign, and ~x is exactly -x-1 (~5 == -6) because negatives behave as an infinite two's-complement pattern. Bitwise operators bind tighter than comparisons but looser than arithmetic, so x > 0 & mask parses as x > (0 & mask) — parenthesise anything mixed. And & is not and: there is no short-circuiting, both operands are always evaluated."
      },
      "syntax": "a & b  # AND\na | b  # OR\na ^ b  # XOR\n~a     # NOT\na << n # left shift\na >> n # right shift",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bitwise-operations-on-integer-types",
      "version": "",
      "section": "Алгоритмы и структуры данных",
      "subcat": "битовые операции",
      "color_group": "op",
      "aliases": [
        "побитовое и",
        "исключающее или",
        "сдвиг битов",
        "битовая маска"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "# Базовые операции",
        "print(0b1010 & 0b1100)  # → 0b1000 = 8 (AND)",
        "print(0b1010 | 0b1100)  # → 0b1110 = 14 (OR)",
        "print(0b1010 ^ 0b1100)  # → 0b0110 = 6 (XOR)",
        "print(~0b1010)          # → -11 (NOT)",
        "print(1 << 3)           # → 8 (2^3)",
        "print(16 >> 2)          # → 4 (16/4)",
        "# Проверка чётности",
        "def is_even(n): return (n & 1) == 0",
        "print(is_even(4))  # → True",
        "print(is_even(7))  # → False",
        "# Умножение/деление на степень двойки",
        "print(5 << 2)   # → 20 (5*4)",
        "print(20 >> 2)  # → 5  (20/4)",
        "# XOR: swap без третьей переменной",
        "a, b = 5, 3",
        "a ^= b; b ^= a; a ^= b",
        "print(a, b)  # → 3 5",
        "# Маска битов",
        "def get_bit(n, i): return (n >> i) & 1",
        "def set_bit(n, i): return n | (1 << i)",
        "def clear_bit(n, i): return n & ~(1 << i)",
        "n = 0b1010",
        "print(get_bit(n, 1))    # → 1",
        "print(bin(set_bit(n, 2)))   # → 0b1110",
        "print(bin(clear_bit(n, 3))) # → 0b0010",
        "# Подсчёт единичных бит (popcount)",
        "def popcount(n): return bin(n).count('1')",
        "print(popcount(255))  # → 8",
        "print(popcount(0))    # → 0",
        "# XOR: найти уникальный элемент",
        "def find_unique(arr):",
        "result = 0",
        "for x in arr: result ^= x",
        "return result",
        "print(find_unique([2,3,2,4,3]))  # → 4"
      ],
      "related": [
        "побитовые-операторы",
        "системы-счисления",
        "int.bit_length",
        "int.bit_count"
      ],
      "related_errors": []
    },
    {
      "id": "два-указателя-two-pointers",
      "title": "Два указателя (two pointers)",
      "kind": "function",
      "summary": {
        "ru": "Два индекса движутся навстречу друг другу или в одном направлении. O(n) для задач на отсортированных массивах.",
        "en": "Two indices move towards each other or in the same direction. O(n) for problems on sorted arrays."
      },
      "body": {
        "ru": "Сходящаяся пара указателей корректна только на отсортированных данных — на произвольном списке она молча вернёт неверный ответ. Сортировать самому значит заплатить O(n log n) и потерять исходные позиции, поэтому в задачах, где нужны именно индексы, обычно выигрывает словарь за один проход. И следите за границей цикла: while lo < hi не даёт сложить элемент сам с собой, а lo <= hi это разрешает.",
        "en": "The converging variant is correct only on sorted data; hand it an unsorted list and it quietly returns a wrong answer. Sorting it yourself costs O(n log n) and destroys the original positions, so when the task asks for indices a one-pass dict usually wins. Watch the loop boundary too: while lo < hi keeps an element from being paired with itself, while lo <= hi allows it."
      },
      "syntax": "lo, hi = 0, len(arr)-1\nwhile lo < hi: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/datastructures.html",
      "version": "",
      "section": "Алгоритмы и структуры данных",
      "subcat": "техники",
      "color_group": "op",
      "aliases": [
        "метод двух индексов",
        "встречные указатели",
        "проход с двух концов"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "# Сумма двух чисел в отсортированном массиве",
        "def two_sum_sorted(arr, target):",
        "lo, hi = 0, len(arr)-1",
        "while lo < hi:",
        "s = arr[lo] + arr[hi]",
        "if s == target: return (lo, hi)",
        "elif s < target: lo += 1",
        "else: hi -= 1",
        "return None",
        "print(two_sum_sorted([1,2,3,4,6], 6))  # → (1, 3) → 2+4",
        "# Проверка палиндрома",
        "def is_palindrome(s):",
        "lo, hi = 0, len(s)-1",
        "while lo < hi:",
        "if s[lo] != s[hi]: return False",
        "lo += 1; hi -= 1",
        "return True",
        "print(is_palindrome('racecar'))  # → True",
        "print(is_palindrome('hello'))    # → False",
        "# Удаление дубликатов in-place",
        "def remove_dups(arr):",
        "if not arr: return 0",
        "slow = 0",
        "for fast in range(1, len(arr)):",
        "if arr[fast] != arr[slow]:",
        "slow += 1",
        "arr[slow] = arr[fast]",
        "return slow + 1",
        "arr = [1,1,2,3,3,4]",
        "k = remove_dups(arr)",
        "print(arr[:k])  # → [1,2,3,4]",
        "# Слияние двух отсортированных массивов",
        "def merge_sorted(a, b):",
        "i = j = 0; result = []",
        "while i < len(a) and j < len(b):",
        "if a[i] <= b[j]: result.append(a[i]); i+=1",
        "else: result.append(b[j]); j+=1",
        "result.extend(a[i:]); result.extend(b[j:])",
        "return result",
        "print(merge_sorted([1,3,5],[2,4,6]))  # → [1,2,3,4,5,6]",
        "# Контейнер с наибольшим объёмом",
        "def max_area(heights):",
        "lo, hi = 0, len(heights)-1",
        "best = 0",
        "while lo < hi:",
        "area = (hi-lo) * min(heights[lo],heights[hi])",
        "best = max(best, area)",
        "if heights[lo] < heights[hi]: lo+=1",
        "else: hi-=1",
        "return best",
        "print(max_area([1,8,6,2,5,4,8,3,7]))  # → 49"
      ],
      "related": [
        "бинарный-поиск",
        "префиксные-суммы",
        "while"
      ],
      "related_errors": [
        "IndexError"
      ]
    },
    {
      "id": "нод-нок-алгоритм-евклида",
      "title": "НОД / НОК / алгоритм Евклида",
      "kind": "term",
      "summary": {
        "ru": "НОД (GCD) — наибольший общий делитель. НОК (LCM) = a*b/GCD(a,b). Алгоритм Евклида — O(log min(a,b)).",
        "en": "GCD is the greatest common divisor. LCM = a*b/GCD(a, b). Euclid's algorithm takes O(log min(a, b))."
      },
      "body": {
        "ru": "Свою реализацию писать обычно незачем: math.gcd есть с Python 3.5 (с 3.9 принимает любое количество аргументов), math.lcm — с 3.9. НОК считайте как a // gcd(a, b) * b: деление через / даст float и на больших числах молча потеряет точность. Краевой случай — ноль: math.gcd(0, 0) равен 0, и самодельная формула НОК через деление на нём падает с ZeroDivisionError, тогда как math.lcm просто вернёт 0.",
        "en": "You rarely need your own version: math.gcd has been there since Python 3.5 (variadic since 3.9), and math.lcm was added in 3.9. Compute the LCM as a // gcd(a, b) * b — using / turns it into a float and silently loses precision on big integers. Watch the zero case: math.gcd(0, 0) is 0, so a hand-rolled LCM that divides by it raises ZeroDivisionError, while math.lcm simply returns 0."
      },
      "syntax": "def gcd(a, b):\n    while b: a, b = b, a%b\n    return a",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.gcd",
      "version": "3.5",
      "section": "Алгоритмы и структуры данных",
      "subcat": "математика",
      "color_group": "op",
      "aliases": [
        "наибольший общий делитель",
        "наименьшее общее кратное",
        "сократить дробь"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "# Алгоритм Евклида",
        "def gcd(a, b):",
        "while b:",
        "a, b = b, a % b",
        "return a",
        "print(gcd(48, 18))  # → 6",
        "print(gcd(100, 75)) # → 25",
        "# Рекурсивный",
        "def gcd_rec(a, b):",
        "if b == 0: return a",
        "return gcd_rec(b, a % b)",
        "print(gcd_rec(56, 98))  # → 14",
        "# НОК",
        "def lcm(a, b):",
        "return a * b // gcd(a, b)",
        "print(lcm(4, 6))   # → 12",
        "print(lcm(12, 18)) # → 36",
        "# Стандартная библиотека",
        "import math",
        "print(math.gcd(48, 36))    # → 12",
        "print(math.lcm(4, 6))      # → 12",
        "print(math.gcd(0, 5))      # → 5",
        "print(math.gcd(12, 18, 24))  # → 6 (Python 3.9+)",
        "# Расширенный алгоритм Евклида",
        "def extended_gcd(a, b):",
        "if b == 0: return a, 1, 0",
        "g, x, y = extended_gcd(b, a%b)",
        "return g, y, x - (a//b)*y",
        "print(extended_gcd(48, 18))  # → (6, -1, 3) → 48*(-1)+18*3=6",
        "# Несколько чисел",
        "from functools import reduce",
        "import math",
        "nums = [12, 18, 24, 36]",
        "print(reduce(math.gcd, nums))  # → 6",
        "print(reduce(math.lcm, nums))  # → 36"
      ],
      "related": [
        "math.gcd",
        "math.lcm",
        "остаток",
        "рекурсия"
      ],
      "related_errors": []
    },
    {
      "id": "очередь-queue",
      "title": "Очередь (queue)",
      "kind": "function",
      "summary": {
        "ru": "FIFO — первый вошёл, первый вышел. Реализуется через deque. Применения: BFS, задачи планирования.",
        "en": "FIFO — first in, first out. Implemented with a deque. Uses: BFS and scheduling problems."
      },
      "body": {
        "ru": "Причина брать deque, а не список, одна: list.pop(0) сдвигает все оставшиеся элементы и стоит O(n), превращая линейный BFS в квадратичный, тогда как deque.popleft() — O(1). Расплата — случайный доступ: обращение к середине deque по индексу тоже O(n), так что для произвольных позиций он не годится. А для передачи данных между потоками нужен не deque, а queue.Queue — с блокировкой и ожиданием.",
        "en": "The one reason to reach for deque instead of a list is list.pop(0): it shifts every remaining element, costs O(n) and turns a linear BFS into a quadratic one, while deque.popleft() is O(1). The trade-off is random access — indexing into the middle of a deque is also O(n), so it is a poor fit when you need arbitrary positions. For handing items between threads use queue.Queue, which adds blocking and waiting; deque has neither."
      },
      "syntax": "from collections import deque\nqueue = deque()\nqueue.append(x)   # enqueue\nqueue.popleft()   # dequeue",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.deque",
      "version": "",
      "section": "Алгоритмы и структуры данных",
      "subcat": "структуры",
      "color_group": "op",
      "aliases": [
        "первый пришёл первый вышел",
        "двусторонняя очередь",
        "дек"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "from collections import deque",
        "queue = deque()",
        "queue.append('a')",
        "queue.append('b')",
        "queue.append('c')",
        "print(queue.popleft())  # → a",
        "print(queue.popleft())  # → b",
        "print(queue)  # → deque(['c'])",
        "# BFS обход графа",
        "def bfs(graph, start):",
        "visited = set([start])",
        "queue = deque([start])",
        "order = []",
        "while queue:",
        "node = queue.popleft()",
        "order.append(node)",
        "for neighbor in graph.get(node, []):",
        "if neighbor not in visited:",
        "visited.add(neighbor)",
        "queue.append(neighbor)",
        "return order",
        "g = {1:[2,3],2:[4],3:[4],4:[]}",
        "print(bfs(g, 1))  # → [1,2,3,4]",
        "# queue.Queue (потокобезопасный)",
        "import queue",
        "q = queue.Queue()",
        "q.put(1); q.put(2)",
        "print(q.get())  # → 1",
        "print(q.qsize())  # → 1",
        "# Скользящее окно максимума",
        "from collections import deque",
        "def max_sliding_window(nums, k):",
        "dq = deque()",
        "result = []",
        "for i, x in enumerate(nums):",
        "while dq and nums[dq[-1]] <= x:",
        "dq.pop()",
        "dq.append(i)",
        "if dq[0] <= i-k: dq.popleft()",
        "if i >= k-1: result.append(nums[dq[0]])",
        "return result",
        "print(max_sliding_window([1,3,-1,-3,5,3,6,7], 3))  # → [3,3,5,5,6,7]",
        "# Задача Джозефуса",
        "def josephus(n, k):",
        "queue = deque(range(1, n+1))",
        "while len(queue) > 1:",
        "for _ in range(k-1):",
        "queue.append(queue.popleft())",
        "queue.popleft()",
        "return queue[0]",
        "print(josephus(7, 3))  # → 4"
      ],
      "related": [
        "collections.deque",
        "стек-stack",
        "куча-как-приоритетная-очередь"
      ],
      "related_errors": [
        "IndexError"
      ]
    },
    {
      "id": "префиксные-суммы",
      "title": "Префиксные суммы",
      "kind": "term",
      "summary": {
        "ru": "Массив prefix[i] = sum(arr[0..i]). Позволяет за O(1) найти сумму на отрезке [l, r].",
        "en": "The array prefix[i] = sum(arr[0..i]). It gives the sum over a range [l, r] in O(1)."
      },
      "body": {
        "ru": "Главная ловушка — сдвиг индексов: если prefix длиной n+1 и начинается с нуля, то сумма отрезка [l, r] включительно равна prefix[r+1] - prefix[l]; без ведущего нуля случай l == 0 приходится обрабатывать отдельно. Техника окупается только на неизменном массиве: правка одного элемента заставляет пересчитывать весь хвост за O(n), и там уже нужно дерево Фенвика или отрезков. itertools.accumulate строит ту же таблицу лениво, но без initial=0 (появился в 3.8) начинает не с нуля, а с arr[0].",
        "en": "The classic bug is the index shift: with a prefix array of length n+1 starting at zero, the inclusive range [l, r] is prefix[r+1] - prefix[l]; drop that leading zero and l == 0 turns into a special case. The trick only pays off on a static array — changing one element forces an O(n) rebuild of the tail, which is where a Fenwick or segment tree takes over. itertools.accumulate builds the same table lazily, but without initial=0 (added in 3.8) it starts at arr[0] instead of 0."
      },
      "syntax": "prefix[i] = prefix[i-1] + arr[i]\nsum(l, r) = prefix[r] - prefix[l-1]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.accumulate",
      "version": "3.2",
      "section": "Алгоритмы и структуры данных",
      "subcat": "техники",
      "color_group": "op",
      "aliases": [
        "сумма на отрезке",
        "кумулятивная сумма",
        "быстрая сумма подмассива"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def build_prefix(arr):",
        "prefix = [0] * (len(arr) + 1)",
        "for i, x in enumerate(arr):",
        "prefix[i+1] = prefix[i] + x",
        "return prefix",
        "def range_sum(prefix, l, r):",
        "return prefix[r+1] - prefix[l]",
        "arr = [1,2,3,4,5]",
        "p = build_prefix(arr)",
        "print(range_sum(p, 1, 3))  # → 9 (2+3+4)",
        "arr2 = [3,-1,4,1,5,-9,2,6]",
        "p2 = build_prefix(arr2)",
        "print(range_sum(p2, 0, 7))  # → 11 (вся сумма)",
        "print(range_sum(p2, 2, 4))  # → 10 (4+1+5)",
        "# Количество подмасивов с нулевой суммой",
        "from collections import Counter",
        "def count_zero_sum(arr):",
        "prefix = 0",
        "counts = Counter({0: 1})",
        "result = 0",
        "for x in arr:",
        "prefix += x",
        "result += counts[prefix]",
        "counts[prefix] += 1",
        "return result",
        "print(count_zero_sum([1,-1,2,-2,3]))  # → 4",
        "# 2D префиксные суммы",
        "matrix = [[1,2,3],[4,5,6],[7,8,9]]",
        "def build_2d_prefix(mat):",
        "n, m = len(mat), len(mat[0])",
        "p = [[0]*(m+1) for _ in range(n+1)]",
        "for i in range(1,n+1):",
        "for j in range(1,m+1):",
        "p[i][j] = mat[i-1][j-1]+p[i-1][j]+p[i][j-1]-p[i-1][j-1]",
        "return p",
        "p2d = build_2d_prefix(matrix)",
        "print(p2d[-1][-1])  # → 45 (сумма всей матрицы)",
        "# Скользящее окно с prefix",
        "arr3 = [1,2,3,4,5,6,7,8]",
        "p3 = build_prefix(arr3)",
        "k = 3",
        "max_sum = max(range_sum(p3,i,i+k-1) for i in range(len(arr3)-k+1))",
        "print(max_sum)  # → 21 (6+7+8)",
        "# Среднее на подмассиве",
        "print(range_sum(p, 0, 4) / 5)  # → 3.0"
      ],
      "related": [
        "itertools.accumulate",
        "sum",
        "два-указателя-two-pointers"
      ],
      "related_errors": []
    },
    {
      "id": "системы-счисления",
      "title": "Системы счисления",
      "kind": "term",
      "summary": {
        "ru": "Перевод между двоичной, восьмеричной, десятичной и шестнадцатеричной системами.",
        "en": "Conversion between the binary, octal, decimal and hexadecimal systems."
      },
      "body": {
        "ru": "bin(), oct() и hex() возвращают не число, а строку с префиксом 0b/0o/0x — складывать их как числа нельзя, и len(bin(n)) на два больше количества разрядов; голые цифры даёт format(n, 'b'). Обратно строку разбирает int(s, base), причём префикс он допускает, а int(s, 0) сам определяет систему по этому префиксу. У отрицательных bin(-5) даёт '-0b101' — знак минус перед модулем, а не дополнительный код, как в C.",
        "en": "bin(), oct() and hex() hand back a string with a 0b/0o/0x prefix, not a number: you cannot add them arithmetically, and len(bin(n)) is two more than the digit count — use format(n, 'b') for bare digits. Parsing back is int(s, base), which tolerates the prefix, while int(s, 0) infers the base from that prefix. For negatives bin(-5) is '-0b101', a minus sign in front of the magnitude rather than a two's-complement pattern as in C."
      },
      "syntax": "bin(n), oct(n), hex(n)\nint(s, base)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#bin",
      "version": "",
      "section": "Алгоритмы и структуры данных",
      "subcat": "математика",
      "color_group": "op",
      "aliases": [
        "перевод в двоичную систему",
        "двоичное представление числа",
        "шестнадцатеричный вид числа"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "# Десятичное → другие",
        "print(bin(42))   # → 0b101010",
        "print(oct(42))   # → 0o52",
        "print(hex(42))   # → 0x2a",
        "print(f'{42:b}')  # → 101010 (без префикса)",
        "print(f'{42:o}')  # → 52",
        "print(f'{42:x}')  # → 2a",
        "print(f'{42:X}')  # → 2A",
        "# Другие → десятичное",
        "print(int('101010', 2))  # → 42",
        "print(int('52', 8))      # → 42",
        "print(int('2a', 16))     # → 42",
        "print(int('0b101010', 2))  # → 42",
        "# Литералы",
        "a = 0b1010  # двоичный",
        "b = 0o17    # восьмеричный",
        "c = 0xff    # шестнадцатеричный",
        "print(a, b, c)  # → 10 15 255",
        "# Произвольное основание",
        "def to_base(n, base):",
        "digits = '0123456789ABCDEF'",
        "if n < base: return digits[n]",
        "return to_base(n//base, base) + digits[n%base]",
        "print(to_base(255, 16))  # → FF",
        "print(to_base(10, 2))    # → 1010",
        "# Форматирование с заполнением",
        "print(f'{255:08b}')  # → 11111111",
        "print(f'{15:04x}')   # → 000f",
        "print(format(42, '08b'))  # → 00101010",
        "# Перевод RGB hex",
        "color = '#ff5733'",
        "r, g, b = int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)",
        "print(r, g, b)  # → 255 87 51",
        "# ASCII и ord",
        "print(ord('A'))  # → 65 = 0x41",
        "print(hex(ord('A')))  # → 0x41"
      ],
      "related": [
        "bin",
        "hex",
        "oct",
        "int"
      ],
      "related_errors": []
    },
    {
      "id": "сортировка-вставками",
      "title": "Сортировка вставками",
      "kind": "term",
      "summary": {
        "ru": "O(n²) в худшем, O(n) для почти отсортированных. Вставляет каждый элемент на правильное место.",
        "en": "O(n²) in the worst case, O(n) for nearly sorted data. It inserts every item into its proper place."
      },
      "body": {
        "ru": "Алгоритм учебный, но не бесполезный: Timsort внутри list.sort() досортировывает вставками короткие участки, потому что на почти упорядоченных данных они действительно близки к O(n). Сортировка устойчива — равные элементы сохраняют взаимный порядок, что важно, когда сортируют по одному ключу после другого. Питон-специфичная ловушка: если в условии внутреннего цикла забыть проверку j >= 0, отрицательный индекс не даст IndexError, а начнёт читать элементы с конца списка, и ошибка проявится молча — неверным результатом.",
        "en": "It is a teaching algorithm that still earns its keep: Timsort inside list.sort() finishes short runs with insertion sort precisely because on nearly ordered data it approaches O(n). The sort is stable — equal items keep their relative order, which matters when you sort by one key after another. A Python-specific trap: drop the j >= 0 guard from the inner loop and a negative index will not raise IndexError but silently start reading from the end of the list, so the bug shows up only as a wrong result."
      },
      "syntax": "def insertion_sort(arr):",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/howto/sorting.html",
      "version": "",
      "section": "Алгоритмы и структуры данных",
      "subcat": "сортировка",
      "color_group": "op",
      "aliases": [
        "сортировка простыми вставками",
        "алгоритм сортировки вставкой"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def insertion_sort(arr):",
        "for i in range(1, len(arr)):",
        "key = arr[i]",
        "j = i - 1",
        "while j >= 0 and arr[j] > key:",
        "arr[j+1] = arr[j]",
        "j -= 1",
        "arr[j+1] = key",
        "return arr",
        "print(insertion_sort([12,11,13,5,6]))  # → [5,6,11,12,13]",
        "# Трассировка",
        "def ins_trace(arr):",
        "arr = arr[:]",
        "for i in range(1, len(arr)):",
        "key = arr[i]",
        "j = i-1",
        "while j >= 0 and arr[j] > key:",
        "arr[j+1] = arr[j]; j -= 1",
        "arr[j+1] = key",
        "print(arr)",
        "return arr",
        "ins_trace([4,3,1,2])",
        "# Онлайн-алгоритм (добавление элементов)",
        "def ins_insert(sorted_arr, x):",
        "i = len(sorted_arr)",
        "sorted_arr.append(x)",
        "while i > 0 and sorted_arr[i-1] > x:",
        "sorted_arr[i] = sorted_arr[i-1]",
        "i -= 1",
        "sorted_arr[i] = x",
        "return sorted_arr",
        "result = []",
        "for x in [3,1,4,1,5]:",
        "ins_insert(result, x)",
        "print(result)  # → [1,1,3,4,5]",
        "# Binary insertion sort",
        "import bisect",
        "def bin_ins_sort(arr):",
        "result = []",
        "for x in arr:",
        "bisect.insort(result, x)",
        "return result",
        "print(bin_ins_sort([5,3,1,4,2]))  # → [1,2,3,4,5]",
        "# Скорость на почти отсортированных данных",
        "import random",
        "arr = list(range(100))",
        "arr[50] = -1  # один элемент не на месте",
        "print(insertion_sort(arr[:])[:5])  # → [−1,0,1,2,3]"
      ],
      "related": [
        "сортировка-выбором",
        "сортировка-пузырьком",
        "sorted"
      ],
      "related_errors": []
    },
    {
      "id": "сортировка-выбором",
      "title": "Сортировка выбором",
      "kind": "term",
      "summary": {
        "ru": "O(n²). Находит минимальный элемент и ставит на нужное место. Минимум обменов (O(n)).",
        "en": "O(n²). It finds the smallest item and puts it where it belongs. The number of swaps is minimal (O(n))."
      },
      "body": {
        "ru": "В отличие от вставок и пузырька, здесь нет быстрого случая: полный проход по остатку делается всегда, поэтому даже на уже отсортированном списке те же O(n²) сравнений. Зато обменов ровно O(n) — по одному на позицию, и это единственная практическая причина о ней помнить: когда запись дороже чтения. Классическая реализация через обмен с минимумом неустойчива — равные элементы могут поменяться местами, так что для сортировки по нескольким ключам подряд она не подходит.",
        "en": "Unlike insertion or bubble sort it has no fast case: the full scan of the remaining tail happens every time, so an already sorted list still costs O(n²) comparisons. What it does buy is exactly O(n) swaps, one per position, and that is the only practical reason to remember it — situations where writing is more expensive than reading. The classic swap-with-the-minimum implementation is not stable: equal items can trade places, which rules it out for sorting by several keys in succession."
      },
      "syntax": "def selection_sort(arr):",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/howto/sorting.html",
      "version": "",
      "section": "Алгоритмы и структуры данных",
      "subcat": "сортировка",
      "color_group": "op",
      "aliases": [
        "сортировка простым выбором",
        "сортировка выбором минимума"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def selection_sort(arr):",
        "n = len(arr)",
        "for i in range(n):",
        "min_idx = i",
        "for j in range(i+1, n):",
        "if arr[j] < arr[min_idx]:",
        "min_idx = j",
        "arr[i], arr[min_idx] = arr[min_idx], arr[i]",
        "return arr",
        "print(selection_sort([64,25,12,22,11]))  # → [11,12,22,25,64]",
        "# Трассировка",
        "def sel_trace(arr):",
        "arr = arr[:]",
        "n = len(arr)",
        "for i in range(n):",
        "min_idx = i",
        "for j in range(i+1, n):",
        "if arr[j] < arr[min_idx]:",
        "min_idx = j",
        "arr[i], arr[min_idx] = arr[min_idx], arr[i]",
        "print(arr)",
        "return arr",
        "sel_trace([3,1,4,2])",
        "# Descending",
        "def sel_desc(arr):",
        "n = len(arr)",
        "for i in range(n):",
        "max_idx = i",
        "for j in range(i+1, n):",
        "if arr[j] > arr[max_idx]:",
        "max_idx = j",
        "arr[i], arr[max_idx] = arr[max_idx], arr[i]",
        "return arr",
        "print(sel_desc([3,1,4,1,5]))  # → [5,4,3,1,1]",
        "# Подсчёт шагов",
        "def sel_steps(arr):",
        "n = len(arr); comparisons = 0",
        "for i in range(n):",
        "min_idx = i",
        "for j in range(i+1, n):",
        "comparisons += 1",
        "if arr[j] < arr[min_idx]:",
        "min_idx = j",
        "arr[i], arr[min_idx] = arr[min_idx], arr[i]",
        "return comparisons",
        "print(sel_steps([5,4,3,2,1]))  # → 10 сравнений для n=5",
        "# Selection sort всегда O(n²) независимо от входных данных",
        "print(selection_sort([1,2,3,4,5]))  # → [1,2,3,4,5] (уже отсортирован)"
      ],
      "related": [
        "сортировка-вставками",
        "сортировка-пузырьком",
        "min"
      ],
      "related_errors": []
    },
    {
      "id": "сортировка-пузырьком",
      "title": "Сортировка пузырьком",
      "kind": "term",
      "summary": {
        "ru": "O(n²). Сравнивает соседние элементы и меняет местами. Наглядна, но медленна. Оптимизация — флаг обмена.",
        "en": "O(n²). It compares neighboring items and swaps them. Easy to follow, but slow. It can be optimized with a swap flag."
      },
      "body": {
        "ru": "В рабочем коде пузырёк не пишут: sorted() и list.sort() используют Timsort на C с O(n log n), и уже на нескольких тысячах элементов разрыв становится неприличным — пузырёк живёт только как учебный разбор. Две ошибки повторяются постоянно: во внутреннем цикле забывают -i-1 (лишние проходы по уже отсортированному хвосту либо выход за границу при обращении к j+1) и не замечают, что обмен идёт на месте — список вызывающего меняется, даже если функция ничего не вернула.",
        "en": "Nobody writes bubble sort in real code: sorted() and list.sort() run Timsort in C at O(n log n), and past a few thousand items the gap is embarrassing — bubble sort survives only as a teaching exercise. Two mistakes repeat endlessly: dropping the -i-1 in the inner loop, which either wastes passes over the already-sorted tail or walks j+1 off the end, and missing that the swap happens in place, so the caller's list changes even when the function returns nothing."
      },
      "syntax": "def bubble_sort(arr):",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/howto/sorting.html",
      "version": "",
      "section": "Алгоритмы и структуры данных",
      "subcat": "сортировка",
      "color_group": "op",
      "aliases": [
        "пузырьковая сортировка",
        "обмен соседних элементов"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def bubble_sort(arr):",
        "n = len(arr)",
        "for i in range(n):",
        "for j in range(0, n-i-1):",
        "if arr[j] > arr[j+1]:",
        "arr[j], arr[j+1] = arr[j+1], arr[j]",
        "return arr",
        "print(bubble_sort([64,34,25,12,22,11,90]))  # → [11,12,22,25,34,64,90]",
        "# С флагом (оптимизация)",
        "def bubble_opt(arr):",
        "n = len(arr)",
        "for i in range(n):",
        "swapped = False",
        "for j in range(0, n-i-1):",
        "if arr[j] > arr[j+1]:",
        "arr[j], arr[j+1] = arr[j+1], arr[j]",
        "swapped = True",
        "if not swapped:",
        "break  # уже отсортирован",
        "return arr",
        "print(bubble_opt([1,2,3,4,5]))  # → [1,2,3,4,5] (1 проход)",
        "# Трассировка",
        "def bubble_trace(arr):",
        "arr = arr[:]",
        "n = len(arr)",
        "for i in range(n):",
        "for j in range(0, n-i-1):",
        "if arr[j] > arr[j+1]:",
        "arr[j], arr[j+1] = arr[j+1], arr[j]",
        "print(f'pass {i+1}: {arr}')",
        "return arr",
        "bubble_trace([5,3,1,4,2])",
        "# → pass 1: [3,1,4,2,5] / pass 2: [1,3,2,4,5] ...",
        "# Число обменов",
        "def bubble_count(arr):",
        "arr = arr[:]",
        "n = len(arr); swaps = 0",
        "for i in range(n):",
        "for j in range(0, n-i-1):",
        "if arr[j] > arr[j+1]:",
        "arr[j], arr[j+1] = arr[j+1], arr[j]",
        "swaps += 1",
        "return arr, swaps",
        "print(bubble_count([3,2,1]))  # → ([1,2,3], 3)",
        "# Worst case: обратно отсортированный",
        "import time",
        "arr = list(range(1000, 0, -1))",
        "t = time.time()",
        "bubble_sort(arr[:])",
        "print(f'Bubble sort 1000 elements: {time.time()-t:.3f}s')"
      ],
      "related": [
        "сортировка-вставками",
        "сортировка-выбором",
        "list.sort"
      ],
      "related_errors": []
    },
    {
      "id": "стек-stack",
      "title": "Стек (stack)",
      "kind": "function",
      "summary": {
        "ru": "LIFO — последний вошёл, первый вышел. Реализуется через list. Применения: скобки, история, DFS.",
        "en": "LIFO — last in, first out. Implemented with a list. Uses: bracket matching, history, DFS."
      },
      "body": {
        "ru": "Для стека список — правильный контейнер: append() и pop() с конца стоят амортизированные O(1), и deque здесь ничего не выигрывает. Ловушка одна и постоянная: pop() на пустом стеке бросает IndexError, поэтому перед снятием проверяют, что стек непуст (сам список в булевом контексте это и показывает). Явный стек ещё и спасает там, где рекурсивный обход уходит слишком глубоко и упирается в RecursionError.",
        "en": "A list is the right container here: append() and pop() at the end are amortized O(1), and a deque buys you nothing. The recurring trap is popping an empty stack — that raises IndexError, so test the stack in a boolean context before you pop. An explicit stack also rescues traversals that go too deep for recursion and would otherwise hit RecursionError."
      },
      "syntax": "stack = []\nstack.append(x)  # push\nstack.pop()      # pop",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/datastructures.html#using-lists-as-stacks",
      "version": "",
      "section": "Алгоритмы и структуры данных",
      "subcat": "структуры",
      "color_group": "op",
      "aliases": [
        "последний пришёл первый вышел",
        "проверка скобок стеком"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "# Базовые операции",
        "stack = []",
        "stack.append(1)",
        "stack.append(2)",
        "stack.append(3)",
        "print(stack[-1])  # peek → 3",
        "print(stack.pop())  # → 3",
        "print(stack)  # → [1,2]",
        "# Проверка правильности скобок",
        "def is_balanced(s):",
        "stack = []",
        "pairs = {')':'(',']':'[','}':'{'}",
        "for ch in s:",
        "if ch in '([{':",
        "stack.append(ch)",
        "elif ch in ')]}':",
        "if not stack or stack[-1] != pairs[ch]:",
        "return False",
        "stack.pop()",
        "return len(stack) == 0",
        "print(is_balanced('({[]})'))  # → True",
        "print(is_balanced('([)]'))    # → False",
        "# Обращение строки через стек",
        "def reverse_string(s):",
        "stack = list(s)",
        "result = []",
        "while stack:",
        "result.append(stack.pop())",
        "return ''.join(result)",
        "print(reverse_string('hello'))  # → olleh",
        "# Перевод в двоичную систему",
        "def to_binary(n):",
        "if n == 0: return '0'",
        "stack = []",
        "while n > 0:",
        "stack.append(n % 2)",
        "n //= 2",
        "return ''.join(str(b) for b in reversed(stack))",
        "print(to_binary(10))   # → 1010",
        "print(to_binary(255))  # → 11111111",
        "# Вычисление постфиксного выражения",
        "def eval_postfix(tokens):",
        "stack = []",
        "for t in tokens:",
        "if t.lstrip('-').isdigit():",
        "stack.append(int(t))",
        "else:",
        "b, a = stack.pop(), stack.pop()",
        "if t=='+': stack.append(a+b)",
        "elif t=='-': stack.append(a-b)",
        "elif t=='*': stack.append(a*b)",
        "elif t=='/': stack.append(a//b)",
        "return stack[0]",
        "print(eval_postfix(['3','4','+','2','*']))  # → 14",
        "# Минимальный стек (O(1) min)",
        "class MinStack:",
        "def __init__(self):",
        "self.stack = []; self.min_stack = []",
        "def push(self, x):",
        "self.stack.append(x)",
        "m = min(x, self.min_stack[-1]) if self.min_stack else x",
        "self.min_stack.append(m)",
        "def pop(self):",
        "self.min_stack.pop(); return self.stack.pop()",
        "def get_min(self): return self.min_stack[-1]",
        "ms = MinStack()",
        "for v in [3,1,4,1,5]: ms.push(v)",
        "print(ms.get_min())  # → 1"
      ],
      "related": [
        "очередь-queue",
        "list.append",
        "list.pop",
        "collections.deque"
      ],
      "related_errors": [
        "IndexError"
      ]
    },
    {
      "id": "хеш-таблица-dict",
      "title": "Хеш-таблица (dict)",
      "kind": "function",
      "summary": {
        "ru": "Dict в Python — хеш-таблица. O(1) в среднем для get/set/del. Применения: подсчёт, кэш, индексирование.",
        "en": "A Python dict is a hash table. get/set/del take O(1) on average. Uses: counting, caches, indexes."
      },
      "body": {
        "ru": "Ключом может быть только хешируемый объект — строка, число, кортеж из неизменяемого; список или множество дадут TypeError: unhashable type. Обращение d[key] к отсутствующему ключу — это KeyError, поэтому там, где ключа может не быть, берут get() с дефолтом или defaultdict. Порядок вставки гарантирован языком с версии 3.7, но это именно порядок добавления, а не отсортированность ключей.",
        "en": "Keys must be hashable — strings, numbers, tuples of immutables; a list or a set gives TypeError: unhashable type. Reading a missing key with d[key] raises KeyError, so when the key may be absent use get() with a default, or a defaultdict. Insertion order has been guaranteed by the language since 3.7, but that is the order you added things in, not sorted order."
      },
      "syntax": "d = {}\nd[key] = value\nd.get(key, default)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#mapping-types-dict",
      "version": "",
      "section": "Алгоритмы и структуры данных",
      "subcat": "структуры",
      "color_group": "op",
      "aliases": [
        "ассоциативный массив",
        "хеширование ключей",
        "быстрый поиск по ключу"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "# Подсчёт частоты",
        "from collections import Counter",
        "words = 'the cat sat on the mat the cat sat'.split()",
        "c = Counter(words)",
        "print(c.most_common(3))  # → [('the',3),('cat',2),('sat',2)]",
        "# Анаграммы",
        "def are_anagrams(s1, s2):",
        "return Counter(s1) == Counter(s2)",
        "print(are_anagrams('listen', 'silent'))  # → True",
        "print(are_anagrams('hello', 'world'))   # → False",
        "# Первый неповторяющийся символ",
        "def first_unique(s):",
        "count = {}",
        "for ch in s:",
        "count[ch] = count.get(ch, 0) + 1",
        "for ch in s:",
        "if count[ch] == 1:",
        "return ch",
        "return None",
        "print(first_unique('leetcode'))  # → l",
        "# Two Sum с хеш-таблицей O(n)",
        "def two_sum_hash(nums, target):",
        "seen = {}",
        "for i, x in enumerate(nums):",
        "if target-x in seen:",
        "return [seen[target-x], i]",
        "seen[x] = i",
        "return []",
        "print(two_sum_hash([2,7,11,15], 9))  # → [0,1]",
        "# Кэш с defaultdict",
        "from collections import defaultdict",
        "graph = defaultdict(list)",
        "graph[1].append(2)",
        "graph[1].append(3)",
        "graph[2].append(4)",
        "print(dict(graph))  # → {1:[2,3], 2:[4]}"
      ],
      "related": [
        "dict",
        "dict.get",
        "hash",
        "collections.counter"
      ],
      "related_errors": [
        "KeyError"
      ]
    },
    {
      "id": "числа-фибоначчи",
      "title": "Числа Фибоначчи",
      "kind": "term",
      "summary": {
        "ru": "F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2). Итеративно O(n), рекурсивно O(2^n), с lru_cache O(n).",
        "en": "F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2). Iteratively O(n), recursively O(2^n), with lru_cache O(n)."
      },
      "body": {
        "ru": "Наивная рекурсия пересчитывает одни и те же значения заново: fib(35) считается секунды, а fib(50) можно не дожидаться — одна строка @lru_cache(maxsize=None) над функцией снимает проблему. У рекурсии есть и второй потолок: около тысячи вложенных вызовов, дальше RecursionError, поэтому для больших n берут цикл, а не память. Формула Бине через float начинает врать примерно с n=71, тогда как целочисленный цикл точен при любом n — у Python длинная арифметика без переполнения.",
        "en": "Naive recursion recomputes the same values again and again: fib(35) takes seconds and fib(50) is hopeless, while a single @lru_cache(maxsize=None) on the function removes the problem. Recursion has a second ceiling as well — roughly a thousand nested calls before RecursionError — so large n calls for a loop rather than memoisation. Binet's float formula starts producing wrong digits around n=71, whereas an integer loop stays exact for any n because Python integers never overflow."
      },
      "syntax": "def fib(n): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/introduction.html#first-steps-towards-programming",
      "version": "",
      "section": "Алгоритмы и структуры данных",
      "subcat": "математика",
      "color_group": "op",
      "aliases": [
        "последовательность фибоначчи",
        "ряд фибоначчи"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "# Итеративно — O(n)",
        "def fib_iter(n):",
        "    if n <= 1: return n",
        "    a, b = 0, 1",
        "    for _ in range(2, n+1):",
        "        a, b = b, a+b",
        "        return b",
        "print([fib_iter(i) for i in range(10)])  # → [0,1,1,2,3,5,8,13,21,34]",
        "# Рекурсивно — O(2^n) (медленно!)",
        "def fib_rec(n):",
        "    if n <= 1: return n",
        "    return fib_rec(n-1) + fib_rec(n-2)",
        "print(fib_rec(10))  # → 55 (для малых n)",
        "# С lru_cache — O(n) + мемоизация",
        "from functools import lru_cache",
        "@lru_cache(maxsize=None)",
        "def fib_memo(n):",
        "    if n <= 1: return n",
        "    return fib_memo(n-1) + fib_memo(n-2)",
        "print(fib_memo(50))  # → 12586269025",
        "# Генератор Фибоначчи",
        "def fib_gen():",
        "    a, b = 0, 1",
        "    while True:",
        "        yield a",
        "        a, b = b, a+b",
        "        from itertools import islice",
        "        print(list(islice(fib_gen(), 10)))  # → [0,1,1,2,3,5,8,13,21,34]",
        "# Матричный метод O(log n)",
        "def mat_mul(A, B):",
        "    return [",
        "[A[0][0]*B[0][0]+A[0][1]*B[1][0], A[0][0]*B[0][1]+A[0][1]*B[1][1]],",
        "[A[1][0]*B[0][0]+A[1][1]*B[1][0], A[1][0]*B[0][1]+A[1][1]*B[1][1]]",
        "]",
        "def mat_pow(M, n):",
        "    if n == 1: return M",
        "    if n % 2 == 0:",
        "        half = mat_pow(M, n//2)",
        "        return mat_mul(half, half)",
        "    return mat_mul(M, mat_pow(M, n-1))",
        "def fib_matrix(n):",
        "    if n == 0: return 0",
        "    M = [[1,1],[1,0]]",
        "    return mat_pow(M, n)[0][1]",
        "print(fib_matrix(10))  # → 55",
        "# Числа Фибоначчи в программировании",
        "# Сумма чисел Фибоначчи до N",
        "def fib_until(n):",
        "    a, b = 0, 1",
        "    result = []",
        "    while a <= n:",
        "        result.append(a)",
        "        a, b = b, a+b",
        "        return result",
        "print(fib_until(100))  # → [0,1,1,2,3,5,8,13,21,34,55,89]"
      ],
      "related": [
        "рекурсия",
        "functools.lru_cache",
        "functools.cache"
      ],
      "related_errors": []
    },
    {
      "id": "callable-arg-ret",
      "title": "Callable[[arg], ret]",
      "kind": "term",
      "summary": {
        "ru": "Аннотация для callable-объектов. Callable[[A1, A2], R] — принимает A1, A2, возвращает R. Callable[..., R] — любые аргументы.",
        "en": "Annotation for callable objects. Callable[[A1, A2], R] takes A1, A2 and returns R. Callable[..., R] accepts any arguments."
      },
      "body": {
        "ru": "Внутренние скобки — это список типов аргументов, причём только позиционных: именованные параметры, значения по умолчанию и *args через Callable описать нельзя, для этого нужен Protocol с методом __call__. Многоточие в Callable[..., R] означает «аргументы любые», а не «без аргументов» — пустой список аргументов записывается двумя пустыми скобками. С Python 3.9 typing.Callable считается устаревшим: в новом коде берут collections.abc.Callable.",
        "en": "The inner brackets list the argument types, and only positional ones: keyword parameters, defaults and *args simply cannot be expressed with Callable — use a Protocol with a __call__ method instead. The ellipsis form means \"any arguments at all\", not \"no arguments\"; a no-argument callable is written with an empty inner list. Since Python 3.9 typing.Callable is deprecated in favour of collections.abc.Callable."
      },
      "syntax": "from typing import Callable\nCallable[[ArgType, ...], ReturnType]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.Callable",
      "version": "",
      "section": "Аннотации и typing",
      "subcat": "callable",
      "color_group": "typing",
      "aliases": [
        "тип вызываемого объекта",
        "аннотация функции как параметра"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import Callable",
        "def apply(f: Callable[[int], int], x: int) -> int:",
        "    return f(x)",
        "print(apply(lambda x: x * 2, 5))  # → 10",
        "def run(cb: Callable[[], None]) -> None:",
        "    cb()",
        "    run(lambda: print('called'))  # → called",
        "def transform(data: list, fn: Callable[[int], int]) -> list:",
        "    return [fn(x) for x in data]",
        "print(transform([1, 2, 3], lambda x: x ** 2))  # → [1, 4, 9]",
        "print(callable(print))  # → True"
      ],
      "related": [
        "callable",
        "вызываемые-объекты-__call__",
        "typing.ParamSpec"
      ],
      "related_errors": []
    },
    {
      "id": "classvar",
      "title": "ClassVar",
      "kind": "term",
      "summary": {
        "ru": "Указывает, что переменная является переменной класса, а не экземпляра. Не должна присваиваться через self.",
        "en": "Marks a variable as belonging to the class rather than to the instance. It must not be assigned through self."
      },
      "body": {
        "ru": "Сама аннотация ничего не запрещает — присваивание через self всё равно создаст атрибут экземпляра, который затенит классовый, и укажет на это только статический анализатор. Настоящий эффект ClassVar проявляется в dataclass: поле с такой аннотацией не попадает ни в сгенерированный __init__, ни в fields() и остаётся обычной константой класса. Помните и про общий подвох классовых атрибутов: изменяемый список или словарь один на всех, и правка на месте видна из каждого экземпляра.",
        "en": "The annotation enforces nothing by itself — assigning through self still creates an instance attribute that shadows the class one, and only a type checker will complain. Where ClassVar really bites is dataclasses: such a field is excluded from the generated __init__ and from fields(), staying a plain class-level constant. Also mind the usual class-attribute trap — a mutable list or dict is shared by every instance, so mutating it in place is visible everywhere."
      },
      "syntax": "from typing import ClassVar\nclass MyClass:\n    attr: ClassVar[type] = value",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.ClassVar",
      "version": "3.5",
      "section": "Аннотации и typing",
      "subcat": "атрибуты",
      "color_group": "typing",
      "aliases": [
        "переменная класса в аннотации",
        "поле класса, а не экземпляра"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import ClassVar",
        "class Counter:",
        "count: ClassVar[int] = 0",
        "def __init__(self): Counter.count += 1",
        "c1 = Counter()",
        "c2 = Counter()",
        "print(Counter.count)  # → 2",
        "class Config:",
        "debug: ClassVar[bool] = False",
        "host: str = 'localhost'",
        "print(Config.debug)  # → False",
        "print(Config.host)  # → localhost",
        "Config.debug = True",
        "print(Config.debug)  # → True"
      ],
      "related": [
        "атрибуты-экземпляра-и-класса",
        "dataclass",
        "final"
      ],
      "related_errors": []
    },
    {
      "id": "final",
      "title": "Final",
      "kind": "term",
      "summary": {
        "ru": "Обозначает переменную как константу — не должна переназначаться после инициализации. Только для статического анализа.",
        "en": "Marks a variable as a constant — it must not be reassigned after initialization. For static analysis only."
      },
      "body": {
        "ru": "Final запрещает переназначать имя, но не делает объект неизменяемым: список, помеченный как Final, всё ещё можно дополнять и сортировать на месте. Проверяет это только статический анализатор — интерпретатор спокойно выполнит повторное присваивание, никакой ошибки во время выполнения не будет. Не путать с декоратором @final: тот про запрет наследовать класс и переопределять метод.",
        "en": "Final forbids rebinding the name, not mutating the object: a list marked Final can still be appended to and sorted in place. Only a static checker enforces it — the interpreter happily performs a reassignment, with no runtime error at all. Do not confuse it with the @final decorator, which forbids subclassing a class or overriding a method."
      },
      "syntax": "from typing import Final\nX: Final = value\nX: Final[type] = value",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.Final",
      "version": "3.8",
      "section": "Аннотации и typing",
      "subcat": "константы",
      "color_group": "typing",
      "aliases": [
        "константа в аннотации",
        "запрет переназначения переменной"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import Final",
        "MAX_SIZE: Final = 100",
        "PI: Final[float] = 3.14159",
        "print(MAX_SIZE)  # → 100",
        "print(PI)  # → 3.14159",
        "class Config:",
        "HOST: Final = 'localhost'",
        "PORT: Final[int] = 8080",
        "print(Config.HOST)  # → localhost",
        "print(Config.PORT)  # → 8080"
      ],
      "related": [
        "classvar",
        "typing.override",
        "аннотации-типов-type-hints"
      ],
      "related_errors": []
    },
    {
      "id": "generic-t",
      "title": "Generic[T]",
      "kind": "term",
      "summary": {
        "ru": "Базовый класс для создания обобщённых пользовательских классов. Параметры типа задаются через TypeVar.",
        "en": "Base class for writing generic user-defined classes. The type parameters are declared with TypeVar."
      },
      "body": {
        "ru": "С Python 3.12 наследовать Generic и заводить TypeVar вручную больше не нужно — параметры типа объявляются прямо в заголовке класса, в квадратных скобках после имени. Параметры существуют только для проверяющего: во время выполнения они стираются, поэтому isinstance с параметризованным типом бросит TypeError. Если параметров несколько, их порядок в Generic задаёт порядок при подстановке типов на месте использования.",
        "en": "Since Python 3.12 you no longer inherit from Generic or declare a TypeVar by hand — the type parameters go straight into the class header, in brackets after the name. Those parameters exist for the checker only: they are erased at runtime, so isinstance against a parameterized type raises TypeError. With several parameters, their order in Generic fixes the order callers must use when supplying concrete types."
      },
      "syntax": "from typing import Generic, TypeVar\nT = TypeVar('T')\nclass MyClass(Generic[T]): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.Generic",
      "version": "",
      "section": "Аннотации и typing",
      "subcat": "generics",
      "color_group": "typing",
      "aliases": [
        "обобщённый класс",
        "дженерики",
        "параметризация типа"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import Generic, TypeVar",
        "T = TypeVar('T')",
        "class Stack(Generic[T]):",
        "def __init__(self): self._items: list[T] = []",
        "def push(self, item: T) -> None: self._items.append(item)",
        "def pop(self) -> T: return self._items.pop()",
        "s: Stack[int] = Stack()",
        "s.push(1)",
        "s.push(2)",
        "print(s.pop())  # → 2",
        "print(len(s._items))  # → 1",
        "s.push(10)",
        "print(s.pop())  # → 10",
        "print(len(s._items))  # → 1"
      ],
      "related": [
        "typevar",
        "typing.Generic",
        "protocol"
      ],
      "related_errors": []
    },
    {
      "id": "int-str-list-dict-аннотации",
      "title": "int / str / list / dict (аннотации)",
      "kind": "function",
      "summary": {
        "ru": "С Python 3.9+ встроенные типы используются как обобщения напрямую: list[int], dict[str, int]. Ранее нужен был typing.List, typing.Dict.",
        "en": "Since Python 3.9 the built-in types are used as generics directly: list[int], dict[str, int]. Earlier this required typing.List and typing.Dict."
      },
      "body": {
        "ru": "Интерпретатор аннотации не проверяет: функция, объявленная как принимающая list[int], без единого возражения примет список строк — расхождение найдёт только mypy или похожий анализатор. Тип указывается один на весь контейнер, поэтому для разнородных данных берут union или tuple с фиксированными типами по позициям. typing.List и typing.Dict формально устарели с 3.9 и живут только ради совместимости со старым кодом.",
        "en": "Annotations are not checked at runtime: a function declared to take list[int] will accept a list of strings without a murmur — only mypy or a similar checker will spot the mismatch. One type applies to the whole container, so heterogeneous data calls for a union or a tuple with per-position types. typing.List and typing.Dict have been deprecated since 3.9 and remain only for compatibility with older code."
      },
      "syntax": "x: int = 5\ns: str = 'hi'\nitems: list[int] = [1, 2, 3]\nmapping: dict[str, int] = {'a': 1}",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#types-genericalias",
      "version": "3.9",
      "section": "Аннотации и typing",
      "subcat": "встроенные",
      "color_group": "typing",
      "aliases": [
        "встроенные типы в аннотациях",
        "аннотация списка и словаря"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "def greet(name: str) -> str:",
        "    return f'Hello, {name}'",
        "print(greet('Alice'))  # → Hello, Alice!",
        "def total(nums: list[int]) -> int:",
        "    return sum(nums)",
        "print(total([1, 2, 3]))  # → 6",
        "coords: tuple[float, float] = (1.0, 2.0)  # → аннотация кортежа",
        "matrix: list[list[int]] = [[1, 2], [3, 4]]  # → вложенные типы"
      ],
      "related": [
        "аннотации-типов-type-hints",
        "union-x-y-x-y",
        "generic-t"
      ],
      "related_errors": []
    },
    {
      "id": "literal-v1-v2",
      "title": "Literal[v1, v2]",
      "kind": "term",
      "summary": {
        "ru": "Тип, допускающий только конкретные литеральные значения. Позволяет mypy/pyright проверить, что передаётся допустимое значение.",
        "en": "A type that admits only the given literal values. Lets mypy/pyright check that an allowed value is passed."
      },
      "body": {
        "ru": "Literal живёт только в голове проверяющего типы: в рантайме ничего не сверяется, и open_file('f.txt', 'x') спокойно выполнится — нужен именно запуск mypy или pyright. Второй подвох — внутрь скобок кладут только сами литералы (строки, int, bool, None, члены Enum), не переменные и не выражения; и если положить значение в обычную переменную, её тип выведется как str, а не как Literal, поэтому такой аргумент проверка отвергнет — объявляйте переменную с типом-алиасом или как Final.",
        "en": "Literal exists only for the type checker: nothing is verified at runtime, and open_file('f.txt', 'x') runs happily unless you actually run mypy or pyright. The other catch is that only literals themselves go inside the brackets (strings, ints, bools, None, Enum members) — no variables or expressions; and a value stashed in an ordinary variable is inferred as str rather than as the literal, so passing it gets rejected. Annotate that variable with the alias type or mark it Final."
      },
      "syntax": "from typing import Literal\nLiteral['read', 'write']\nLiteral[1, 2, 3]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.Literal",
      "version": "3.8",
      "section": "Аннотации и typing",
      "subcat": "литерал",
      "color_group": "typing",
      "aliases": [
        "тип из конкретных значений",
        "ограничить допустимые значения аргумента"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import Literal",
        "Mode = Literal['r', 'w', 'a']",
        "def open_file(path: str, mode: Mode) -> None:",
        "print(f'open {path} as {mode}')",
        "open_file('f.txt', 'r')  # → open f.txt as r",
        "open_file('f.txt', 'w')  # → open f.txt as w",
        "Direction = Literal['N', 'S', 'E', 'W']",
        "def move(d: Direction) -> str:",
        "return f'Moving {d}'",
        "print(move('N'))  # → Moving N",
        "print(move('S'))  # → Moving S"
      ],
      "related": [
        "union-x-y-x-y",
        "enum",
        "final"
      ],
      "related_errors": []
    },
    {
      "id": "namedtuple",
      "title": "NamedTuple",
      "kind": "term",
      "summary": {
        "ru": "Типизированный именованный кортеж через наследование. Синтаксис класса с аннотациями.",
        "en": "A typed named tuple defined by inheritance. Class syntax with annotations."
      },
      "body": {
        "ru": "Берите NamedTuple, когда нужна неизменяемая хешируемая запись, которая ведёт себя как кортеж: распаковка, индексация, использование ключом словаря, экономия памяти. Если поля должны меняться или планируется наследование и своя логика — это работа для dataclass. Ловушка ровно в том, что это настоящий кортеж: Point(1.0, 2.0) == (1.0, 2.0) даёт True, так что тип не спасёт от случайного смешивания с обычными кортежами.",
        "en": "Reach for NamedTuple when you want an immutable, hashable record that still behaves like a tuple: unpacking, indexing, use as a dict key, small memory footprint. If the fields have to change, or you need inheritance and custom behaviour, a dataclass fits better. The trap is that it really is a tuple: Point(1.0, 2.0) == (1.0, 2.0) is True, so the type will not protect you from mixing it up with plain tuples."
      },
      "syntax": "from typing import NamedTuple\nclass MyTuple(NamedTuple):\n    field: type",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.NamedTuple",
      "version": "",
      "section": "Аннотации и typing",
      "subcat": "namedtuple",
      "color_group": "typing",
      "aliases": [
        "типизированный именованный кортеж",
        "кортеж с полями и типами"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import NamedTuple",
        "class Point(NamedTuple):",
        "x: float",
        "y: float",
        "label: str = ''",
        "p = Point(1.0, 2.0, 'A')",
        "print(p.x)  # → 1.0",
        "print(p.label)  # → A",
        "print(p._asdict())  # → {'x': 1.0, 'y': 2.0, 'label': 'A'}",
        "print(p._replace(x=5.0))  # → Point(x=5.0, y=2.0, label='A')"
      ],
      "related": [
        "collections.namedtuple",
        "dataclass",
        "typeddict",
        "наименованный-кортеж-namedtuple"
      ],
      "related_errors": []
    },
    {
      "id": "optional-x-x-none",
      "title": "Optional[X] / X | None",
      "kind": "term",
      "summary": {
        "ru": "Optional[X] означает X | None. С Python 3.10+ можно писать X | None напрямую. Применяется для значений, которые могут отсутствовать.",
        "en": "Optional[X] means X | None. Since Python 3.10 you can write X | None directly. Used for values that may be absent."
      },
      "body": {
        "ru": "Главная путаница новичков: Optional не значит «аргумент можно не передавать». Он говорит лишь о том, что значением бывает None, а необязательным параметр делает значение по умолчанию — def f(x: Optional[int]) по-прежнему требует аргумент. И mypy давно не дописывает Optional сам: x: int = None теперь ошибка, пишите x: int | None = None.",
        "en": "The usual beginner mix-up: Optional does not mean \"you may omit the argument\". It only says the value can be None; what makes a parameter optional is a default value, so def f(x: Optional[int]) still demands an argument. Also, mypy no longer adds Optional implicitly — x: int = None is an error these days, so spell out x: int | None = None."
      },
      "syntax": "from typing import Optional\nOptional[X]  # эквивалентно X | None\n# Python 3.10+: X | None",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.Optional",
      "version": "3.10",
      "section": "Аннотации и typing",
      "subcat": "опциональный",
      "color_group": "typing",
      "aliases": [
        "необязательное значение в аннотации",
        "тип, допускающий отсутствие значения"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import Optional",
        "def find(name: str) -> Optional[str]:",
        "    return name if name else None",
        "print(find('Alice'))  # → Alice",
        "print(find(''))  # → None",
        "result: Optional[int] = None  # → допустимо",
        "def greet(name: str | None = None) -> str:",
        "    return f'Hi, {name or \"stranger\"}'",
        "print(greet())  # → Hi, stranger"
      ],
      "related": [
        "union-x-y-x-y",
        "nonetype",
        "аннотации-типов-type-hints",
        "int-str-list-dict-аннотации"
      ],
      "related_errors": []
    },
    {
      "id": "protocol",
      "title": "Protocol",
      "kind": "term",
      "summary": {
        "ru": "Определяет интерфейс через структуру (duck typing). Класс считается совместимым, если реализует все методы Protocol, без явного наследования.",
        "en": "Defines an interface by structure (duck typing). A class is compatible if it implements every method of the Protocol, with no explicit inheritance."
      },
      "body": {
        "ru": "Protocol пригодится, когда нужно типизировать чужие классы, которые не переделать под наследование от ABC: совместимость определяется набором методов, а не родословной. По умолчанию это чисто статическая история — isinstance с протоколом бросит TypeError, пока класс не помечен декоратором @runtime_checkable, и даже тогда сверяется только наличие атрибутов, а не их сигнатуры.",
        "en": "Protocol earns its keep when you need to type third-party classes you cannot make inherit from an ABC: compatibility comes from the set of methods, not from ancestry. By default it is a purely static affair — isinstance against a protocol raises TypeError until the protocol is decorated with @runtime_checkable, and even then only the presence of the attributes is checked, never their signatures."
      },
      "syntax": "from typing import Protocol\nclass MyProtocol(Protocol):\n    def method(self) -> type: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.Protocol",
      "version": "3.8",
      "section": "Аннотации и typing",
      "subcat": "протокол",
      "color_group": "typing",
      "aliases": [
        "утиная типизация",
        "структурная типизация",
        "интерфейс без наследования"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import Protocol",
        "class Drawable(Protocol):",
        "    def draw(self) -> None: ...",
        "class Circle:",
        "    def draw(self) -> None: print('O')",
        "class Square:",
        "    def draw(self) -> None: print('[]')",
        "    def render(shape: Drawable) -> None:",
        "        shape.draw()",
        "        render(Circle())  # → O",
        "        render(Square())  # → []",
        "        print(isinstance(Circle(), object))  # → True",
        "        print(hasattr(Circle(), 'draw'))  # → True"
      ],
      "related": [
        "abc.ABC",
        "typing.runtime_checkable",
        "абстрактные-классы"
      ],
      "related_errors": []
    },
    {
      "id": "type_checking",
      "title": "TYPE_CHECKING",
      "kind": "term",
      "summary": {
        "ru": "Булева константа, True только при статическом анализе (mypy, pyright). Позволяет импортировать типы лишь для аннотаций, избегая циклических импортов в рантайме.",
        "en": "A boolean constant that is True only during static analysis (mypy, pyright). Lets you import types for annotations alone, avoiding circular imports at runtime."
      },
      "body": {
        "ru": "Раз импорт под if TYPE_CHECKING в рантайме не выполняется, имя из него не должно попадать в вычисляемое выражение: аннотацию берут в кавычки либо ставят from __future__ import annotations в начале файла, иначе будет NameError при загрузке модуля. Побочный эффект — такие аннотации остаются строками, и всё, что читает типы во время работы программы (typing.get_type_hints, dataclasses, валидаторы), может не суметь их разрешить.",
        "en": "Because the import under if TYPE_CHECKING never runs, the name must not end up in an expression that is actually evaluated: quote the annotation or put from __future__ import annotations at the top of the file, otherwise the module raises NameError on import. The side effect is that such annotations stay strings, so anything that inspects types at runtime — typing.get_type_hints, dataclasses, validation libraries — may fail to resolve them."
      },
      "syntax": "from typing import TYPE_CHECKING\nif TYPE_CHECKING:\n    from module import SomeType",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING",
      "version": "3.5",
      "section": "Аннотации и typing",
      "subcat": "импорт",
      "color_group": "typing",
      "aliases": [
        "импорт только для аннотаций",
        "циклический импорт при типизации"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import TYPE_CHECKING",
        "print(TYPE_CHECKING)  # → False  (в рантайме)",
        "if TYPE_CHECKING:",
        "    from collections import OrderedDict",
        "def process(path: 'str') -> None:",
        "    print(path)  # → строковая аннотация",
        "    process('test')  # → test",
        "    print(bool(TYPE_CHECKING))  # → False"
      ],
      "related": [
        "аннотации-типов-type-hints",
        "typing.ForwardRef",
        "typing.get_type_hints"
      ],
      "related_errors": []
    },
    {
      "id": "typeddict",
      "title": "TypedDict",
      "kind": "term",
      "summary": {
        "ru": "Словарь с фиксированными типизированными ключами. Только для статического анализа; в рантайме — обычный dict.",
        "en": "A dictionary with a fixed set of typed keys. For static analysis only; at runtime it is an ordinary dict."
      },
      "body": {
        "ru": "Главная ловушка: проверок в рантайме нет — лишний ключ, отсутствующий ключ или строка вместо float спокойно пройдут, ошибку покажет только mypy или другой статический анализатор. Это не класс: экземпляр создаётся как обычный литерал словаря, доступ идёт через p['x'], а не p.x, и isinstance с TypedDict не работает. Берите его для готовых dict-данных (JSON от API, конфиг), а если структуру вы создаёте сами — удобнее dataclass или NamedTuple с атрибутами.",
        "en": "The main trap: nothing is checked at runtime — an extra key, a missing key or a string where a float was declared all pass silently, and only mypy or another static checker will complain. It is not a real class: you build it as a plain dict literal, read it as p['x'] rather than p.x, and isinstance against a TypedDict is not allowed. Reach for it when the data already arrives as a dict (JSON from an API, a config); if you design the structure yourself, a dataclass or NamedTuple with real attributes is usually nicer."
      },
      "syntax": "from typing import TypedDict\nclass MyDict(TypedDict):\n    key: type",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.TypedDict",
      "version": "",
      "section": "Аннотации и typing",
      "subcat": "typeddict",
      "color_group": "typing",
      "aliases": [
        "типизированный словарь",
        "словарь с фиксированными ключами"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import TypedDict",
        "class Point(TypedDict):",
        "x: float",
        "y: float",
        "p: Point = {'x': 1.0, 'y': 2.0}",
        "print(p['x'])  # → 1.0",
        "print(p['y'])  # → 2.0",
        "class User(TypedDict, total=False):",
        "name: str",
        "age: int",
        "u: User = {'name': 'Alice'}  # → age необязателен",
        "print(u)  # → {'name': 'Alice'}"
      ],
      "related": [
        "namedtuple",
        "dataclass",
        "typing.is_typeddict"
      ],
      "related_errors": []
    },
    {
      "id": "typevar",
      "title": "TypeVar",
      "kind": "term",
      "summary": {
        "ru": "Переменная типа для создания обобщённых функций и классов. Ограничения задаются через bound= или явный список типов.",
        "en": "A type variable used to write generic functions and classes. Constraints are given with bound= or as an explicit list of types."
      },
      "body": {
        "ru": "Смысл TypeVar появляется только тогда, когда одна и та же переменная встречается в сигнатуре минимум дважды — так вы связываете вход с выходом. Если T стоит в одном месте, толку от него нет, это фактически то же самое, что Any. Различайте два вида ограничений: bound=X разрешает X и любых наследников, а перечисление TypeVar('T', int, str) — только ровно int или ровно str, без промежуточных вариантов. В Python 3.12 то же самое пишется короче: def first[T](lst: list[T]) -> T, без отдельного объявления переменной.",
        "en": "A TypeVar only earns its keep when the same variable appears at least twice in a signature — that is how you tie the return type to the argument type. A T used in just one place buys you nothing and is effectively Any. Note the two kinds of restriction: bound=X accepts X and any subclass, while the value form TypeVar('T', int, str) allows exactly int or exactly str and nothing in between. Python 3.12 offers a shorter spelling, def first[T](lst: list[T]) -> T, with no separate variable to declare."
      },
      "syntax": "from typing import TypeVar\nT = TypeVar('T')\nT = TypeVar('T', bound=SomeType)\nT = TypeVar('T', int, str)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.TypeVar",
      "version": "",
      "section": "Аннотации и typing",
      "subcat": "generics",
      "color_group": "typing",
      "aliases": [
        "переменная типа",
        "параметр типа"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import TypeVar",
        "T = TypeVar('T')",
        "def first(lst: list[T]) -> T:",
        "    return lst[0]",
        "print(first([1, 2, 3]))  # → 1",
        "print(first(['a', 'b']))  # → 'a'",
        "N = TypeVar('N', int, float)",
        "def double(x: N) -> N:",
        "    return x * 2",
        "print(double(3))  # → 6",
        "print(double(1.5))  # → 3.0"
      ],
      "related": [
        "generic-t",
        "typing.TypeVarTuple",
        "typing.ParamSpec"
      ],
      "related_errors": []
    },
    {
      "id": "typing.Any",
      "title": "typing.Any",
      "kind": "term",
      "summary": {
        "ru": "Специальный тип: совместим с любым типом. Отключает проверку типов. Используется при миграции кода или для действительно динамических значений.",
        "en": "A special type compatible with every other type. It turns type checking off. Used when migrating code or for genuinely dynamic values."
      },
      "body": {
        "ru": "Any заразен: всё, что вы достали из Any-значения, тоже становится Any, и проверка молча гаснет в целом куске кода — поэтому одна ленивая аннотация способна обесценить типизацию всего модуля. Если смысл в том, что подойдёт любой объект, но пользоваться им вы собираетесь осторожно, честнее написать object: анализатор тогда заставит сузить тип перед вызовом методов. Any — это осознанное «я отключаю проверку здесь», а не «я пока не придумал тип».",
        "en": "Any is contagious: anything you pull out of an Any value is Any as well, so type checking quietly evaporates across a whole region of code and one lazy annotation can undo the typing of an entire module. If you really mean \"any object at all\" but intend to handle it carefully, object is the honest choice — the checker will then force you to narrow before calling methods. Treat Any as a deliberate \"checking off here\", not as a placeholder for a type you have not worked out yet."
      },
      "syntax": "from typing import Any\nx: Any = ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.Any",
      "version": "",
      "section": "Аннотации и typing",
      "subcat": "any",
      "color_group": "typing",
      "aliases": [
        "Any"
      ],
      "keywords": [
        "typing.Any",
        "Any"
      ],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import Any",
        "def log(value: Any) -> None: print(value)",
        "log(42)  # → 42",
        "log('text')  # → text",
        "log([1, 2, 3])  # → [1, 2, 3]",
        "data: list[Any] = [1, 'a', None, 3.14]  # смешанный список",
        "print(type(data[0]))  # → <class 'int'>",
        "print(Any)  # → typing.Any"
      ],
      "related": [
        "аннотации-типов-type-hints",
        "object",
        "typing.cast"
      ],
      "related_errors": []
    },
    {
      "id": "union-x-y-x-y",
      "title": "Union[X, Y] / X | Y",
      "kind": "term",
      "summary": {
        "ru": "Union[X, Y] — значение может быть типа X или Y. С Python 3.10+ поддерживается сокращённая запись X | Y.",
        "en": "Union[X, Y] — the value may be of type X or of type Y. Since Python 3.10 the shorthand X | Y is supported."
      },
      "body": {
        "ru": "Классическая путаница: Optional[X] (то же самое, что X | None) говорит только о том, что значением может быть None, и никак не связан с необязательностью аргумента — за неё отвечает значение по умолчанию. Аннотация сама ничего не проверяет: анализатор просто потребует сузить тип, например через isinstance, прежде чем вы вызовете метод, который есть лишь у одной из веток. Запись X | Y работает в аннотациях и в isinstance с Python 3.10; в более старых версиях нужен Union из typing.",
        "en": "A classic mix-up: Optional[X], which is just X | None, only says the value may be None — it has nothing to do with the argument being optional, that is what a default value is for. The annotation checks nothing by itself; the type checker will simply demand that you narrow, typically with isinstance, before calling a method that exists on only one branch. The X | Y spelling works in annotations and in isinstance from Python 3.10 onward; older versions need Union from typing."
      },
      "syntax": "from typing import Union\nUnion[int, str]   # int или str\n# Python 3.10+: int | str",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.Union",
      "version": "3.10",
      "section": "Аннотации и typing",
      "subcat": "объединение",
      "color_group": "typing",
      "aliases": [
        "объединение типов",
        "несколько допустимых типов аргумента"
      ],
      "keywords": [
        "typing.Union"
      ],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import Union",
        "def double(x: Union[int, float]) -> Union[int, float]:",
        "    return x * 2",
        "print(double(3))  # → 6",
        "print(double(1.5))  # → 3.0",
        "def parse(val: int | str) -> str:",
        "    return str(val)",
        "print(parse(42))  # → '42'",
        "print(parse('hi'))  # → 'hi'"
      ],
      "related": [
        "optional-x-x-none",
        "literal-v1-v2",
        "typing.get_args"
      ],
      "related_errors": []
    },
    {
      "id": "abs",
      "title": "abs()",
      "kind": "function",
      "summary": {
        "ru": "Встроенная функция, возвращающая абсолютное значение (модуль) числа. Работает с int, float и complex.",
        "en": "Built-in function that returns the absolute value (magnitude) of a number. Works with int, float and complex."
      },
      "body": {
        "ru": "Тип результата следует за аргументом: int даёт int, float — float, а complex — float, потому что это длина вектора, а не «число без знака». Привычная конструкция abs(a - b) < 1e-9 для сравнения дробных чисел надёжна только около единицы: на больших значениях такой абсолютный допуск всегда провалится, поэтому в общем случае берите math.isclose() с относительным допуском.",
        "en": "The result type follows the argument: int gives int, float gives float, and complex gives float, since that is a vector length rather than a sign-stripped number. The familiar abs(a - b) < 1e-9 idiom for comparing floats only holds near 1: at large magnitudes such a fixed absolute tolerance always fails, so reach for math.isclose() with its relative tolerance instead."
      },
      "syntax": "abs(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#abs",
      "version": "",
      "section": "Арифметика и операторы",
      "subcat": "числа",
      "color_group": "op",
      "aliases": [
        "модуль числа",
        "абсолютное значение",
        "убрать минус у числа"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "print(abs(-7))  # → 7",
        "print(abs(3.14))  # → 3.14",
        "print(abs(-0.0))  # → 0.0",
        "print(abs(3 + 4j))  # → 5.0 (модуль комплексного числа)",
        "nums = [-3, 1, -5, 2]",
        "print(max(nums, key=abs))  # → -5 (наибольший по модулю)"
      ],
      "related": [
        "math.fabs",
        "round",
        "math.copysign"
      ],
      "related_errors": []
    },
    {
      "id": "divmod",
      "title": "divmod()",
      "kind": "function",
      "summary": {
        "ru": "Встроенная функция, возвращающая кортеж (частное, остаток) — результат целочисленного деления и остатка. Эффективнее двух отдельных операций.",
        "en": "Built-in function that returns the tuple (quotient, remainder) — the results of floor division and of the modulo. More efficient than two separate operations."
      },
      "body": {
        "ru": "Главный сюрприз — отрицательные числа: деление округляется вниз, к минус бесконечности, поэтому divmod(-7, 2) даёт (-4, 1), а не (-3, -1), как привыкли в C или Java; знак остатка всегда совпадает со знаком делителя. Брать divmod стоит там, где обе части нужны сразу — перевод секунд в часы и минуты, разбивка на страницы; ради одного значения короче и понятнее просто // или %.",
        "en": "The surprise is negative operands: division floors toward minus infinity, so divmod(-7, 2) is (-4, 1), not (-3, -1) as in C or Java, and the remainder always carries the sign of the divisor. Reach for divmod when you genuinely need both parts at once (seconds to hours and minutes, paging); for a single value plain // or % reads better."
      },
      "syntax": "divmod(a, b) -> (a // b, a % b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#divmod",
      "version": "",
      "section": "Арифметика и операторы",
      "subcat": "числа",
      "color_group": "op",
      "aliases": [
        "частное и остаток сразу",
        "деление с остатком"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "print(divmod(10, 3))  # → (3, 1)",
        "h, m = divmod(150, 60)",
        "print(f\"{h} ч {m} мин\")  # → 2 ч 30 мин",
        "pages, rem = divmod(105, 10)",
        "print(f\"{pages} страниц, остаток {rem}\")  # → 10 страниц, остаток 5",
        "q, r = divmod(17, 5)",
        "print(f\"17 = 5 * {q} + {r}\")  # → 17 = 5 * 3 + 2",
        "print(divmod(-7, 2))  # → (-4, 1) (знак остатка — как у делителя)",
        "print(divmod(3.5, 1.5))  # → (2.0, 0.5) (работает и с float)"
      ],
      "related": [
        "целочисленное-деление",
        "остаток",
        "обработка-цифр-числа"
      ],
      "related_errors": [
        "ZeroDivisionError"
      ]
    },
    {
      "id": "pow",
      "title": "pow()",
      "kind": "function",
      "summary": {
        "ru": "Встроенная функция возведения в степень. С двумя аргументами — как **; с тремя (base, exp, mod) — модульное возведение в степень, эффективно для больших чисел.",
        "en": "Built-in exponentiation function. With two arguments it is the same as **; with three (base, exp, mod) it does modular exponentiation, which is efficient for large numbers."
      },
      "body": {
        "ru": "Три аргумента — это не просто сокращение записи: pow(a, b, m) берёт остаток на каждом шаге и никогда не строит гигантское промежуточное число, тогда как (a ** b) % m сначала честно возведёт в степень и на большом b подвесит программу. Трёхаргументная форма работает только с целыми, а отрицательный показатель в ней разрешён лишь с Python 3.8 (тогда это обратный элемент по модулю). С двумя аргументами pow(2, -1) вернёт float 0.5, а не целое.",
        "en": "The three-argument form is not just shorthand: pow(a, b, m) reduces modulo m at every step and never materialises the huge intermediate value, while (a ** b) % m computes the full power first and will hang on a large exponent. It works with ints only, and a negative exponent there is allowed only since Python 3.8, where it yields a modular inverse. With two arguments, pow(2, -1) gives the float 0.5, not an int."
      },
      "syntax": "pow(base, exp[, mod])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#pow",
      "version": "",
      "section": "Арифметика и операторы",
      "subcat": "числа",
      "color_group": "op",
      "aliases": [
        "возвести в степень функцией",
        "степень по модулю"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "print(pow(2, 10))  # → 1024",
        "print(pow(3, 3))  # → 27",
        "print(pow(2, -1))  # → 0.5 (отрицательный показатель)",
        "print(pow(2, 10, 1000))  # → 24 (2**10 % 1000, модульная арифметика)",
        "print(pow(3, 100, 7))  # → 4 (быстрое вычисление по модулю)",
        "print(pow(4, 0.5))  # → 2.0 (дробная степень — корень)"
      ],
      "related": [
        "степень",
        "math.pow",
        "math.sqrt"
      ],
      "related_errors": [
        "ZeroDivisionError"
      ]
    },
    {
      "id": "round",
      "title": "round()",
      "kind": "function",
      "summary": {
        "ru": "Встроенная функция округления числа. С одним аргументом возвращает int; с ndigits — округляет до указанного количества знаков после запятой. Использует банковское (к ближайшему чётному) округление.",
        "en": "Built-in rounding function. With one argument it returns an int; with ndigits it rounds to that many decimal places. It uses banker's rounding (to the nearest even)."
      },
      "body": {
        "ru": "round(2.675, 2) вернёт 2.67, и виновато тут не банковское округление, а двоичный float: сохранённое значение чуть меньше десятичной записи 2.675. Для денег и отчётов берите decimal.Decimal и quantize(). Ещё тонкость: round(x) без ndigits даёт int, а round(x, 0) — float (2.0), так что «до целого» и «до нуля знаков» — не одно и то же.",
        "en": "round(2.675, 2) yields 2.67, and banker's rounding is not the culprit — the binary float actually stored is slightly below the decimal 2.675. For money use decimal.Decimal with quantize(). Also note that round(x) without ndigits returns an int while round(x, 0) returns a float (2.0), so \"round to an integer\" and \"round to zero digits\" are different requests."
      },
      "syntax": "round(number[, ndigits])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#round",
      "version": "",
      "section": "Арифметика и операторы",
      "subcat": "числа",
      "color_group": "op",
      "aliases": [
        "округление",
        "округлить до знаков после запятой",
        "банковское округление"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "print(round(3.7))  # → 4",
        "print(round(3.14159, 2))  # → 3.14",
        "print(round(2.5))  # → 2 (банковское округление к чётному)",
        "print(round(3.5))  # → 4 (банковское округление к чётному)",
        "print(round(12345, -2))  # → 12300 (до сотен)",
        "print(round(0.1 + 0.2, 10))  # → 0.3 (компенсация float погрешности)"
      ],
      "related": [
        "math.floor",
        "math.ceil",
        "math.trunc",
        "f-строки"
      ],
      "related_errors": []
    },
    {
      "id": "walrus-operator",
      "title": "Walrus operator :=",
      "kind": "term",
      "summary": {
        "ru": "Оператор «морж» (:=) — присваивает значение переменной прямо внутри выражения (Python 3.8+). Удобен в условиях while, в list comprehension и в if.",
        "en": "The walrus operator (:=) assigns a value to a variable inside an expression (Python 3.8+). Handy in while conditions, in comprehensions and in if."
      },
      "body": {
        "ru": "Приоритет у := ниже почти всех операторов, поэтому в условиях его почти всегда оборачивают в скобки: if (n := len(s)) > 10. Отдельной строкой вместо обычного = он не работает — n := 10 на верхнем уровне это SyntaxError. Главная выгода не в краткости, а в том, что значение вычисляется один раз и не приходится звать одну и ту же функцию дважды: в условии и в теле.",
        "en": "The := operator binds more loosely than nearly everything else, so inside a condition it is almost always wrapped in parentheses: if (n := len(s)) > 10. It cannot replace a plain assignment statement — a bare line n := 10 is a SyntaxError. The real win is not brevity but computing a value once instead of calling the same function twice, in the test and again in the body."
      },
      "syntax": "variable := expression",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#assignment-expressions",
      "version": "3.8",
      "section": "Арифметика и операторы",
      "subcat": "операторы",
      "color_group": "op",
      "aliases": [
        "оператор морж",
        "присваивание внутри выражения",
        "двоеточие равно"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "data = [1, 2, 3, 4]",
        "while chunk := data[:2]:",
        "    data = data[2:]; print(chunk)  # → [1, 2]  затем [3, 4]",
        "    nums = [1, -2, 3, -4]",
        "    pos = [y for x in nums if (y := x * 2) > 0]",
        "    print(pos)  # → [2, 6]",
        "    if n := len(\"hello\"): print(n)  # → 5",
        "    s = \"abcdef\"",
        "    if m := len(s) > 3: print(\"длинная\")  # → длинная"
      ],
      "related": [
        "while",
        "условный-list-comprehension",
        "переменные"
      ],
      "related_errors": []
    },
    {
      "id": "вычитание",
      "title": "- вычитание",
      "kind": "term",
      "summary": {
        "ru": "Бинарный оператор вычитания. Унарный минус меняет знак числа.",
        "en": "The binary subtraction operator. The unary minus changes the sign of a number."
      },
      "body": {
        "ru": "Унарный минус слабее возведения в степень: -2 ** 2 читается как -(2 ** 2) и даёт -4, а не 4 — если хочется квадрат отрицательного числа, скобки обязательны. С float разность почти никогда не точна: 0.3 - 0.1 не равно ровно 0.2, поэтому сравнивать результат вычитания через == нельзя, для этого есть math.isclose().",
        "en": "Unary minus binds more loosely than exponentiation: -2 ** 2 parses as -(2 ** 2) and gives -4, not 4, so wrap the base in parentheses if you meant the square of a negative number. With floats a difference is almost never exact — 0.3 - 0.1 is not exactly 0.2 — so never compare the result of a subtraction with ==; use math.isclose() instead."
      },
      "syntax": "a - b | -a",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations",
      "version": "3.5",
      "section": "Арифметика и операторы",
      "subcat": "арифметика",
      "color_group": "op",
      "aliases": [
        "минус",
        "унарный минус",
        "разность чисел"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(10 - 3)  # → 7",
        "print(3.5 - 1.1)  # → 2.4 (float)",
        "x = 5; print(-x)  # → -5 (унарный минус)",
        "print(0 - 100)  # → -100",
        "from datetime import date",
        "delta = date(2025, 12, 31) - date(2025, 1, 1)",
        "print(delta.days)  # → 364 (разница дат)"
      ],
      "related": [
        "сложение",
        "операторы-присваивания",
        "приоритет-операций"
      ],
      "related_errors": []
    },
    {
      "id": "деление",
      "title": "/ деление",
      "kind": "term",
      "summary": {
        "ru": "Оператор деления. Всегда возвращает float, даже при делении двух целых. Деление на ноль вызывает ZeroDivisionError.",
        "en": "The division operator. It always returns a float, even when both operands are integers. Division by zero raises ZeroDivisionError."
      },
      "body": {
        "ru": "Результат всегда float, а у float всего 53 бита мантиссы: на больших целых младшие разряды тихо теряются, а совсем крупные числа вроде 10**400 / 2 падают с OverflowError — там, где нужен точный целый ответ, бери // и не превращай float обратно в int. Деление на 0.0 тоже даёт ZeroDivisionError, а не inf, как в C или NumPy.",
        "en": "The result is always a float, and a float has only 53 bits of mantissa: with large integers the low digits are silently lost, and truly huge values such as 10**400 / 2 raise OverflowError — when you need an exact integer answer use // instead of dividing and converting back. Dividing by 0.0 also raises ZeroDivisionError rather than producing inf as C or NumPy would."
      },
      "syntax": "a / b -> float",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations",
      "version": "3.5",
      "section": "Арифметика и операторы",
      "subcat": "арифметика",
      "color_group": "op",
      "aliases": [
        "дробное деление",
        "частное",
        "поделить числа"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(10 / 2)  # → 5.0 (всегда float)",
        "print(7 / 2)  # → 3.5",
        "print(1 / 3)  # → 0.3333333333333333",
        "print(type(6 / 2))  # → <class 'float'>",
        "try:",
        "    print(1 / 0)",
        "except ZeroDivisionError:",
        "    print(\"Деление на ноль!\")  # → Деление на ноль!",
        "    print(9 / 3)  # → 3.0"
      ],
      "related": [
        "целочисленное-деление",
        "остаток",
        "divmod",
        "zerodivisionerror"
      ],
      "related_errors": []
    },
    {
      "id": "операторы-присваивания",
      "title": "Операторы присваивания",
      "kind": "term",
      "summary": {
        "ru": "Составные операторы присваивания: +=, -=, *=, /=, //=, %=, **=. Сочетают операцию с присваиванием, изменяя переменную на месте.",
        "en": "The augmented assignment operators: +=, -=, *=, /=, //=, %=, **=. They combine an operation with an assignment, updating the variable in place."
      },
      "body": {
        "ru": "Для изменяемых объектов += правит объект на месте: lst += [1] это фактически extend, и любое другое имя, ссылающееся на тот же список, увидит изменение, тогда как lst = lst + [1] создаёт новый список. У int, str и кортежей менять на месте нечего, поэтому там всегда получается новый объект, а старый просто теряет ссылку. Операторов ++ и -- в Python нет: ++x — это два унарных плюса и молчаливое ничего, а x++ вообще синтаксическая ошибка.",
        "en": "For mutable objects += edits the object in place: lst += [1] is effectively extend, so every other name bound to that same list sees the change, while lst = lst + [1] builds a new list. With int, str and tuples there is nothing to mutate, so you always get a fresh object and the old one simply loses a reference. Python has no ++ or -- : ++x is just two unary pluses that do nothing, and x++ is a syntax error."
      },
      "syntax": "x op= value  # эквивалент x = x op value",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/simple_stmts.html#augmented-assignment-statements",
      "version": "",
      "section": "Арифметика и операторы",
      "subcat": "присваивание",
      "color_group": "op",
      "aliases": [
        "плюс равно",
        "сокращённая запись присваивания",
        "увеличить переменную"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "x = 10; x += 5; print(x)  # → 15",
        "x = 10; x -= 3; print(x)  # → 7",
        "x = 4; x *= 3; print(x)  # → 12",
        "x = 10; x /= 4; print(x)  # → 2.5",
        "x = 10; x //= 3; print(x)  # → 3",
        "x = 10; x %= 3; print(x)  # → 1",
        "x = 2; x **= 8; print(x)  # → 256"
      ],
      "related": [
        "переменные",
        "сложение",
        "walrus-operator"
      ],
      "related_errors": []
    },
    {
      "id": "остаток",
      "title": "% остаток",
      "kind": "term",
      "summary": {
        "ru": "Оператор получения остатка от деления (modulo). Знак результата совпадает со знаком делителя. Широко используется для проверки чётности и цикличности.",
        "en": "The remainder (modulo) operator. The sign of the result follows the sign of the divisor. Widely used to test parity and periodicity."
      },
      "body": {
        "ru": "Из правила знака следует неочевидное: -123 % 10 равно 7, а не 3, поэтому привычный приём «последняя цифра через % 10» на отрицательных числах ломается — считайте от abs(n). Если нужны и частное, и остаток, divmod(a, b) отдаёт оба за один проход, а b, равное нулю, даёт ZeroDivisionError так же, как обычное деление.",
        "en": "The sign rule has a surprising consequence: -123 % 10 is 7, not 3, so the familiar \"last digit via % 10\" trick breaks on negative numbers — take abs(n) first. When you need both the quotient and the remainder, divmod(a, b) returns them in one step, and a zero divisor raises ZeroDivisionError just like ordinary division."
      },
      "syntax": "a % b",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations",
      "version": "3.5",
      "section": "Арифметика и операторы",
      "subcat": "арифметика",
      "color_group": "op",
      "aliases": [
        "остаток от деления",
        "по модулю",
        "проверка на чётность"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(10 % 3)  # → 1",
        "print(7 % 2)  # → 1 (нечётное число)",
        "print(8 % 2)  # → 0 (чётное число)",
        "print(123 % 10)  # → 3 (последняя цифра)",
        "seconds = 3661; print(seconds % 60)  # → 1 (секунды)",
        "print(15 % 5)  # → 0 (кратно)",
        "try:",
        "    print(5 % 0)",
        "except ZeroDivisionError:",
        "    print(\"Деление на ноль\")  # → Деление на ноль"
      ],
      "related": [
        "целочисленное-деление",
        "divmod",
        "деление",
        "math.fmod"
      ],
      "related_errors": []
    },
    {
      "id": "побитовые-операторы",
      "title": "Побитовые операторы",
      "kind": "term",
      "summary": {
        "ru": "Операторы для работы с числами на уровне отдельных бит: & (AND), | (OR), ^ (XOR), ~ (NOT/инверсия), << (сдвиг влево), >> (сдвиг вправо).",
        "en": "Operators that work on numbers bit by bit: & (AND), | (OR), ^ (XOR), ~ (NOT/inversion), << (left shift), >> (right shift)."
      },
      "body": {
        "ru": "Первым кусается приоритет: x & 1 == 0 разбирается как x & (1 == 0), потому что сравнение связывает сильнее битовых операторов — нужны скобки. Целые в Python неограниченной длины и ведут себя как дополнительный код, поэтому ~x всегда равно -x - 1, сдвиг влево не переполняется (растёт число, а не обрезается), а >> у отрицательных сохраняет знак. Отрицательное число разрядов в сдвиге — ValueError.",
        "en": "Precedence bites first: x & 1 == 0 parses as x & (1 == 0), because comparisons bind tighter than bitwise operators — add parentheses. Python ints are arbitrary precision and behave as two's complement, so ~x is always -x - 1, a left shift never overflows (the number just grows), and >> keeps the sign of a negative value. A negative shift count raises ValueError."
      },
      "syntax": "a & b  a | b  a ^ b  ~a  a << n  a >> n",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bitwise-operations-on-integer-types",
      "version": "",
      "section": "Арифметика и операторы",
      "subcat": "битовые",
      "color_group": "op",
      "aliases": [
        "сдвиг битов",
        "исключающее или",
        "битовая маска"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(0b1100 & 0b1010)  # → 8   (0b1000)",
        "print(0b1100 | 0b1010)  # → 14  (0b1110)",
        "print(0b1100 ^ 0b1010)  # → 6   (0b0110)",
        "print(~5)               # → -6",
        "print(1 << 3)           # → 8",
        "print(16 >> 2)          # → 4"
      ],
      "related": [
        "битовые-операции",
        "bin",
        "int.bit_length",
        "int.bit_count"
      ],
      "related_errors": []
    },
    {
      "id": "приоритет-операций",
      "title": "Приоритет операций",
      "kind": "term",
      "summary": {
        "ru": "Порядок вычисления выражений: ** > унарный минус > * / // % > + -. Скобки повышают приоритет. Помнить: PEMDAS / BODMAS.",
        "en": "The order in which an expression is evaluated: ** > unary minus > * / // % > + -. Parentheses raise the precedence. Remember PEMDAS / BODMAS."
      },
      "body": {
        "ru": "Таблица не заканчивается на арифметике: сравнения стоят ниже неё, а not, and и or — ещё ниже, поэтому a + b > c and d читается как ((a + b) > c) and d. Возведение в степень связывает крепче унарного минуса слева, но не справа — запись 2 ** -1 законна и даёт 0.5. Учить всю таблицу наизусть незачем: в смешанном выражении скобки обходятся дешевле отладки.",
        "en": "The table does not stop at arithmetic: comparisons sit below it and not/and/or sit lower still, so a + b > c and d parses as ((a + b) > c) and d. Exponentiation binds tighter than a unary minus on its left but not on its right, which is why 2 ** -1 is legal and yields 0.5. There is no need to memorise the whole table — in a mixed expression parentheses are cheaper than debugging."
      },
      "syntax": "( ) → ** → -x → * / // % → + -",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#operator-precedence",
      "version": "",
      "section": "Арифметика и операторы",
      "subcat": "приоритет",
      "color_group": "op",
      "aliases": [
        "порядок вычислений",
        "порядок действий в выражении",
        "скобки в выражении"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(2 + 3 * 4)  # → 14 (сначала умножение)",
        "print((2 + 3) * 4)  # → 20 (скобки первыми)",
        "print(2 ** 3 ** 2)  # → 512 (правоассоциативность: 3**2=9, 2**9)",
        "print(-2 ** 2)  # → -4 (сначала степень, потом минус)",
        "print(10 - 3 + 2)  # → 9 (слева направо)",
        "print(10 % 3 + 1)  # → 2 (% раньше +)"
      ],
      "related": [
        "степень",
        "and-or-not",
        "операторы-сравнения",
        "цепочка-сравнений"
      ],
      "related_errors": []
    },
    {
      "id": "сложение",
      "title": "+ сложение",
      "kind": "term",
      "summary": {
        "ru": "Бинарный оператор сложения. Для чисел — арифметическое сложение; для строк и списков — конкатенация; для множеств — объединение.",
        "en": "The binary addition operator. For numbers it is arithmetic addition; for strings and lists, concatenation; for sets, union."
      },
      "body": {
        "ru": "Неявных преобразований нет: \"5\" + 1 сразу даёт TypeError, а список нельзя сложить с кортежем — типы приводят вручную. Для последовательностей + всегда создаёт новый объект и копирует оба операнда, поэтому склейка в цикле выходит квадратичной по времени: строки собирайте через \"\".join(), к спискам добавляйте append() или extend().",
        "en": "There is no implicit conversion: \"5\" + 1 raises TypeError immediately, and a list cannot be added to a tuple — convert explicitly. For sequences + always builds a new object and copies both operands, so gluing in a loop is quadratic: build strings with \"\".join() and grow lists with append() or extend()."
      },
      "syntax": "a + b",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations",
      "version": "3.5",
      "section": "Арифметика и операторы",
      "subcat": "арифметика",
      "color_group": "op",
      "aliases": [
        "плюс",
        "сложить числа",
        "сумма двух чисел"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(3 + 5)  # → 8",
        "print(\"Привет\" + \" \" + \"мир\")  # → Привет мир",
        "print([1, 2] + [3, 4])  # → [1, 2, 3, 4]",
        "print(1.5 + 2.5)  # → 4.0",
        "print(10 ** 18 + 10 ** 18)  # → 2000000000000000000 (big int)",
        "x = 5; print(+x)  # → 5 (унарный плюс)"
      ],
      "related": [
        "вычитание",
        "конкатенация-строк",
        "объединение-повторение-списков",
        "операторы-присваивания"
      ],
      "related_errors": []
    },
    {
      "id": "степень",
      "title": "** степень",
      "kind": "term",
      "summary": {
        "ru": "Оператор возведения в степень. Выполняется справа налево (правоассоциативен). Поддерживает дробные степени (корни) и отрицательные показатели.",
        "en": "The exponentiation operator. It is evaluated right to left (right-associative). It supports fractional exponents (roots) and negative ones."
      },
      "body": {
        "ru": "Правоассоциативность важнее, чем кажется: 2 ** 3 ** 2 — это 2 ** 9, то есть 512, а не 64. Целое на выходе получается только при неотрицательном целом показателе: 2 ** -1 даёт float 0.5, а дробная степень отрицательного числа уходит в комплексные. Для остатка по модулю берите pow(a, b, m) — это несравнимо быстрее, чем (a ** b) % m с гигантским промежуточным числом.",
        "en": "Right-associativity matters more than it looks: 2 ** 3 ** 2 is 2 ** 9, that is 512, not 64. You get an int back only for a non-negative integer exponent — 2 ** -1 is the float 0.5, and a fractional power of a negative number lands in complex numbers. For modular arithmetic use pow(a, b, m); it is far faster than (a ** b) % m, which first builds a giant intermediate value."
      },
      "syntax": "a ** b",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#the-power-operator",
      "version": "3.5",
      "section": "Арифметика и операторы",
      "subcat": "арифметика",
      "color_group": "op",
      "aliases": [
        "возведение в степень",
        "квадрат числа",
        "дробная степень"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(2 ** 10)  # → 1024",
        "print(3 ** 3)  # → 27",
        "print(16 ** 0.5)  # → 4.0 (квадратный корень)",
        "print(27 ** (1/3))  # → 3.0 (кубический корень)",
        "print(2 ** -1)  # → 0.5 (отрицательная степень)",
        "print(2 ** 100)  # → большое число (Python big int)"
      ],
      "related": [
        "pow",
        "math.pow",
        "math.sqrt",
        "приоритет-операций"
      ],
      "related_errors": []
    },
    {
      "id": "умножение",
      "title": "* умножение",
      "kind": "term",
      "summary": {
        "ru": "Бинарный оператор умножения. Для строк и списков — повторение.",
        "en": "The binary multiplication operator. For strings and lists it means repetition."
      },
      "body": {
        "ru": "Главная ловушка — вложенные списки: [[0] * 3] * 3 не копирует внутренний список, а трижды кладёт ссылку на один и тот же, поэтому запись в одну «строку» меняет сразу все. Матрицу собирают генератором списка. Ноль и отрицательный множитель дают пустую последовательность, а не ошибку.",
        "en": "The classic trap is nesting: [[0] * 3] * 3 does not copy the inner list, it stores the very same one three times, so writing into one row changes every row. Build matrices with a list comprehension instead. A zero or negative factor yields an empty sequence rather than an error."
      },
      "syntax": "a * b",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations",
      "version": "3.5",
      "section": "Арифметика и операторы",
      "subcat": "арифметика",
      "color_group": "op",
      "aliases": [
        "произведение чисел",
        "умножить числа"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(6 * 7)  # → 42",
        "print(\"ab\" * 3)  # → ababab",
        "print([0] * 5)  # → [0, 0, 0, 0, 0]",
        "print(2.5 * 4)  # → 10.0",
        "print(3 * 3 * 3)  # → 27 (куб без оператора **)",
        "matrix = [[0] * 3 for _ in range(3)]  # 3x3 нулевая матрица",
        "print(matrix[0])  # → [0, 0, 0]"
      ],
      "related": [
        "деление",
        "повторение-строки",
        "объединение-повторение-списков"
      ],
      "related_errors": []
    },
    {
      "id": "целочисленное-деление",
      "title": "// целочисленное деление",
      "kind": "term",
      "summary": {
        "ru": "Оператор целочисленного (этажного) деления. Возвращает наибольшее целое, не превышающее частное. Для float аргументов возвращает float.",
        "en": "The floor division operator. It returns the largest integer not greater than the quotient. With float operands it returns a float."
      },
      "body": {
        "ru": "Округление идёт к минус бесконечности, а не к нулю, как в C или Java: -7 // 2 даёт -4, тогда как int(-7 / 2) даёт -3. Пара // и % согласована — a == (a // b) * b + a % b, поэтому знак остатка совпадает со знаком делителя. Деление на ноль — ZeroDivisionError; на очень больших целых // остаётся точным, а math.floor(a / b) сначала гоняет числа через float и теряет точность.",
        "en": "Rounding goes toward minus infinity, not toward zero as in C or Java: -7 // 2 is -4, while int(-7 / 2) is -3. // and % stay consistent — a == (a // b) * b + a % b — which is why the remainder takes the sign of the divisor. A zero divisor raises ZeroDivisionError, and on very large integers // stays exact whereas math.floor(a / b) pushes the numbers through float and loses precision."
      },
      "syntax": "a // b",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations",
      "version": "3.5",
      "section": "Арифметика и операторы",
      "subcat": "арифметика",
      "color_group": "op",
      "aliases": [
        "деление нацело",
        "деление без остатка",
        "отбросить дробную часть при делении"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(7 // 2)  # → 3",
        "print(-7 // 2)  # → -4 (округление к меньшему, не к нулю!)",
        "print(7.5 // 2)  # → 3.0 (float аргумент → float результат)",
        "print(100 // 10)  # → 10",
        "seconds = 3661",
        "print(seconds // 3600)  # → 1 (полные часы)",
        "print(seconds % 3600 // 60)  # → 1 (минуты)"
      ],
      "related": [
        "деление",
        "остаток",
        "divmod",
        "math.floor"
      ],
      "related_errors": []
    },
    {
      "id": "цепочка-сравнений",
      "title": "Цепочка сравнений",
      "kind": "term",
      "summary": {
        "ru": "Python позволяет записывать несколько сравнений подряд в одном выражении. Выражение 1 < x < 10 эквивалентно (1 < x) and (x < 10) и вычисляется слева направо.",
        "en": "Python lets you write several comparisons in a row in one expression. 1 < x < 10 is equivalent to (1 < x) and (x < 10) and is evaluated left to right."
      },
      "body": {
        "ru": "Средний операнд вычисляется ровно один раз, и цепочка коротко замыкается — если первое сравнение ложно, второе даже не выполнится. Отсюда ловушка: x != y != z не означает, что все три различны, это лишь два соседних неравенства, и x с z спокойно могут совпадать.",
        "en": "The middle operand is evaluated exactly once, and the chain short-circuits: if the first comparison is false, the second is never run. Hence the classic trap — x != y != z does not mean all three differ, it only checks neighbouring pairs, so x and z may well be equal."
      },
      "syntax": "a < b < c  a == b == c  a <= b >= c",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#comparisons",
      "version": "3.8",
      "section": "Арифметика и операторы",
      "subcat": "сравнение",
      "color_group": "op",
      "aliases": [
        "двойное неравенство",
        "число в диапазоне",
        "несколько сравнений подряд"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "x = 5",
        "print(1 < x < 10)      # → True",
        "print(1 < x < 4)       # → False",
        "print(1 == 1 == 1)     # → True",
        "print(1 == 1 == 2)     # → False",
        "print(0 <= x <= 100)   # → True",
        "print(10 > x > 0)      # → True"
      ],
      "related": [
        "операторы-сравнения",
        "and-or-not",
        "приоритет-операций"
      ],
      "related_errors": []
    },
    {
      "id": "async-def-await",
      "title": "async def / await",
      "kind": "term",
      "summary": {
        "ru": "async def объявляет корутину. await приостанавливает выполнение до результата другой корутины.",
        "en": "async def declares a coroutine. await suspends execution until another coroutine produces its result."
      },
      "body": {
        "ru": "Вызов greet('Alice') без await ничего не выполняет — он возвращает объект-корутину, а при завершении программы вы получите предупреждение \"coroutine was never awaited\". Само по себе await доступно только внутри async def, а верхний уровень программы запускается через asyncio.run(main()). И асинхронность здесь не про параллельность: цикл событий один и однопоточный, поэтому обычный блокирующий вызов вроде time.sleep() или синхронного requests замораживает сразу все задачи.",
        "en": "Calling greet('Alice') without await runs nothing — it just builds a coroutine object, and at exit you get the warning that a coroutine was never awaited. await itself is only legal inside async def, and the top of the program is started with asyncio.run(main()). Also, async is not parallelism: the event loop is a single thread, so one ordinary blocking call such as time.sleep() or synchronous requests freezes every task at once."
      },
      "syntax": "async def name() -> T:\n    result = await other_coroutine()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#coroutine-function-definition",
      "version": "",
      "section": "Асинхронное программирование",
      "subcat": "корутины",
      "color_group": "iter",
      "aliases": [
        "корутина",
        "асинхронная функция",
        "дождаться результата корутины"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "import asyncio",
        "async def greet(name):",
        "    await asyncio.sleep(0)",
        "    return f'hello, {name}'",
        "async def main():",
        "    msg = await greet('Alice')",
        "    print(msg) # → hello, Alice",
        "    asyncio.run(main())",
        "    # coroutine без await не выполняется автоматически"
      ],
      "related": [
        "asyncio.run",
        "asyncio.gather",
        "async-for-async-with"
      ],
      "related_errors": []
    },
    {
      "id": "async-for-async-with",
      "title": "async for / async with",
      "kind": "term",
      "summary": {
        "ru": "async for итерирует по асинхронному итератору. async with использует асинхронный контекстный менеджер.",
        "en": "async for iterates over an asynchronous iterator. async with uses an asynchronous context manager."
      },
      "body": {
        "ru": "Это не ускорители, а способ дождаться того, что приходит порциями: async for всё равно идёт по одному элементу за раз, просто между шагами отдаёт управление event loop. Обе формы работают только внутри async def и только с объектами, у которых есть __aiter__/__anext__ (или __aenter__/__aexit__) — обычный список или синхронный with-объект дадут TypeError. Если нужна именно параллельность, а не последовательное ожидание, собирайте корутины и отдавайте их gather или TaskGroup.",
        "en": "Neither form makes anything parallel: async for still walks items one at a time, it just yields control back to the event loop between steps. Both are allowed only inside an async def, and only for objects implementing __aiter__/__anext__ (or __aenter__/__aexit__) — a plain list or a sync context manager raises TypeError. When you actually want things to overlap, collect the coroutines and hand them to gather or a TaskGroup."
      },
      "syntax": "async for item in aiter: ...\nasync with ctx_manager() as obj: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#the-async-for-statement",
      "version": "",
      "section": "Асинхронное программирование",
      "subcat": "итерация",
      "color_group": "iter",
      "aliases": [
        "асинхронный цикл",
        "асинхронный контекстный менеджер",
        "перебор асинхронного итератора"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "import asyncio",
        "async def agen():",
        "    for i in range(3):",
        "        await asyncio.sleep(0)",
        "        yield i",
        "async def main():",
        "    async for val in agen():",
        "        print(val) # → 0, 1, 2",
        "        asyncio.run(main())"
      ],
      "related": [
        "async-def-await",
        "contextlib.asynccontextmanager",
        "collections.abc.AsyncIterator"
      ],
      "related_errors": []
    },
    {
      "id": "asyncio.create_task",
      "title": "asyncio.create_task()",
      "kind": "function",
      "summary": {
        "ru": "Оборачивает корутину в Task, запуская её в фоне event loop. Задача стартует немедленно, не дожидаясь await.",
        "en": "Wraps a coroutine in a Task, running it in the background of the event loop. The task starts immediately, without waiting for an await."
      },
      "body": {
        "ru": "Корутина не стартует прямо в точке вызова: задача лишь ставится в очередь event loop и начинает выполняться, когда текущая корутина сама дойдёт до ближайшего await. Вторая ловушка — ссылка: loop держит на задачу только слабую ссылку, поэтому task, никуда не сохранённый, может быть собран сборщиком мусора посреди работы; храните его в переменной или во множестве до завершения. И исключение внутри задачи молчит, пока её не заawait'или — потерянные задачи легко прячут ошибки.",
        "en": "Nothing runs at the call site itself: the task is queued on the event loop and only starts once the current coroutine reaches its next await. Also keep a reference — the loop holds only a weak one, so a task assigned nowhere can be garbage-collected mid-flight; park it in a variable or a set until it finishes. An exception raised inside a task stays silent until someone awaits it, which is how fire-and-forget tasks swallow bugs."
      },
      "syntax": "task = asyncio.create_task(coro())",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task",
      "version": "3.7",
      "section": "Асинхронное программирование",
      "subcat": "задачи",
      "color_group": "iter",
      "aliases": [
        "фоновая задача",
        "запустить корутину в фоне"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "import asyncio",
        "async def bg():",
        "    await asyncio.sleep(0)",
        "    return 'done'",
        "async def main():",
        "    t = asyncio.create_task(bg())",
        "    print('working...')",
        "    result = await t",
        "    print(result) # → done",
        "    asyncio.run(main())"
      ],
      "related": [
        "asyncio.gather",
        "asyncio.run",
        "async-def-await"
      ],
      "related_errors": [
        "RuntimeError"
      ]
    },
    {
      "id": "asyncio.gather",
      "title": "asyncio.gather()",
      "kind": "function",
      "summary": {
        "ru": "Запускает несколько корутин конкурентно и ждёт завершения всех. Возвращает список результатов.",
        "en": "Runs several coroutines concurrently and waits until all of them finish. Returns the list of results."
      },
      "body": {
        "ru": "Результаты приходят в порядке аргументов, а не в порядке завершения. По умолчанию (return_exceptions=False) первая же ошибка сразу пробрасывается наружу, но остальные корутины при этом не отменяются и продолжают крутиться в фоне — отсюда висящие задачи и жалобы при выходе; с return_exceptions=True исключения просто лягут в список результатов на своих местах. В Python 3.11+ для группы задач с внятной отменой обычно удобнее asyncio.TaskGroup.",
        "en": "Results come back in argument order, never in completion order. With the default return_exceptions=False the first failure propagates at once, yet the remaining coroutines are not cancelled and keep running in the background — that is where stray tasks and shutdown warnings come from; return_exceptions=True instead places each exception into the result list. On Python 3.11+ asyncio.TaskGroup is usually the better fit when you want the whole group cancelled on failure."
      },
      "syntax": "results = await asyncio.gather(coro1, coro2, ...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/asyncio-task.html#asyncio.gather",
      "version": "",
      "section": "Асинхронное программирование",
      "subcat": "параллельность",
      "color_group": "iter",
      "aliases": [
        "запустить корутины параллельно",
        "дождаться всех задач"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "import asyncio",
        "async def fetch(n):",
        "    await asyncio.sleep(0)",
        "    return n ** 2",
        "async def main():",
        "    res = await asyncio.gather(fetch(2), fetch(3), fetch(4))",
        "    print(res) # → [4, 9, 16]",
        "    asyncio.run(main())"
      ],
      "related": [
        "asyncio.create_task",
        "as-completed",
        "asyncio.run"
      ],
      "related_errors": []
    },
    {
      "id": "asyncio.queue",
      "title": "asyncio.Queue",
      "kind": "term",
      "summary": {
        "ru": "Очередь для обмена данными между корутинами. put/get — с await. Не потокобезопасна: для обмена между потоками используй queue.Queue.",
        "en": "A queue for passing data between coroutines. put/get are awaited. Not thread-safe: use queue.Queue to pass data between threads."
      },
      "body": {
        "ru": "Главная боль — как остановить потребителя: await q.get() на пустой очереди ждёт вечно, а проверка q.empty() в цикле — гонка, продюсер мог просто ещё не успеть положить элемент. Штатные способы завершиться — послать сентинел (например, None) или отмечать обработанное через task_done() и ждать q.join(). maxsize больше нуля превращает put в точку ожидания и даёт backpressure: быстрый продюсер не убежит от медленного потребителя.",
        "en": "The hard part is stopping the consumer: await q.get() on an empty queue waits forever, and looping on q.empty() is a race — the producer may simply not have put the item yet. The usual exits are a sentinel value (say None) or marking work with task_done() and waiting on q.join(). A maxsize above zero makes put itself a suspension point, giving you backpressure so a fast producer cannot outrun a slow consumer."
      },
      "syntax": "q = asyncio.Queue(maxsize=0)\nawait q.put(item)\nitem = await q.get()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/asyncio-queue.html#asyncio.Queue",
      "version": "",
      "section": "Асинхронное программирование",
      "subcat": "очередь",
      "color_group": "iter",
      "aliases": [
        "асинхронная очередь",
        "очередь для корутин"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "import asyncio",
        "async def producer(q):",
        "    for i in range(3):",
        "        await q.put(i)",
        "async def consumer(q):",
        "    while not q.empty():",
        "        v = await q.get()",
        "        print(v) # → 0, 1, 2",
        "async def main():",
        "    q = asyncio.Queue()",
        "    await producer(q)",
        "    await consumer(q)",
        "    asyncio.run(main())"
      ],
      "related": [
        "очередь-queue",
        "asyncio.create_task",
        "collections.deque"
      ],
      "related_errors": []
    },
    {
      "id": "asyncio.run",
      "title": "asyncio.run()",
      "kind": "function",
      "summary": {
        "ru": "Запускает корутину верхнего уровня в новом event loop. Главная точка входа для async-программ (Python 3.7+).",
        "en": "Runs a top-level coroutine in a new event loop. The main entry point of an async program (Python 3.7+)."
      },
      "body": {
        "ru": "Вызвать его изнутри уже работающего цикла нельзя: в Jupyter или внутри другой корутины получите RuntimeError про asyncio.run() cannot be called from a running event loop. Каждый вызов создаёт новый loop и в конце закрывает его, отменяя недоделанные задачи, поэтому привязанные к циклу объекты (Queue, Lock, открытые соединения) нельзя переиспользовать между двумя вызовами. Практическое правило: ровно один вызов на всю программу, в самой верхней точке.",
        "en": "You cannot call it from inside a loop that is already running — in Jupyter or within another coroutine you get RuntimeError: asyncio.run() cannot be called from a running event loop. Every call builds a fresh loop and closes it on exit, cancelling whatever is still pending, so loop-bound objects such as Queue, Lock or open connections cannot be shared across two calls. Rule of thumb: exactly one call, at the very top of your program."
      },
      "syntax": "asyncio.run(coro)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/asyncio-runner.html#asyncio.run",
      "version": "3.7",
      "section": "Асинхронное программирование",
      "subcat": "event loop",
      "color_group": "iter",
      "aliases": [
        "запустить корутину",
        "запуск асинхронной программы"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "import asyncio",
        "async def main():",
        "    return 42",
        "result = asyncio.run(main())",
        "result # → 42",
        "async def hello():",
        "    print('start')",
        "    await asyncio.sleep(0)",
        "    print('end')",
        "    asyncio.run(hello()) # → start\\nend"
      ],
      "related": [
        "async-def-await",
        "asyncio.gather",
        "asyncio.create_task"
      ],
      "related_errors": [
        "RuntimeError"
      ]
    },
    {
      "id": "asyncio.sleep",
      "title": "asyncio.sleep()",
      "kind": "function",
      "summary": {
        "ru": "Приостанавливает корутину на указанное время, не блокируя event loop. Аналог time.sleep() для async.",
        "en": "Suspends a coroutine for the given time without blocking the event loop. The async counterpart of time.sleep()."
      },
      "body": {
        "ru": "Главная ошибка — оставить в корутине time.sleep(): он замораживает весь поток вместе с event loop, и параллельные задачи встают, хотя код внешне выглядит асинхронным. Указанное время — минимум, а не точность: возврат произойдёт не раньше него, но насколько позже — зависит от загруженности цикла. Вызов asyncio.sleep(0) — идиома «уступить управление»: спать не нужно, но другим готовым задачам дают шанс поработать.",
        "en": "The classic mistake is leaving time.sleep() inside a coroutine: it freezes the whole thread along with the event loop, so every concurrent task stalls even though the code looks asynchronous. The delay is a lower bound, not a guarantee — you never resume earlier, but how much later depends on how busy the loop is. asyncio.sleep(0) is the idiom for yielding control: no real waiting, just a chance for other ready tasks to run."
      },
      "syntax": "await asyncio.sleep(seconds)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/asyncio-task.html#asyncio.sleep",
      "version": "",
      "section": "Асинхронное программирование",
      "subcat": "event loop",
      "color_group": "iter",
      "aliases": [
        "асинхронная пауза",
        "неблокирующая задержка",
        "подождать в корутине"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "import asyncio",
        "async def task(n):",
        "    await asyncio.sleep(0.1)",
        "    return n * 2",
        "async def main():",
        "    r = await task(5)",
        "    print(r) # → 10",
        "    asyncio.run(main())",
        "    # asyncio.sleep(0) — уступить управление без паузы"
      ],
      "related": [
        "async-def-await",
        "asyncio.run",
        "asyncio.gather"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.append",
      "title": "bytearray.append",
      "kind": "function",
      "summary": {
        "ru": "Добавляет один байт (int 0–255) в конец (изменяет объект).",
        "en": "Append a single byte (int 0–255) in place."
      },
      "body": {
        "ru": "Аргумент — целое от 0 до 255, а не байтовая строка: ba.append(b'c') падает с TypeError, нужно либо ba.append(99), либо ba += b'c'. Это та же логика, по которой индексация bytearray отдаёт int, а не односимвольный объект; выход за 0–255 даёт ValueError, а само добавление в конец стоит амортизированно O(1).",
        "en": "The argument is an int in range 0–255, not a bytes object: ba.append(b'c') raises TypeError, so use ba.append(99) or ba += b'c'. This mirrors the fact that indexing a bytearray yields an int; a value outside 0–255 raises ValueError, and appending itself is amortised O(1)."
      },
      "syntax": "ba.append(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.append",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — мутирующие",
      "color_group": "seq",
      "aliases": [
        "добавить байт в конец",
        "дописать один байт"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "ba = bytearray(b'ab')",
        "ba.append(99)",
        "print(ba)   # → bytearray(b'abc')"
      ],
      "related": [
        "bytearray.extend",
        "bytearray.insert",
        "list.append"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "bytearray.capitalize",
      "title": "bytearray.capitalize",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию: первый байт в верхнем регистре, остальные — в нижнем.",
        "en": "Return a copy with the first byte upper-cased and the rest lower-cased."
      },
      "body": {
        "ru": "Регистр меняется только у ASCII-букв: байты 0x80-0xFF, например кириллица в UTF-8, остаются как есть — bytearray ничего не знает о кодировке. Второй сюрприз в том, что весь хвост принудительно опускается в нижний регистр, так что b'iPhone' станет b'Iphone'; если аббревиатуры надо сохранить, склеивайте поднятый первый байт с нетронутым остатком.",
        "en": "Only ASCII letters change case: bytes 0x80-0xFF, such as UTF-8 encoded Cyrillic, pass through unchanged because a bytearray knows nothing about encodings. The other surprise is that everything after the first byte is forced to lower case, so b'iPhone' becomes b'Iphone'; to preserve internal capitals, upper-case the first byte yourself and concatenate it with the untouched remainder."
      },
      "syntax": "ba.capitalize(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.capitalize",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — регистр",
      "color_group": "seq",
      "aliases": [
        "первая буква заглавная в байтовом массиве",
        "байтовый массив с прописной буквы"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'hello').capitalize())   # → bytearray(b'Hello')",
        "print(bytearray(b'HELLO WORLD').capitalize())   # → bytearray(b'Hello world')",
        "names = [bytearray(b'ANNA'), bytearray(b'bob')]; print([n.capitalize().decode() for n in names])   # → ['Anna', 'Bob']",
        "ba = bytearray(b'python'); print(ba.capitalize(), ba)   # → bytearray(b'Python') bytearray(b'python')",
        "print(bytearray(b'123abc').capitalize())   # → bytearray(b'123abc')",
        "print(bytearray(b'').capitalize())   # → bytearray(b'')"
      ],
      "related": [
        "bytearray.title",
        "bytearray.lower",
        "bytes.capitalize"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.center",
      "title": "bytearray.center",
      "kind": "function",
      "summary": {
        "ru": "Центрирует в поле заданной ширины байтом-заполнителем.",
        "en": "Center in a field of the given width using a fill byte."
      },
      "body": {
        "ru": "Заполнитель — обязательно bytes длиной ровно один байт (b'*'); обычная строка '*' даёт TypeError, это самая частая ошибка при переходе со строк. Несмотря на изменяемость bytearray, метод ничего не правит на месте: он возвращает новый объект, а если width не больше текущей длины — просто копию без выравнивания.",
        "en": "The fill must be a bytes object exactly one byte long (b'*'); passing the str '*' raises TypeError, which is the usual stumble when moving over from strings. And although bytearray is mutable, this method changes nothing in place: it returns a new object, and when width is not greater than the current length you simply get an unpadded copy."
      },
      "syntax": "ba.center(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.center",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — выравнивание",
      "color_group": "seq",
      "aliases": [
        "центрировать байты",
        "выровнять байты по центру"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'hi').center(6, b'*'))   # → bytearray(b'**hi**')",
        "print(bytearray(b'hi').center(6))   # → bytearray(b'  hi  ')",
        "print(bytearray(b'ok').center(10, b'.').decode())   # → ....ok....",
        "print(bytearray(b'hi').center(5, b'-'))   # → bytearray(b'--hi-')",
        "print(bytearray(b'hello').center(3, b'*'))   # → bytearray(b'hello')",
        "print(bytearray(b'hi').center(6, b'**'))   # → TypeError"
      ],
      "related": [
        "bytearray.ljust",
        "bytearray.rjust",
        "bytes.center"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.clear",
      "title": "bytearray.clear",
      "kind": "function",
      "summary": {
        "ru": "Удаляет все байты (делает пустым).",
        "en": "Remove all bytes (make it empty)."
      },
      "body": {
        "ru": "Метод правит сам объект, поэтому опустевший буфер увидят все имена, которые на него ссылаются; если другие ссылки должны сохранить старое содержимое, присваивайте новый ba = bytearray(), а не чистите текущий. Полный аналог — del ba[:]; у неизменяемого bytes такого метода нет вообще.",
        "en": "It mutates the object itself, so every name bound to that buffer sees it become empty; if other references must keep the old content, rebind with ba = bytearray() instead of clearing. It is exactly equivalent to del ba[:], and immutable bytes has no such method at all."
      },
      "syntax": "ba.clear(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.clear",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — мутирующие",
      "color_group": "seq",
      "aliases": [
        "очистить байтовый массив",
        "удалить все байты"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "ba = bytearray(b'abc')",
        "ba.clear()",
        "print(ba)   # → bytearray(b'')"
      ],
      "related": [
        "bytearray.pop",
        "bytearray.remove",
        "list.clear"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.copy",
      "title": "bytearray.copy",
      "kind": "function",
      "summary": {
        "ru": "Возвращает поверхностную копию (новый bytearray).",
        "en": "Return a shallow copy (a new bytearray)."
      },
      "body": {
        "ru": "Слово «поверхностная» здесь ничего не отнимает: элементы — обычные int, так что копия полностью независима от оригинала, в отличие от списка со вложенными списками. Тот же результат дают bytearray(ba) и ba[:], а bytes(ba) вернёт неизменяемый снимок содержимого.",
        "en": "\"Shallow\" costs you nothing here: the elements are plain ints, so the copy is fully independent, unlike a list holding nested lists. bytearray(ba) and ba[:] give the same result, while bytes(ba) yields an immutable snapshot of the same content."
      },
      "syntax": "ba.copy(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.copy",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — мутирующие",
      "color_group": "seq",
      "aliases": [
        "копия байтового массива",
        "скопировать байты"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'abc').copy())   # → bytearray(b'abc')",
        "ba = bytearray(b'abc'); c = ba.copy(); c.append(100); print(ba, c)   # → bytearray(b'abc') bytearray(b'abcd')",
        "ba = bytearray(b'abc'); alias = ba; alias.append(122); print(ba)   # → bytearray(b'abcz')",
        "ba = bytearray(b'abc'); print(ba.copy() == ba, ba.copy() is ba)   # → True False",
        "print(bytearray().copy())   # → bytearray(b'')"
      ],
      "related": [
        "list.copy",
        "bytearray",
        "copy.copy"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.count",
      "title": "bytearray.count",
      "kind": "function",
      "summary": {
        "ru": "Считает непересекающиеся вхождения подпоследовательности.",
        "en": "Count non-overlapping occurrences of a subsequence."
      },
      "body": {
        "ru": "Счёт идёт слева направо и без перекрытий, поэтому в b'aaaa' подпоследовательность b'aa' находится 2 раза, а не 3. Вместо байтовой строки можно передать целое 0-255 — это тот же одиночный байт, что и b'a'; у str такого приёма нет. Пустая подпоследовательность даёт число позиций между байтами, то есть длину плюс один.",
        "en": "Counting goes left to right and never overlaps, so b'aa' occurs twice in b'aaaa', not three times. Instead of a bytes literal you may pass an integer in 0-255, which means that single byte — a trick str.count() does not have. An empty subsequence returns the number of gaps between bytes, i.e. the length plus one."
      },
      "syntax": "ba.count(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.count",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — поиск",
      "color_group": "seq",
      "aliases": [
        "сколько раз байты встречаются в массиве",
        "посчитать вхождения подпоследовательности байтов"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'banana').count(b'a'))   # → 3",
        "print(bytearray(b'aaaa').count(b'aa'))   # → 2",
        "print(bytearray(b'banana').count(b'a', 2))   # → 2",
        "print(bytearray(b'banana').count(97))   # → 3",
        "print(bytearray(b'banana').count(b'z'))   # → 0"
      ],
      "related": [
        "bytearray.find",
        "bytes.count",
        "bytearray.index"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.decode",
      "title": "bytearray.decode",
      "kind": "function",
      "summary": {
        "ru": "Декодирует в строку (str) по кодировке (по умолчанию UTF-8).",
        "en": "Decode to a str using an encoding (UTF-8 by default)."
      },
      "body": {
        "ru": "Чаще всего UnicodeDecodeError тут вызван не неверной кодировкой, а границей куска: режете поток по произвольному числу байт — и многобайтовый символ рвётся пополам на совершенно валидных данных. Глушить это через errors='ignore' значит молча терять символы; лучше дочитать недостающие байты или взять инкрементальный декодер из codecs.",
        "en": "A UnicodeDecodeError here usually means a bad chunk boundary rather than a wrong encoding: cut a stream at an arbitrary byte offset and a multi-byte character gets sliced in half, even though the data is perfectly valid. Silencing it with errors='ignore' quietly drops characters — read the missing bytes first, or use an incremental decoder from codecs."
      },
      "syntax": "ba.decode(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.decode",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — кодирование",
      "color_group": "seq",
      "aliases": [
        "байтовый массив в строку",
        "получить текст из байтового массива"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'hi').decode())   # → hi",
        "print(bytearray(b'\\xd0\\xbf\\xd1\\x80\\xd0\\xb8\\xd0\\xb2\\xd0\\xb5\\xd1\\x82').decode('utf-8'))   # → привет",
        "ba = bytearray(b'name=Ann'); print(ba.decode().split('='))   # → ['name', 'Ann']",
        "print(bytearray(b'ab\\xff').decode('utf-8'))   # → UnicodeDecodeError",
        "print(bytearray(b'ab\\xff').decode('utf-8', 'ignore'))   # → ab",
        "print(type(bytearray(b'hi').decode()))   # → <class 'str'>"
      ],
      "related": [
        "str.encode",
        "bytes.decode",
        "bytearray.hex",
        "unicodedecodeerror"
      ],
      "related_errors": [
        "UnicodeDecodeError"
      ]
    },
    {
      "id": "bytearray.endswith",
      "title": "bytearray.endswith",
      "kind": "function",
      "summary": {
        "ru": "Проверяет заданный суффикс.",
        "en": "Check for a given suffix."
      },
      "body": {
        "ru": "Сравнение побайтовое и регистрозависимое: b'.TXT' не совпадёт с b'.txt', так что перед проверкой приводите к одному регистру через lower(). Аргумент — bytes-подобный объект или кортеж таких; str здесь вызывает TypeError, а одиночное целое, в отличие от count() и find(), не принимается вовсе.",
        "en": "The comparison is byte-for-byte and case-sensitive, so b'.TXT' will not match b'.txt' — normalise with lower() first. The argument must be a bytes-like object or a tuple of them: a str raises TypeError, and unlike count() or find(), a bare integer is not accepted at all."
      },
      "syntax": "ba.endswith(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.endswith",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — поиск",
      "color_group": "seq",
      "aliases": [
        "проверить конец байтового массива",
        "заканчивается ли нужными байтами"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'hello').endswith(b'lo'))   # → True",
        "print(bytearray(b'file.txt').endswith((b'.txt', b'.md')))   # → True",
        "print(bytearray(b'hello').endswith(b'll', 0, 4))   # → True",
        "print(bytearray(b'log.txt').endswith(b'.TXT'))   # → False",
        "print(bytearray(b'hello').endswith('lo'))   # → TypeError"
      ],
      "related": [
        "bytearray.startswith",
        "bytearray.removesuffix",
        "bytes.endswith"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytearray.expandtabs",
      "title": "bytearray.expandtabs",
      "kind": "function",
      "summary": {
        "ru": "Заменяет табуляции пробелами до следующей позиции табуляции.",
        "en": "Replace tabs with spaces up to the next tab stop."
      },
      "body": {
        "ru": "Метод не подставляет фиксированное число пробелов вместо табуляции: он считает текущую колонку и добирает пробелы до ближайшей позиции, кратной tabsize (по умолчанию 8). Поэтому одинаковые \\t дают разное число пробелов в зависимости от того, сколько байтов уже накопилось в текущей строке; байты \\n и \\r сбрасывают счётчик колонки. Возвращается новый bytearray, исходный не меняется.",
        "en": "This does not insert a fixed number of spaces per tab: it tracks the current column and pads up to the next multiple of tabsize (8 by default). The same \\t therefore expands to a different number of spaces depending on how many bytes precede it on the line, and \\n or \\r reset the column counter. A new bytearray is returned; the original is untouched."
      },
      "syntax": "ba.expandtabs(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.expandtabs",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — выравнивание",
      "color_group": "seq",
      "aliases": [],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'a\\tb').expandtabs(4))   # → bytearray(b'a   b')",
        "print(bytearray(b'a\\tb').expandtabs())   # → bytearray(b'a       b')",
        "print(bytearray(b'1\\t22\\t333').expandtabs(4).decode())   # → 1   22  333",
        "print(bytearray(b'ab\\tc').expandtabs(4))   # → bytearray(b'ab  c')",
        "print(bytearray(b'abc').expandtabs(4))   # → bytearray(b'abc')"
      ],
      "related": [
        "bytes.expandtabs",
        "str.expandtabs",
        "bytearray.replace"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.extend",
      "title": "bytearray.extend",
      "kind": "function",
      "summary": {
        "ru": "Дописывает байты из итерируемого в конец (изменяет объект).",
        "en": "Extend with bytes from an iterable in place."
      },
      "body": {
        "ru": "Ждёт итерируемое из целых 0–255: bytes и bytearray подходят, а строка нет — ba.extend('ab') падает, текст сначала надо закодировать в байты. Одиночное число extend тоже не примет, оно не итерируемо: для него есть append.",
        "en": "It expects an iterable of ints in 0–255: bytes and bytearray work, a str does not — ba.extend('ab') fails, encode the text first. A single number is rejected too, since it is not iterable; use append for that."
      },
      "syntax": "ba.extend(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.extend",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — мутирующие",
      "color_group": "seq",
      "aliases": [
        "дописать несколько байтов",
        "добавить байты из последовательности"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "ba = bytearray(b'ab')",
        "ba.extend(b'cd')",
        "print(ba)   # → bytearray(b'abcd')"
      ],
      "related": [
        "bytearray.append",
        "list.extend",
        "bytearray.insert"
      ],
      "related_errors": [
        "TypeError",
        "ValueError"
      ]
    },
    {
      "id": "bytearray.find",
      "title": "bytearray.find",
      "kind": "function",
      "summary": {
        "ru": "Индекс первого вхождения подпоследовательности или -1.",
        "en": "Index of the first occurrence, or -1."
      },
      "body": {
        "ru": "Возвращает -1, а не исключение, и -1 — совершенно валидный индекс: ba[ba.find(b'x')] молча отдаст последний байт, если ничего не найдено. Когда нужен только ответ «есть или нет», пиши b'na' in ba; find бери, когда важна именно позиция. Аргументы start и end лишь сужают окно поиска — индекс всё равно отсчитывается от начала bytearray.",
        "en": "A miss gives -1 rather than an exception, and -1 is a legal index, so ba[ba.find(b'x')] quietly hands you the last byte instead of failing. Use b'na' in ba when you only need presence; reach for find when the position itself matters. The start and end arguments merely narrow the search window — the returned index is still counted from the beginning of the bytearray."
      },
      "syntax": "ba.find(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.find",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — поиск",
      "color_group": "seq",
      "aliases": [
        "найти позицию байтов в массиве",
        "поиск байтов возвращает минус один"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'banana').find(b'na'))   # → 2",
        "print(bytearray(b'banana').find(b'na', 3))   # → 4",
        "ba = bytearray(b'key: value'); print(ba[ba.find(b':') + 2:].decode())   # → value",
        "print(bytearray(b'banana').find(b'z'))   # → -1",
        "print(bytearray(b'banana').index(b'z'))   # → ValueError"
      ],
      "related": [
        "bytearray.index",
        "bytearray.rfind",
        "bytes.find"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytearray.fromhex",
      "title": "bytearray.fromhex",
      "kind": "function",
      "summary": {
        "ru": "Статический метод: создаёт bytearray из шестнадцатеричной строки.",
        "en": "A static method creating a bytearray from a hex string."
      },
      "body": {
        "ru": "Это метод самого типа, а не экземпляра: пишут bytearray.fromhex(...), никакого объекта заранее создавать не нужно. Пробелы и переводы строк внутри строки игнорируются, а вот нечётное число цифр или посторонний символ дают ValueError — операция обратная к .hex().",
        "en": "It is called on the type itself — bytearray.fromhex(...) — no existing object needed. ASCII whitespace inside the string is skipped, but an odd number of digits or any non-hex character raises ValueError; it is the exact inverse of .hex()."
      },
      "syntax": "ba.fromhex(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.fromhex",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — кодирование",
      "color_group": "seq",
      "aliases": [
        "шестнадцатеричная строка в байты",
        "создать байты из шестнадцатеричного текста"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray.fromhex('616263'))   # → bytearray(b'abc')",
        "print(bytearray.fromhex('61 62 63'))   # → bytearray(b'abc')",
        "print(bytearray.fromhex('48656c6c6f').decode())   # → Hello",
        "print(list(bytearray.fromhex('ff00')))   # → [255, 0]",
        "print(bytearray.fromhex('6'))   # → ValueError"
      ],
      "related": [
        "bytearray.hex",
        "bytes.fromhex",
        "int.from_bytes"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "bytearray.hex",
      "title": "bytearray.hex",
      "kind": "function",
      "summary": {
        "ru": "Возвращает шестнадцатеричное представление как строку.",
        "en": "Return a hexadecimal string of the bytes."
      },
      "body": {
        "ru": "Разделитель sep и группировка bytes_per_sep появились в 3.8: при положительном bytes_per_sep группы отсчитываются справа, при отрицательном — слева, и разница видна, когда длина не кратна размеру группы. Цифры всегда в нижнем регистре, для верхнего нужен .upper().",
        "en": "The sep and bytes_per_sep arguments arrived in 3.8: a positive bytes_per_sep groups from the right end, a negative one groups from the left, and the difference shows up whenever the length is not a multiple of the group size. Digits always come out lowercase, so call .upper() if you need capitals."
      },
      "syntax": "ba.hex(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.hex",
      "version": "3.5",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — кодирование",
      "color_group": "seq",
      "aliases": [
        "байты в шестнадцатеричный вид",
        "шестнадцатеричное представление байтов"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'abc').hex())   # → 616263",
        "print(bytearray(b'abc').hex(' '))   # → 61 62 63",
        "print(bytearray(b'abcd').hex('-', 2))   # → 6162-6364",
        "print(bytearray(b'\\xff\\x10').hex())   # → ff10",
        "print(bytearray.fromhex(bytearray(b'abc').hex()))   # → bytearray(b'abc')",
        "print(repr(bytearray().hex()))   # → ''"
      ],
      "related": [
        "bytearray.fromhex",
        "bytes.hex",
        "bytearray.decode"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.index",
      "title": "bytearray.index",
      "kind": "function",
      "summary": {
        "ru": "Как find, но бросает ValueError, если не найдено.",
        "en": "Like find, but raises ValueError if not found."
      },
      "body": {
        "ru": "Бери index там, где отсутствие подпоследовательности — это ошибка в данных: ValueError упадёт сразу, а -1 от find легко утечёт дальше и испортит срез. Искать можно и целым числом 0-255 — тогда ищется один байт; это удобно, потому что ba[i] и сам возвращает int, а не bytes.",
        "en": "Prefer index when a missing needle means the data is broken: it raises ValueError on the spot, whereas find's -1 tends to slip downstream and corrupt a slice. The argument may also be a plain int in range 0-255, which searches for that single byte — handy, since ba[i] already yields an int rather than bytes."
      },
      "syntax": "ba.index(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.index",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — поиск",
      "color_group": "seq",
      "aliases": [
        "позиция байтов или ошибка если не найдено",
        "искать байты с исключением"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'banana').index(b'na'))   # → 2",
        "print(bytearray(b'banana').index(ord('n')))   # → 2",
        "print(bytearray(b'banana').index(b'na', 3))   # → 4",
        "print(bytearray(b'banana').find(b'x'))   # → -1",
        "print(bytearray(b'banana').index(b'x'))   # → ValueError"
      ],
      "related": [
        "bytearray.find",
        "bytearray.rindex",
        "bytes.index"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "bytearray.insert",
      "title": "bytearray.insert",
      "kind": "function",
      "summary": {
        "ru": "Вставляет байт по индексу (изменяет объект).",
        "en": "Insert a byte at an index in place."
      },
      "body": {
        "ru": "Индекс ведёт себя как у list.insert: слишком большой обрезается до конца буфера, отрицательный отсчитывается с конца, IndexError вы не получите — молча вставится не туда, куда ждали. Вставка не в конец сдвигает весь хвост, то есть стоит O(n); в цикле по большому буферу это заметно, лучше собрать куски и склеить.",
        "en": "The index behaves like list.insert: an oversized one is clamped to the end, a negative one counts from the end, and you never get an IndexError — the byte just lands somewhere you did not expect. Inserting anywhere but the end shifts the whole tail, so it costs O(n); doing that in a loop over a large buffer is slow, better to collect pieces and join them."
      },
      "syntax": "ba.insert(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.insert",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — мутирующие",
      "color_group": "seq",
      "aliases": [
        "вставить байт по индексу",
        "вставить байт в середину"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "ba = bytearray(b'ac')",
        "ba.insert(1, 98)",
        "print(ba)   # → bytearray(b'abc')"
      ],
      "related": [
        "bytearray.append",
        "list.insert",
        "bytearray.pop"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "bytearray.isalnum",
      "title": "bytearray.isalnum",
      "kind": "function",
      "summary": {
        "ru": "Все байты — ASCII-буквы/цифры и последовательность непуста.",
        "en": "All bytes are ASCII letters/digits and non-empty."
      },
      "body": {
        "ru": "Проверка чисто ASCII: буквой считается только A-Z и a-z, цифрой — только 0-9, поэтому любой байт со значением 128 и выше (например, кириллица в UTF-8) сразу даёт False. Подчёркивание, дефис, точка и пробел тоже не проходят, так что как проверка «это безопасное имя файла или идентификатор» метод не годится — там нужен свой список разрешённых символов.",
        "en": "The check is strictly ASCII: only A-Z, a-z count as letters and only 0-9 as digits, so any byte at 128 or above (Cyrillic in UTF-8, for instance) makes it False. Underscore, hyphen, dot and space fail too, so this is not a usable \"safe filename or identifier\" test — for that, spell out your own allowed set."
      },
      "syntax": "ba.isalnum(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.isalnum",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — проверки is*",
      "color_group": "seq",
      "aliases": [
        "в байтах только буквы и цифры",
        "проверить байты на буквы и цифры"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'abc123').isalnum())   # → True",
        "print(bytearray(b'abc 123').isalnum())   # → False",
        "print(bytearray(b'user_42').isalnum())   # → False",
        "print(bytearray(b'').isalnum())   # → False",
        "print(bytearray('привет'.encode()).isalnum())   # → False"
      ],
      "related": [
        "bytearray.isalpha",
        "bytearray.isdigit",
        "bytes.isalnum"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.isalpha",
      "title": "bytearray.isalpha",
      "kind": "function",
      "summary": {
        "ru": "Все байты — ASCII-буквы и последовательность непуста.",
        "en": "All bytes are ASCII letters and non-empty."
      },
      "body": {
        "ru": "В отличие от str.isalpha(), здесь никакого Unicode нет: буквами считаются только ASCII A-Z и a-z. Русское слово, закодированное в UTF-8, даст False — его байты лежат выше 127. Если задача про текст, а не про сырые байты, сначала декодируй в строку и проверяй уже её.",
        "en": "Unlike str.isalpha(), there is no Unicode here: only ASCII A-Z and a-z count as letters. A word encoded in UTF-8 from any non-Latin alphabet returns False, since its bytes sit above 127. If the task is about text rather than raw bytes, decode to a string first and test that."
      },
      "syntax": "ba.isalpha(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.isalpha",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — проверки is*",
      "color_group": "seq",
      "aliases": [
        "в байтах только буквы",
        "проверить байты на буквы"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'abc').isalpha())   # → True",
        "print(bytearray(b'abc1').isalpha())   # → False",
        "print(bytearray(b'Hello World').isalpha())   # → False",
        "print(bytearray(b'').isalpha())   # → False",
        "print(bytearray(b'abc1').isalnum())   # → True"
      ],
      "related": [
        "bytearray.isalnum",
        "bytearray.isdigit",
        "bytes.isalpha"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.isascii",
      "title": "bytearray.isascii",
      "kind": "function",
      "summary": {
        "ru": "Все байты — ASCII (0–127).",
        "en": "All bytes are ASCII (0–127)."
      },
      "body": {
        "ru": "Единственный метод этого семейства, который на пустой последовательности возвращает True: условие «все байты в диапазоне 0-127» на пустом наборе выполняется тривиально. Проверяется только диапазон значений, а не читаемость — управляющие байты вроде нулевого или символа escape тоже ASCII. Метод появился в Python 3.7.",
        "en": "This is the one member of the is* family that returns True for an empty sequence: \"every byte is within 0-127\" holds vacuously when there are no bytes. It tests the value range only, not readability — control bytes such as NUL or ESC are ASCII as well. Available since Python 3.7."
      },
      "syntax": "ba.isascii(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.isascii",
      "version": "3.7",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — проверки is*",
      "color_group": "seq",
      "aliases": [
        "проверить байты на аски",
        "все байты меньше 128"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'abc').isascii())   # → True",
        "print(bytearray(b'abc\\xff').isascii())   # → False",
        "print(bytearray(b'\\x7f').isascii())   # → True",
        "print(bytearray(b'').isascii())   # → True",
        "print(bytearray('привет'.encode('utf-8')).isascii())   # → False"
      ],
      "related": [
        "bytes.isascii",
        "bytearray.decode",
        "bytearray.isalnum"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.isdigit",
      "title": "bytearray.isdigit",
      "kind": "function",
      "summary": {
        "ru": "Все байты — ASCII-цифры и последовательность непуста.",
        "en": "All bytes are ASCII digits and non-empty."
      },
      "body": {
        "ru": "Метод отвечает ровно на вопрос «все байты — цифры 0-9»: знак минуса, десятичная точка, пробел или разделитель разрядов сразу дают False, поэтому как проверка «это число» он не работает. Аналогов isdecimal() и isnumeric(), которые есть у str, у байтов нет — надёжнее попробовать преобразовать значение и поймать ValueError.",
        "en": "It answers exactly one question: are all bytes digits 0-9. A minus sign, decimal point, space or thousands separator makes it False, so it is not an \"is this a number\" test. Bytes have no isdecimal() or isnumeric() counterparts the way str does — attempting the conversion and catching ValueError is the more reliable route."
      },
      "syntax": "ba.isdigit(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.isdigit",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — проверки is*",
      "color_group": "seq",
      "aliases": [
        "в байтах только цифры",
        "проверить байты на цифры"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'123').isdigit())   # → True",
        "print(bytearray(b'12.3').isdigit())   # → False",
        "print(bytearray(b'-5').isdigit())   # → False",
        "print(bytearray(b'').isdigit())   # → False",
        "raw = bytearray(b'2026')",
        "print(int(raw) if raw.isdigit() else None)   # → 2026"
      ],
      "related": [
        "bytearray.isalnum",
        "bytearray.isalpha",
        "bytes.isdigit"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.islower",
      "title": "bytearray.islower",
      "kind": "function",
      "summary": {
        "ru": "Все буквенные байты — в нижнем регистре.",
        "en": "All cased bytes are lower-case."
      },
      "body": {
        "ru": "Регистр здесь есть только у ASCII-букв, а последовательность вообще без букв (одни цифры, пунктуация или пустая) даёт False. Отсюда важное следствие: islower() не является отрицанием isupper() — на строке из одних цифр обе проверки False, так что рассуждать «раз не верхний регистр, значит нижний» нельзя.",
        "en": "Only ASCII letters carry case here, and a sequence with no letters at all — digits, punctuation, or empty — returns False. The consequence matters: islower() is not the negation of isupper(), since a digits-only sequence makes both False, so \"not upper, therefore lower\" is a broken inference."
      },
      "syntax": "ba.islower(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.islower",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — проверки is*",
      "color_group": "seq",
      "aliases": [
        "все байты в нижнем регистре",
        "проверить байты на строчные буквы"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'abc').islower())   # → True",
        "print(bytearray(b'abc1!').islower())   # → True",
        "print(bytearray(b'Abc').islower())   # → False",
        "print(bytearray(b'123').islower())   # → False",
        "print(bytearray(b'').islower())   # → False"
      ],
      "related": [
        "bytearray.isupper",
        "bytearray.lower",
        "bytes.islower"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.isspace",
      "title": "bytearray.isspace",
      "kind": "function",
      "summary": {
        "ru": "Все байты — пробельные и последовательность непуста.",
        "en": "All bytes are whitespace and non-empty."
      },
      "body": {
        "ru": "Пробельными здесь считаются только ASCII-байты: пробел, \\t, \\n, \\r, \\v, \\f. Байт 0xa0 (неразрывный пробел в latin-1) в набор не входит, хотя str.isspace() для символа '\\xa0' вернёт True — если байты пришли из текста, надёжнее сначала декодировать их в строку и проверять уже её.",
        "en": "Only ASCII whitespace counts here: space, \\t, \\n, \\r, \\v, \\f. Byte 0xa0 (a non-breaking space in latin-1) is not in that set, even though str.isspace() answers True for the character '\\xa0' — if the bytes came from text, decode first and test the resulting string."
      },
      "syntax": "ba.isspace(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.isspace",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — проверки is*",
      "color_group": "seq",
      "aliases": [
        "в байтах только пробельные символы",
        "проверить байты на пробелы"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'   ').isspace())   # → True",
        "print(bytearray(b' \\t\\n').isspace())   # → True",
        "print(bytearray(b' a ').isspace())   # → False",
        "print(bytearray(b'').isspace())   # → False",
        "print(bytearray(b' \\t ').strip())   # → bytearray(b'')"
      ],
      "related": [
        "bytes.isspace",
        "bytearray.strip",
        "bytearray.isalnum"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.istitle",
      "title": "bytearray.istitle",
      "kind": "function",
      "summary": {
        "ru": "Последовательность в «заголовочном» регистре.",
        "en": "The bytes are title-cased."
      },
      "body": {
        "ru": "Проверка чисто механическая: каждая цепочка букв должна начинаться с заглавной, а дальше идти только строчные — к правилам оформления заголовков в живом языке это отношения не имеет. Любой небуквенный байт (цифра, дефис, пунктуация) считается границей слова, поэтому b'Hello2World' проходит проверку, а b'HI There' — нет из-за второй заглавной подряд.",
        "en": "The test is purely mechanical: every run of letters must start with an upper-case byte and continue in lower case — it has nothing to do with real headline capitalization rules. Any non-letter byte (digit, hyphen, punctuation) acts as a word boundary, so b'Hello2World' passes while b'HI There' fails on the second consecutive capital."
      },
      "syntax": "ba.istitle(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.istitle",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — проверки is*",
      "color_group": "seq",
      "aliases": [],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'Hi There').istitle())   # → True",
        "print(bytearray(b'hi there').istitle())   # → False",
        "print(bytearray(b'HI There').istitle())   # → False",
        "print(bytearray(b'Hello2World').istitle())   # → True",
        "print(bytearray(b'').istitle())   # → False",
        "print(bytearray(b'hi there').title())   # → bytearray(b'Hi There')"
      ],
      "related": [
        "bytearray.title",
        "bytearray.isupper",
        "bytes.istitle"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.isupper",
      "title": "bytearray.isupper",
      "kind": "function",
      "summary": {
        "ru": "Все буквенные байты — в верхнем регистре.",
        "en": "All cased bytes are upper-case."
      },
      "body": {
        "ru": "Нужен хотя бы один буквенный байт: у b'123' и у пустого bytearray результат False, хотя строчных букв там нет вовсе. И из isupper() == False не следует islower() == True — у b'Ab1' обе проверки дают False, так что «не верхний регистр» и «нижний регистр» — разные вопросы.",
        "en": "At least one cased byte is required: b'123' and an empty bytearray both give False even though they contain no lower-case letters. Also, isupper() being False does not make islower() True — b'Ab1' fails both, so \"not upper-case\" and \"lower-case\" are different questions."
      },
      "syntax": "ba.isupper(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.isupper",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — проверки is*",
      "color_group": "seq",
      "aliases": [
        "все байты в верхнем регистре",
        "проверить байты на заглавные буквы"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'ABC').isupper())   # → True",
        "print(bytearray(b'ABC1!').isupper())   # → True",
        "print(bytearray(b'Abc').isupper())   # → False",
        "print(bytearray(b'123').isupper())   # → False",
        "print(bytearray(b'').isupper())   # → False"
      ],
      "related": [
        "bytearray.islower",
        "bytearray.upper",
        "bytes.isupper"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.join",
      "title": "bytearray.join",
      "kind": "function",
      "summary": {
        "ru": "Соединяет последовательность байтовых объектов через разделитель.",
        "en": "Join a sequence of bytes with this separator."
      },
      "body": {
        "ru": "Элементы должны быть байтоподобными (bytes, bytearray, memoryview): обычная строка в списке даёт TypeError, неявного кодирования не будет — кодируйте через .encode() сами. Склейка через join проходит по всем кускам один раз, а накопление результата в цикле через += квадратично по числу фрагментов, поэтому собирать длинные последовательности стоит именно join.",
        "en": "Every element must be a bytes-like object (bytes, bytearray, memoryview): a plain str in the list raises TypeError, since there is no implicit encoding — call .encode() yourself. join walks the pieces once, whereas building the result with += in a loop is quadratic in the number of fragments, so use join for anything long."
      },
      "syntax": "ba.join(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.join",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "склеить байтовые куски через разделитель",
        "объединить байты в один массив"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'-').join([b'a', b'b']))   # → bytearray(b'a-b')",
        "print(bytearray(b'').join([b'ab', b'cd']))   # → bytearray(b'abcd')",
        "print(bytearray(b', ').join([bytearray(b'x'), bytearray(b'y'), bytearray(b'z')]))   # → bytearray(b'x, y, z')",
        "print(bytearray(b'-').join([b'solo']))   # → bytearray(b'solo')",
        "print(bytearray(b'-').join([]))   # → bytearray(b'')",
        "print(bytearray(b'-').join(['a', 'b']))   # → TypeError"
      ],
      "related": [
        "bytearray.split",
        "bytes.join",
        "str.join"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytearray.ljust",
      "title": "bytearray.ljust",
      "kind": "function",
      "summary": {
        "ru": "Выравнивает по левому краю в поле заданной ширины.",
        "en": "Left-justify in a field of the given width."
      },
      "body": {
        "ru": "Хотя bytearray изменяемый, метод ничего не правит на месте — он возвращает новый объект, и результат нужно присвоить. Заполнитель обязан быть байтовым объектом длиной ровно один байт (b'.', а не '.' и не b'..'), иначе TypeError. Аргумент — итоговая ширина поля, а не количество добавляемых байтов; если строка уже длиннее, она возвращается как есть, без обрезки.",
        "en": "Even though bytearray is mutable, this method changes nothing in place — it returns a new object that you have to assign. The fill must be a bytes-like object exactly one byte long (b'.', not '.' and not b'..'), otherwise you get a TypeError. The argument is the total field width, not the number of bytes added, and longer input is returned unchanged rather than truncated."
      },
      "syntax": "ba.ljust(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.ljust",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — выравнивание",
      "color_group": "seq",
      "aliases": [
        "выровнять байты по левому краю",
        "дополнить байты справа до длины"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'hi').ljust(5, b'.'))   # → bytearray(b'hi...')",
        "print(bytearray(b'hi').ljust(5))   # → bytearray(b'hi   ')",
        "print(bytearray(b'7').ljust(4, b'0'))   # → bytearray(b'7000')",
        "print(bytearray(b'id').ljust(6, b'.') + bytearray(b'42'))   # → bytearray(b'id....42')",
        "print(bytearray(b'hello').ljust(3))   # → bytearray(b'hello')",
        "print(bytearray(b'hi').rjust(5, b'.'))   # → bytearray(b'...hi')"
      ],
      "related": [
        "bytearray.rjust",
        "bytearray.center",
        "bytes.ljust",
        "bytearray.zfill"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.lower",
      "title": "bytearray.lower",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию с ASCII-буквами в нижнем регистре.",
        "en": "Return a copy with ASCII letters lower-cased."
      },
      "body": {
        "ru": "Хотя bytearray изменяемый, метод ничего не правит на месте — он возвращает новый объект, и результат нужно куда-то присвоить, иначе изменения потеряются. Переводятся только ASCII-буквы A-Z, независимо от локали системы; байты UTF-8 текста (кириллица, é) остаются как есть, поэтому для настоящего текста сначала decode(), а уже потом str.lower().",
        "en": "Even though bytearray is mutable, this method changes nothing in place: it hands back a new object, so the result has to be assigned somewhere or it is lost. Only ASCII A-Z is folded, regardless of system locale — bytes of UTF-8 text (Cyrillic, é) pass through untouched, so decode() first and use str.lower() when you are dealing with real text."
      },
      "syntax": "ba.lower(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.lower",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — регистр",
      "color_group": "seq",
      "aliases": [
        "байтовый массив в нижний регистр",
        "маленькие буквы в байтовом массиве"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'ABC').lower())   # → bytearray(b'abc')",
        "print(bytearray(b'Hello, World!').lower())   # → bytearray(b'hello, world!')",
        "print(bytearray(b'YES').lower() == b'yes')   # → True",
        "ba = bytearray(b'ABC')",
        "print(ba.lower(), ba)   # → bytearray(b'abc') bytearray(b'ABC')",
        "print(bytearray(b'\\xc0Z').lower())   # → bytearray(b'\\xc0z')"
      ],
      "related": [
        "bytearray.upper",
        "bytearray.islower",
        "bytes.lower"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.lstrip",
      "title": "bytearray.lstrip",
      "kind": "function",
      "summary": {
        "ru": "Убирает байты слева.",
        "en": "Strip bytes from the left."
      },
      "body": {
        "ru": "Аргумент — набор байтов, которые срезаются по одному, пока встречаются, а не префикс целиком: lstrip(b'xy') снимет слева любое количество 'x' и 'y' в любом порядке. Если нужно убрать ровно одну заданную последовательность, это работа для removeprefix. Сам bytearray не меняется — возвращается новый объект.",
        "en": "The argument is a set of byte values peeled off one at a time while they keep matching, not a prefix taken as a whole: lstrip(b'xy') eats any run of 'x' and 'y' in any order. To drop exactly one given sequence, reach for removeprefix. The bytearray itself is untouched — you get a new object back."
      },
      "syntax": "ba.lstrip(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.lstrip",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — обрезка",
      "color_group": "seq",
      "aliases": [
        "убрать пробелы в начале байтов",
        "обрезать байты слева"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'  hi').lstrip())   # → bytearray(b'hi')",
        "print(bytearray(b'\\t\\n hi').lstrip())   # → bytearray(b'hi')",
        "print(bytearray(b'000123').lstrip(b'0'))   # → bytearray(b'123')",
        "print(bytearray(b'xyxdata').lstrip(b'xy'))   # → bytearray(b'data')",
        "print(bytearray(b'  hi  ').lstrip())   # → bytearray(b'hi  ')",
        "print(bytearray(b'0000').lstrip(b'0'))   # → bytearray(b'')"
      ],
      "related": [
        "bytearray.rstrip",
        "bytearray.strip",
        "bytearray.removeprefix",
        "bytes.lstrip"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.maketrans",
      "title": "bytearray.maketrans",
      "kind": "function",
      "summary": {
        "ru": "Статический метод: строит таблицу перевода из двух байтовых объектов.",
        "en": "A static method building a translation table."
      },
      "body": {
        "ru": "Это статический метод: вызывать его удобнее от типа (bytearray.maketrans), а не от конкретного объекта, и результат — обычный bytes длиной 256, где i-й байт задаёт замену для байта со значением i. Оба аргумента должны быть одинаковой длины, иначе ValueError; замена идёт байт в байт, поэтому превратить один байт в два символа так нельзя. Удалять байты maketrans не умеет — для этого есть второй аргумент translate.",
        "en": "It is a static method, so call it on the type (bytearray.maketrans) rather than on an instance; the result is a plain bytes object of length 256 whose i-th byte is the replacement for byte value i. Both arguments must have the same length or you get a ValueError, and mapping is strictly one byte to one byte — you cannot expand a byte into two. Deletion is not part of the table: pass the bytes to drop as translate's second argument."
      },
      "syntax": "ba.maketrans(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.maketrans",
      "version": "3.1",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — замена и перевод",
      "color_group": "seq",
      "aliases": [
        "построить таблицу замены байтов",
        "подготовить таблицу перевода для байтов"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(len(bytearray.maketrans(b'ab', b'AB')))   # → 256",
        "table = bytearray.maketrans(b'ab', b'AB')",
        "print(bytearray(b'cabbage').translate(table))   # → bytearray(b'cABBAge')",
        "print(bytearray(b'ATGCA').translate(bytearray.maketrans(b'ATGC', b'TACG')))   # → bytearray(b'TACGT')",
        "print(type(bytearray.maketrans(b'ab', b'AB')))   # → <class 'bytes'>",
        "print(bytearray.maketrans(b'ab', b'A'))   # → ValueError"
      ],
      "related": [
        "bytearray.translate",
        "bytes.maketrans",
        "str.maketrans"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "bytearray.partition",
      "title": "bytearray.partition",
      "kind": "function",
      "summary": {
        "ru": "Делит по первому разделителю на тройку.",
        "en": "Split at the first separator into a triple."
      },
      "body": {
        "ru": "Тройка возвращается всегда, поэтому распаковка head, sep, tail не падает никогда — узнать, нашёлся ли разделитель, можно по среднему элементу, а не по длине результата. Ищется первое вхождение слева; для последнего есть rpartition. Пустой разделитель — не «разбить по каждому байту», а ValueError.",
        "en": "You always get exactly three parts, so unpacking head, sep, tail can never fail — check the middle item to learn whether the separator was actually found, rather than inspecting a length. The search takes the leftmost occurrence; use rpartition for the rightmost. An empty separator is not \"split everywhere\" — it raises ValueError."
      },
      "syntax": "ba.partition(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.partition",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "разделить байтовый массив по первому разделителю",
        "байтовый массив на три части"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'a=b').partition(b'='))   # → (bytearray(b'a'), bytearray(b'='), bytearray(b'b'))",
        "print(bytearray(b'key=1=2').partition(b'='))   # → (bytearray(b'key'), bytearray(b'='), bytearray(b'1=2'))",
        "print(bytearray(b'name=Alice').partition(b'=')[2])   # → bytearray(b'Alice')",
        "print(bytearray(b'noeq').partition(b'='))   # → (bytearray(b'noeq'), bytearray(b''), bytearray(b''))",
        "print(bytearray(b'a=b=c').rpartition(b'='))   # → (bytearray(b'a=b'), bytearray(b'='), bytearray(b'c'))",
        "print(bytearray(b'ab').partition(b''))   # → ValueError"
      ],
      "related": [
        "bytearray.rpartition",
        "bytearray.split",
        "bytes.partition"
      ],
      "related_errors": [
        "TypeError",
        "ValueError"
      ]
    },
    {
      "id": "bytearray.pop",
      "title": "bytearray.pop",
      "kind": "function",
      "summary": {
        "ru": "Удаляет и возвращает байт по индексу (по умолчанию последний).",
        "en": "Remove and return a byte at an index (last by default)."
      },
      "body": {
        "ru": "Возвращается целое число 0-255, а не однобайтовая последовательность, так что сравнивать результат с b'a' бессмысленно — сравнивайте с 97 или с ord('a'). Снятие с конца дешёвое, а pop(0) и pop из середины сдвигают весь хвост, то есть работают за O(n); на пустом bytearray и при индексе за границей будет IndexError.",
        "en": "The value comes back as an int in 0-255, not as a one-byte sequence, so comparing it to b'a' never matches — compare with 97 or ord('a') instead. Popping from the end is cheap, while pop(0) or a pop from the middle shifts the whole tail and costs O(n); an empty bytearray or an out-of-range index raises IndexError."
      },
      "syntax": "ba.pop(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.pop",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — мутирующие",
      "color_group": "seq",
      "aliases": [
        "удалить и вернуть байт",
        "извлечь байт по индексу"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "ba = bytearray(b'abc')",
        "print(ba.pop())   # → 99",
        "print(bytearray(b'abc').pop(0))   # → 97",
        "print(bytearray(b'abc').pop(-2))   # → 98",
        "ba2 = bytearray(b'abc'); ba2.pop(); print(ba2)   # → bytearray(b'ab')",
        "print(bytearray().pop())   # → IndexError"
      ],
      "related": [
        "bytearray.remove",
        "list.pop",
        "bytearray.append"
      ],
      "related_errors": [
        "IndexError"
      ]
    },
    {
      "id": "bytearray.remove",
      "title": "bytearray.remove",
      "kind": "function",
      "summary": {
        "ru": "Удаляет первое вхождение байта с данным значением.",
        "en": "Remove the first occurrence of a byte value."
      },
      "body": {
        "ru": "Аргумент — целое число 0-255, а не байтовая строка: ba.remove(b'b') падает с TypeError, писать надо ba.remove(98) или ba.remove(ord('b')). Убирается только первое вхождение слева, а если такого байта в буфере нет вовсе — ValueError, поэтому либо проверяйте наличие через in, либо оборачивайте вызов в try.",
        "en": "The argument is an int in 0-255, not a bytes literal: ba.remove(b'b') fails with TypeError, so write ba.remove(98) or ba.remove(ord('b')). Only the leftmost occurrence goes away, and if the byte is absent you get a ValueError — check with in first or wrap the call in try."
      },
      "syntax": "ba.remove(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.remove",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — мутирующие",
      "color_group": "seq",
      "aliases": [
        "удалить байт по значению",
        "убрать первое вхождение байта"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "ba = bytearray(b'abc')",
        "ba.remove(98)",
        "print(ba)   # → bytearray(b'ac')"
      ],
      "related": [
        "bytearray.pop",
        "list.remove",
        "bytearray.index",
        "valueerror"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "bytearray.removeprefix",
      "title": "bytearray.removeprefix",
      "kind": "function",
      "summary": {
        "ru": "Убирает заданный префикс, если он есть (3.9+).",
        "en": "Remove a given prefix if present (3.9+)."
      },
      "body": {
        "ru": "Возвращается новый bytearray, а исходный не меняется, так что вызов ради побочного эффекта ничего не сделает — результат нужно присвоить. Снимается ровно одна копия префикса, повторы остаются на месте, а если префикса нет — данные возвращаются как есть, без ошибки.",
        "en": "You get a fresh bytearray back and the original stays as it was, so calling it for its side effect does nothing — assign the result. Exactly one copy of the prefix is removed, repeats stay put, and a missing prefix simply returns the data unchanged instead of raising."
      },
      "syntax": "ba.removeprefix(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.removeprefix",
      "version": "3.9",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — обрезка",
      "color_group": "seq",
      "aliases": [
        "убрать префикс из байтового массива",
        "отрезать начало если совпадает"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'foobar').removeprefix(b'foo'))   # → bytearray(b'bar')",
        "print(bytearray(b'foobar').removeprefix(b'bar'))   # → bytearray(b'foobar')",
        "print(bytearray(b'foofoobar').removeprefix(b'foo'))   # → bytearray(b'foobar')",
        "ba = bytearray(b'log:42'); print(ba.removeprefix(b'log:'), ba)   # → bytearray(b'42') bytearray(b'log:42')",
        "print(bytearray(b'abc').removeprefix('a'))   # → TypeError"
      ],
      "related": [
        "bytearray.removesuffix",
        "bytearray.lstrip",
        "bytes.removeprefix",
        "bytearray.startswith"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.removesuffix",
      "title": "bytearray.removesuffix",
      "kind": "function",
      "summary": {
        "ru": "Убирает заданный суффикс, если он есть (3.9+).",
        "en": "Remove a given suffix if present (3.9+)."
      },
      "body": {
        "ru": "Снимает ровно одно вхождение указанной последовательности целиком — в отличие от rstrip(b'.txt'), который грызёт с конца любые байты из набора '.', 't', 'x'. Если суффикса нет, возвращается копия исходного значения без изменений; метод не работает на месте, сам bytearray остаётся прежним.",
        "en": "It strips exactly one occurrence of the whole given sequence, unlike rstrip(b'.txt'), which keeps chewing off any of the bytes '.', 't' and 'x'. When the suffix is absent you simply get an unchanged copy — and either way the original bytearray is untouched, since this is not an in-place operation."
      },
      "syntax": "ba.removesuffix(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.removesuffix",
      "version": "3.9",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — обрезка",
      "color_group": "seq",
      "aliases": [
        "убрать суффикс из байтового массива",
        "отрезать окончание если совпадает"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'foobar').removesuffix(b'bar'))   # → bytearray(b'foo')",
        "print(bytearray(b'foobar').removesuffix(b'foo'))   # → bytearray(b'foobar')",
        "print(bytearray(b'report.txt.txt').removesuffix(b'.txt'))   # → bytearray(b'report.txt')",
        "ba = bytearray(b'file.csv'); print(ba.removesuffix(b'.csv'), ba)   # → bytearray(b'file') bytearray(b'file.csv')",
        "print(bytearray(b'abc').removesuffix(b''))   # → bytearray(b'abc')"
      ],
      "related": [
        "bytearray.removeprefix",
        "bytearray.rstrip",
        "bytes.removesuffix",
        "bytearray.endswith"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytearray.replace",
      "title": "bytearray.replace",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию с заменой вхождений подпоследовательности.",
        "en": "Return a copy with occurrences replaced."
      },
      "body": {
        "ru": "Хотя bytearray изменяемый, replace ничего не правит на месте — возвращается новая копия, и без присваивания результат просто потеряется. Оба аргумента обязаны быть bytes-подобными: str или голое число вместо b'a' дадут TypeError.",
        "en": "bytearray is mutable, yet replace never edits in place — it hands back a fresh copy, so the result is lost unless you assign it. Both arguments must be bytes-like; a str, or a bare int for a single byte, raises TypeError."
      },
      "syntax": "ba.replace(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.replace",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — замена и перевод",
      "color_group": "seq",
      "aliases": [
        "заменить последовательность байтов",
        "замена вхождений в байтовом массиве"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'aaa').replace(b'a', b'b'))   # → bytearray(b'bbb')",
        "print(bytearray(b'aaa').replace(b'a', b'b', 2))   # → bytearray(b'bba')",
        "print(bytearray(b'a-b-c').replace(b'-', b''))   # → bytearray(b'abc')",
        "print(bytearray(b'abc').replace(b'z', b'y'))   # → bytearray(b'abc')",
        "ba = bytearray(b'aaa'); ba.replace(b'a', b'b'); print(ba)   # → bytearray(b'aaa')"
      ],
      "related": [
        "bytearray.translate",
        "bytes.replace",
        "str.replace"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytearray.resize",
      "title": "bytearray.resize()",
      "kind": "function",
      "summary": {
        "ru": "Меняет длину bytearray на месте: лишние байты отбрасываются, недостающие добавляются нулевыми (b'\\x00'). Возвращает None. Python 3.14+.",
        "en": "Resizes a bytearray in place: extra bytes are truncated, new bytes are filled with null bytes. Returns None. Python 3.14+."
      },
      "body": {
        "ru": "Метод добавлен только в 3.14 — на 3.12/3.13 будет AttributeError, а переносимые аналоги те же: b += b'\\x00' * n для роста и del b[n:] для обрезки. Изменение размера может переселить буфер в памяти, поэтому пока на bytearray жив memoryview, вызов не пройдёт и упадёт с BufferError.",
        "en": "The method only exists in 3.14 and later — on 3.12/3.13 you get an AttributeError, and the portable equivalents are b += b'\\x00' * n to grow and del b[n:] to shrink. Resizing may move the underlying buffer, so while a memoryview is exported on the bytearray the call fails with BufferError."
      },
      "syntax": "bytearray.resize(size, /)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.resize",
      "version": "3.14",
      "section": "Байтовые последовательности",
      "subcat": "изменение размера",
      "color_group": "str",
      "aliases": [
        "изменить размер байтового массива",
        "обрезать байтовый массив",
        "дополнить байтовый массив нулями"
      ],
      "keywords": [
        "bytearray.resize",
        "resize"
      ],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "b = bytearray(b'abc')",
        "print(b.resize(5))   # → None",
        "print(b)             # → bytearray(b'abc\\x00\\x00')",
        "print(len(b))        # → 5",
        "b.resize(2)",
        "print(b)             # → bytearray(b'ab')"
      ],
      "related": [
        "bytearray",
        "bytearray.extend",
        "bytearray.clear",
        "bytearray.append"
      ],
      "related_errors": [
        "ValueError",
        "BufferError"
      ]
    },
    {
      "id": "bytearray.reverse",
      "title": "bytearray.reverse",
      "kind": "function",
      "summary": {
        "ru": "Разворачивает байты на месте.",
        "en": "Reverse the bytes in place."
      },
      "body": {
        "ru": "Метод меняет объект на месте и возвращает None, поэтому ba = ba.reverse() затрёт ваши данные значением None — вызывайте его отдельной строкой. Если нужна перевёрнутая копия, а оригинал должен уцелеть, берите срез ba[::-1].",
        "en": "It reverses in place and returns None, so ba = ba.reverse() silently replaces your data with None — call it as a standalone statement. When you need a reversed copy and want the original intact, use the slice ba[::-1] instead."
      },
      "syntax": "ba.reverse(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.reverse",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — мутирующие",
      "color_group": "seq",
      "aliases": [
        "перевернуть байты на месте",
        "развернуть байтовый массив"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "ba = bytearray(b'abc')",
        "ba.reverse()",
        "print(ba)   # → bytearray(b'cba')"
      ],
      "related": [
        "list.reverse",
        "reversed",
        "срезы-с-шагом-2-1"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.rfind",
      "title": "bytearray.rfind",
      "kind": "function",
      "summary": {
        "ru": "Индекс последнего вхождения подпоследовательности или -1.",
        "en": "Index of the last occurrence, or -1."
      },
      "body": {
        "ru": "Поиск идёт справа налево, но найденный индекс всё равно считается от начала, поэтому для одной и той же подпоследовательности rfind и find дают разные числа. start и end задают обычное окно [start, end), нумерация внутри него не переворачивается — «справа» влияет лишь на то, какое из совпадений выбрано.",
        "en": "The scan runs right to left, yet the index it returns is still measured from the start, so rfind and find report different numbers for the very same needle. start and end define the ordinary [start, end) window; nothing is reversed there — the direction only decides which match inside that window wins."
      },
      "syntax": "ba.rfind(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.rfind",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — поиск",
      "color_group": "seq",
      "aliases": [
        "найти последнее вхождение байтов",
        "искать байты с конца"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'banana').rfind(b'na'))   # → 4",
        "print(bytearray(b'banana').find(b'na'))   # → 2",
        "print(bytearray(b'banana').rfind(b'na', 0, 4))   # → 2",
        "print(bytearray(b'banana').rfind(b'z'))   # → -1",
        "print(bytearray(b'banana').rfind(97))   # → 5",
        "print(bytearray(b'banana').rindex(b'z'))   # → ValueError"
      ],
      "related": [
        "bytearray.find",
        "bytearray.rindex",
        "bytes.rfind"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytearray.rindex",
      "title": "bytearray.rindex",
      "kind": "function",
      "summary": {
        "ru": "Как rfind, но бросает ValueError, если не найдено.",
        "en": "Like rfind, but raises ValueError if not found."
      },
      "body": {
        "ru": "Нужен там, где последнее вхождение обязано существовать: разделитель в пути, точка перед расширением. Пропавший разделитель тогда упадёт исключением, а не тихо съедет на -1 и не даст бессмысленный срез. Для типичного «взять всё после последнего разделителя» обычно проще rpartition() — он сам разруливает случай «не найдено» без try/except.",
        "en": "Use it where the last occurrence is required by contract: a separator in a path, the dot before an extension. A vanished separator then raises instead of silently sliding to -1 and producing a nonsense slice. For the common \"everything after the last separator\" job, rpartition() is usually cleaner — it handles the not-found case for you, no try/except needed."
      },
      "syntax": "ba.rindex(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.rindex",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — поиск",
      "color_group": "seq",
      "aliases": [
        "последнее вхождение байтов или ошибка",
        "искать байты с конца с исключением"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'banana').rindex(b'na'))   # → 4",
        "print(bytearray(b'banana').rindex(b'a'))   # → 5",
        "print(bytearray(b'banana').rindex(b'na', 0, 4))   # → 2",
        "print(bytearray(b'banana').rindex(b'z'))   # → ValueError",
        "print(bytearray(b'banana').rfind(b'z'))   # → -1"
      ],
      "related": [
        "bytearray.rfind",
        "bytearray.index",
        "bytes.rindex"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "bytearray.rjust",
      "title": "bytearray.rjust",
      "kind": "function",
      "summary": {
        "ru": "Выравнивает по правому краю в поле заданной ширины.",
        "en": "Right-justify in a field of the given width."
      },
      "body": {
        "ru": "Число — это итоговая ширина поля, а не количество добавляемых байтов, и слишком длинные данные не обрезаются: для гарантированной ширины режьте срезом отдельно. Заполнитель нулями через rjust(n, b'0') отличается от zfill: rjust не знает про знак и превратит b'-42' в b'00-42', тогда как zfill поставит нули после минуса.",
        "en": "The number is the total field width, not the count of padding bytes, and oversized data is never truncated — slice it yourself if you need a hard width. Padding with rjust(n, b'0') is not the same as zfill: rjust knows nothing about signs and turns b'-42' into b'00-42', while zfill keeps the minus in front and inserts the zeros after it."
      },
      "syntax": "ba.rjust(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.rjust",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — выравнивание",
      "color_group": "seq",
      "aliases": [
        "выровнять байты по правому краю",
        "дополнить байты слева до длины"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'hi').rjust(5, b'.'))   # → bytearray(b'...hi')",
        "print(bytearray(b'42').rjust(6))   # → bytearray(b'    42')",
        "print(bytearray(b'7').rjust(3, b'0'))   # → bytearray(b'007')",
        "print(bytearray(b'hello').rjust(3, b'.'))   # → bytearray(b'hello')",
        "print(bytearray(b'hi').ljust(5, b'.'))   # → bytearray(b'hi...')"
      ],
      "related": [
        "bytearray.ljust",
        "bytearray.zfill",
        "bytearray.center",
        "bytes.rjust"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.rpartition",
      "title": "bytearray.rpartition",
      "kind": "function",
      "summary": {
        "ru": "Делит по последнему разделителю на тройку.",
        "en": "Split at the last separator into a triple."
      },
      "body": {
        "ru": "Кортеж всегда ровно из трёх элементов, так что распаковка head, sep, tail = ba.rpartition(b'=') не упадёт никогда. Но когда разделителя в данных нет, пустыми оказываются первые два элемента, а исходное содержимое уезжает в третий — у partition ровно наоборот. Поэтому факт находки проверяют по среднему элементу, а не по длине кортежа и не по тому, что head непустой.",
        "en": "The result is always a 3-tuple, so head, sep, tail = ba.rpartition(b'=') can never fail to unpack. When the separator is absent, though, it is the first two parts that come back empty and the original content that lands in the third — the mirror image of partition. Test the middle element to decide whether a match happened; never rely on tuple length or on head being non-empty."
      },
      "syntax": "ba.rpartition(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.rpartition",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "разделить байтовый массив по последнему разделителю",
        "отделить хвост байтового массива после разделителя"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'a=b=c').rpartition(b'='))   # → (bytearray(b'a=b'), bytearray(b'='), bytearray(b'c'))",
        "print(bytearray(b'a=b=c').partition(b'='))   # → (bytearray(b'a'), bytearray(b'='), bytearray(b'b=c'))",
        "print(bytearray(b'key: value').rpartition(b': ')[2])   # → bytearray(b'value')",
        "print(bytearray(b'/usr/local/bin').rpartition(b'/')[2])   # → bytearray(b'bin')",
        "print(bytearray(b'abc').rpartition(b'='))   # → (bytearray(b''), bytearray(b''), bytearray(b'abc'))"
      ],
      "related": [
        "bytearray.partition",
        "bytearray.rsplit",
        "bytes.rpartition"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytearray.rsplit",
      "title": "bytearray.rsplit",
      "kind": "function",
      "summary": {
        "ru": "Как split, но справа и с ограничением числа разбиений.",
        "en": "Like split, but from the right with a max count."
      },
      "body": {
        "ru": "Без maxsplit rsplit возвращает ровно то же, что split, — смысл появляется только когда надо отрезать хвост: последний сегмент пути, расширение файла, последнее поле строки. Разделитель тоже ведёт себя по-разному: с None пробельные схлопываются и краевые пустые куски не появляются, а с явным b' ' два пробела подряд дадут между собой пустой bytearray.",
        "en": "With no maxsplit, rsplit gives exactly what split gives — it earns its keep only when you need to peel something off the end: the last path segment, a file extension, the trailing field of a record. The separator matters too: with None runs of whitespace collapse and no empty edge pieces appear, while an explicit b' ' turns two adjacent spaces into an empty bytearray between them."
      },
      "syntax": "ba.rsplit(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.rsplit",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "разбить байтовый массив с конца",
        "разделить байтовый массив справа с ограничением"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'a b c').rsplit(None, 1))   # → [bytearray(b'a b'), bytearray(b'c')]",
        "print(bytearray(b'a b c').rsplit())   # → [bytearray(b'a'), bytearray(b'b'), bytearray(b'c')]",
        "print(bytearray(b'a,b,c').rsplit(b',', 1))   # → [bytearray(b'a,b'), bytearray(b'c')]",
        "print(bytearray(b'a,b,c').split(b',', 1))   # → [bytearray(b'a'), bytearray(b'b,c')]",
        "print(bytearray(b'name.tar.gz').rsplit(b'.', 1)[-1])   # → bytearray(b'gz')",
        "print(bytearray(b'').rsplit(b','))   # → [bytearray(b'')]"
      ],
      "related": [
        "bytearray.split",
        "bytearray.rpartition",
        "bytes.rsplit"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.rstrip",
      "title": "bytearray.rstrip",
      "kind": "function",
      "summary": {
        "ru": "Убирает байты справа.",
        "en": "Strip bytes from the right."
      },
      "body": {
        "ru": "Аргумент — набор байтов, а не суффикс: rstrip(b'an') срезает с конца подряд идущие 'a' и 'n', поэтому b'banana' сжимается до b'b'. Когда нужно убрать именно окончание целиком, берите removesuffix() (3.9+). Результат — новый bytearray, исходный не меняется.",
        "en": "The argument is a set of bytes, not a suffix: rstrip(b'an') keeps removing trailing 'a' and 'n' bytes, so b'banana' collapses to b'b'. If you mean \"drop this exact ending\", use removesuffix() (3.9+). The call returns a new bytearray and leaves the original as it was."
      },
      "syntax": "ba.rstrip(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.rstrip",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — обрезка",
      "color_group": "seq",
      "aliases": [
        "убрать пробельные байты справа",
        "обрезать байтовый массив справа"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'hi  ').rstrip())   # → bytearray(b'hi')",
        "print(bytearray(b'file.txt...').rstrip(b'.'))   # → bytearray(b'file.txt')",
        "print(bytearray(b'xxhixx').rstrip(b'x'))   # → bytearray(b'xxhi')",
        "print(bytearray(b'banana').rstrip(b'an'))   # → bytearray(b'b')",
        "print(bytearray(b'  hi  ').strip())   # → bytearray(b'hi')"
      ],
      "related": [
        "bytearray.lstrip",
        "bytearray.strip",
        "bytearray.removesuffix",
        "bytes.rstrip"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.split",
      "title": "bytearray.split",
      "kind": "function",
      "summary": {
        "ru": "Разбивает по разделителю (по умолчанию по пробельным).",
        "en": "Split on a separator (whitespace by default)."
      },
      "body": {
        "ru": "Разбить «побайтово», передав пустой разделитель, нельзя: split(b'') бросает ValueError, а для отдельных байтов берут срезы или list(ba), который вернёт целые числа, а не куски. Куски из списка — самостоятельные копии bytearray: их можно менять, исходный объект от этого не изменится.",
        "en": "You cannot split into individual bytes by passing an empty separator: split(b'') raises ValueError. For per-byte access use slices, or list(ba), which yields integers rather than byte chunks. The pieces in the returned list are independent bytearray copies — mutating them leaves the original object untouched."
      },
      "syntax": "ba.split(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.split",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "разбить байтовый массив на части по разделителю",
        "разделить байтовый массив по пробелам"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'a b').split())   # → [bytearray(b'a'), bytearray(b'b')]",
        "print(bytearray(b'a,b,c').split(b','))   # → [bytearray(b'a'), bytearray(b'b'), bytearray(b'c')]",
        "print(bytearray(b'a,b,c').split(b',', 1))   # → [bytearray(b'a'), bytearray(b'b,c')]",
        "print(len(bytearray(b'  one  two  ').split()))   # → 2",
        "print(bytearray(b'a,,b').split(b','))   # → [bytearray(b'a'), bytearray(b''), bytearray(b'b')]",
        "print(bytearray(b'   ').split())   # → []"
      ],
      "related": [
        "bytearray.rsplit",
        "bytearray.join",
        "bytearray.splitlines",
        "bytes.split"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.splitlines",
      "title": "bytearray.splitlines",
      "kind": "function",
      "summary": {
        "ru": "Разбивает по границам строк.",
        "en": "Split at line boundaries."
      },
      "body": {
        "ru": "В отличие от split(b'\\n'), завершающий перевод строки не порождает лишний пустой элемент в конце, а пустой bytearray даёт пустой список — поэтому для построчного разбора данных берут именно splitlines. Для байтов границами считаются только \\n, \\r и \\r\\n (у str их заметно больше), а keepends=True оставляет сам разделитель приклеенным к концу строки.",
        "en": "Unlike split(b'\\n'), a trailing newline does not produce a spurious empty last element, and an empty bytearray yields an empty list — which is why line-by-line parsing uses splitlines. For byte data only \\n, \\r and \\r\\n count as boundaries (str recognises considerably more), and keepends=True keeps the terminator attached to each line."
      },
      "syntax": "ba.splitlines(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.splitlines",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "разбить байтовый массив на строки",
        "байтовый массив по переводам строки"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'a\\nb').splitlines())   # → [bytearray(b'a'), bytearray(b'b')]",
        "print(bytearray(b'a\\nb').splitlines(True))   # → [bytearray(b'a\\n'), bytearray(b'b')]",
        "print(bytearray(b'a\\r\\nb\\rc').splitlines())   # → [bytearray(b'a'), bytearray(b'b'), bytearray(b'c')]",
        "print(len(bytearray(b'line1\\nline2\\nline3').splitlines()))   # → 3",
        "print(bytearray(b'a\\nb\\n').splitlines())   # → [bytearray(b'a'), bytearray(b'b')]",
        "print(bytearray(b'a\\nb\\n').split(b'\\n'))   # → [bytearray(b'a'), bytearray(b'b'), bytearray(b'')]"
      ],
      "related": [
        "bytearray.split",
        "bytes.splitlines",
        "bytearray.join"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.startswith",
      "title": "bytearray.startswith",
      "kind": "function",
      "summary": {
        "ru": "Проверяет заданный префикс.",
        "en": "Check for a given prefix."
      },
      "body": {
        "ru": "Аргументом можно передать кортеж префиксов — это заменяет цепочку проверок через or. В отличие от find, целое число здесь не примут, а str даст TypeError: байты со строкой Python сравнивать не станет. Если префикс надо не проверить, а отрезать, есть removeprefix() (Python 3.9+).",
        "en": "Pass a tuple of prefixes and one call replaces a chain of or-ed checks. Unlike find, this method rejects a bare int, and handing it a str raises TypeError — Python will not compare bytes against text. When you want to strip the prefix rather than test for it, removeprefix() (Python 3.9+) does that directly."
      },
      "syntax": "ba.startswith(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.startswith",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — поиск",
      "color_group": "seq",
      "aliases": [
        "проверить начало байтового массива",
        "начинается ли с нужных байтов"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'hello').startswith(b'he'))   # → True",
        "print(bytearray(b'hello').startswith(b'lo'))   # → False",
        "print(bytearray(b'hello').startswith((b'ha', b'he')))   # → True",
        "print(bytearray(b'hello').startswith(b'll', 2))   # → True",
        "print(bytearray(b'hello').startswith(b''))   # → True",
        "print(bytearray(b'hello').startswith('he'))   # → TypeError"
      ],
      "related": [
        "bytearray.endswith",
        "bytearray.removeprefix",
        "bytes.startswith"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytearray.strip",
      "title": "bytearray.strip",
      "kind": "function",
      "summary": {
        "ru": "Убирает байты (по умолчанию пробельные) с обоих концов.",
        "en": "Strip bytes (whitespace by default) from both ends."
      },
      "body": {
        "ru": "Без аргумента убираются только ASCII-пробельные байты (пробел, \\t, \\n, \\r, \\v, \\f): у байтов нет понятия юникодных пробелов, так что неразрывный пробел из UTF-8 уцелеет. И хотя bytearray изменяемый, strip() не правит его на месте — он возвращает новый объект, результат нужно присвоить.",
        "en": "With no argument only ASCII whitespace bytes are removed (space, \\t, \\n, \\r, \\v, \\f); bytes have no notion of Unicode spaces, so a UTF-8 non-breaking space survives. And although bytearray is mutable, strip() is not in-place: it hands back a new object that you have to assign somewhere."
      },
      "syntax": "ba.strip(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.strip",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — обрезка",
      "color_group": "seq",
      "aliases": [
        "убрать пробельные байты с обоих концов",
        "обрезать байтовый массив с двух сторон"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'  hi  ').strip())   # → bytearray(b'hi')",
        "print(bytearray(b'xxhixx').strip(b'x'))   # → bytearray(b'hi')",
        "print(bytearray(b'\\n data \\t').strip())   # → bytearray(b'data')",
        "print(bytearray(b'ababhiba').strip(b'ab'))   # → bytearray(b'hi')",
        "print(bytearray(b'hello').strip(b'lo'))   # → bytearray(b'he')",
        "print(bytearray(b'  hi  ').lstrip())   # → bytearray(b'hi  ')"
      ],
      "related": [
        "bytearray.lstrip",
        "bytearray.rstrip",
        "bytes.strip"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.swapcase",
      "title": "bytearray.swapcase",
      "kind": "function",
      "summary": {
        "ru": "Меняет регистр ASCII-букв на противоположный.",
        "en": "Swap the case of ASCII letters."
      },
      "body": {
        "ru": "На байтах операция строго обратима: применив её дважды, вы получите исходные данные, потому что затрагиваются только пары A-Z и a-z. У строк такой гарантии нет — 'ß'.swapcase() даёт 'SS', и обратно уже не собрать; не переносите привычки str на bytearray вслепую. Как и остальные регистровые методы, он возвращает копию, а не меняет объект на месте.",
        "en": "On bytes the operation is exactly reversible: apply it twice and you get the original data back, since only the A-Z/a-z pairs are touched. Strings give no such guarantee — 'ß'.swapcase() yields 'SS', which cannot be swapped back — so do not carry str habits over to bytearray unchecked. Like the other case methods, it returns a copy instead of mutating in place."
      },
      "syntax": "ba.swapcase(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.swapcase",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — регистр",
      "color_group": "seq",
      "aliases": [
        "поменять регистр байтового массива на противоположный",
        "инвертировать регистр байтового массива"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'AbC').swapcase())   # → bytearray(b'aBc')",
        "print(bytearray(b'Hello World').swapcase())   # → bytearray(b'hELLO wORLD')",
        "print(bytearray(b'abc123!').swapcase())   # → bytearray(b'ABC123!')",
        "ba = bytearray(b'Xy')",
        "print(ba.swapcase())   # → bytearray(b'xY')",
        "print(ba)   # → bytearray(b'Xy')"
      ],
      "related": [
        "bytearray.upper",
        "bytearray.lower",
        "bytes.swapcase"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.title",
      "title": "bytearray.title",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию в «заголовочном» регистре.",
        "en": "Return a title-cased copy."
      },
      "body": {
        "ru": "Словом здесь считается любая цепочка ASCII-букв, а любой другой байт — граница: цифры, дефисы и апострофы начинают новое «слово», поэтому сокращения и формы с апострофом разваливаются. Для человекочитаемых заголовков это почти всегда не то, что нужно, тем более что не-ASCII байты метод не трогает вовсе — разбирайте текст после decode() по словам вручную.",
        "en": "A word here is just a run of ASCII letters, and every other byte is a boundary — digits, hyphens and apostrophes all start a new \"word\", which mangles contractions and hyphenated forms. For human-readable headings this is rarely what you want, especially since non-ASCII bytes are left alone entirely; decode() first and capitalise word by word yourself."
      },
      "syntax": "ba.title(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.title",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — регистр",
      "color_group": "seq",
      "aliases": [
        "каждое слово с большой буквы в байтовом массиве",
        "заголовочный регистр байтового массива"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'hi there').title())   # → bytearray(b'Hi There')",
        "print(bytearray(b'HELLO WORLD').title())   # → bytearray(b'Hello World')",
        "print(bytearray(b'a1b2 c3').title())   # → bytearray(b'A1B2 C3')",
        "print(bytearray(b\"they're here\").title())   # → bytearray(b\"They\\'Re Here\")",
        "ba = bytearray(b'abc')",
        "print(ba.title(), ba)   # → bytearray(b'Abc') bytearray(b'abc')"
      ],
      "related": [
        "bytearray.istitle",
        "bytearray.capitalize",
        "bytes.title"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.translate",
      "title": "bytearray.translate",
      "kind": "function",
      "summary": {
        "ru": "Заменяет байты по таблице перевода (256 байт).",
        "en": "Map bytes through a 256-byte translation table."
      },
      "body": {
        "ru": "Таблица — ровно 256 байт (проще всего получить из bytes.maketrans()), иначе ValueError; None означает «ничего не отображать», только удалить перечисленное в delete. Удаление выполняется до перевода, и всё идёт побайтово — для кириллицы в UTF-8 это не работает, там один символ занимает несколько байт.",
        "en": "The table must be exactly 256 bytes long (bytes.maketrans() builds one), otherwise you get a ValueError; None means \"map nothing\" and only applies delete. Deletion happens before mapping, and the whole operation is byte-by-byte, so it cannot rewrite multi-byte UTF-8 characters — but for plain byte swaps one pass beats a chain of replace calls."
      },
      "syntax": "ba.translate(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.translate",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — замена и перевод",
      "color_group": "seq",
      "aliases": [
        "заменить байты по готовой таблице",
        "удалить байты по таблице перевода"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'abc').translate(bytes.maketrans(b'a', b'X')))   # → bytearray(b'Xbc')",
        "table = bytes.maketrans(b'abc', b'xyz')",
        "print(bytearray(b'cabbage').translate(table))   # → bytearray(b'zxyyxge')",
        "print(bytearray(b'a-b-c').translate(None, delete=b'-'))   # → bytearray(b'abc')",
        "print(bytearray(b'1-800-555').translate(bytes.maketrans(b'0', b'O'), delete=b'-'))   # → bytearray(b'18OO555')",
        "print(bytearray(b'abc').translate(None))   # → bytearray(b'abc')"
      ],
      "related": [
        "bytearray.maketrans",
        "bytearray.replace",
        "bytes.translate"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "bytearray.upper",
      "title": "bytearray.upper",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию с ASCII-буквами в верхнем регистре.",
        "en": "Return a copy with ASCII letters upper-cased."
      },
      "body": {
        "ru": "Это чистая ASCII-таблица: байт вне диапазона a-z остаётся собой, а от локали системы результат не зависит — в отличие от toupper() в C. Возвращается новый bytearray, исходный не меняется, и сравнивать его с литералом bytes можно напрямую: bytearray и bytes сопоставляются по содержимому, а не по типу.",
        "en": "This is a pure ASCII table: any byte outside a-z stays as it is, and unlike C's toupper() the result never depends on the system locale. A fresh bytearray comes back and the original is untouched; comparing it to a bytes literal works fine, because bytearray and bytes compare by content rather than by type."
      },
      "syntax": "ba.upper(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.upper",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — регистр",
      "color_group": "seq",
      "aliases": [
        "байтовый массив в верхний регистр",
        "большие буквы в байтовом массиве"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'abc').upper())   # → bytearray(b'ABC')",
        "print(bytearray(b'Hello, World! 42').upper())   # → bytearray(b'HELLO, WORLD! 42')",
        "print(bytearray(b'caf\\xc3\\xa9').upper())   # → bytearray(b'CAF\\xc3\\xa9')",
        "print(bytearray(b'Yes').upper() == b'YES')   # → True",
        "ba = bytearray(b'abc')",
        "print(ba.upper(), ba)   # → bytearray(b'ABC') bytearray(b'abc')"
      ],
      "related": [
        "bytearray.lower",
        "bytearray.isupper",
        "bytes.upper"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray.zfill",
      "title": "bytearray.zfill",
      "kind": "function",
      "summary": {
        "ru": "Дополняет слева нулями до заданной длины.",
        "en": "Pad on the left with zeros to a given length."
      },
      "body": {
        "ru": "Единственное, чем zfill отличается от rjust с нулём: ведущий ASCII-байт + или - остаётся на месте, а нули вставляются после него. Это чисто текстовое дополнение байтов, а не форматирование числа — на b'3.5' или на любой не-цифровой мусор оно сработает точно так же, и слишком длинное значение не обрезается.",
        "en": "The one thing zfill adds over rjust with a zero fill: a leading ASCII + or - stays in front and the zeros go after it. It is plain byte padding, not number formatting — b'3.5' or any non-digit content is padded just the same, and values longer than the requested width are returned unchanged."
      },
      "syntax": "ba.zfill(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytearray.zfill",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytearray — выравнивание",
      "color_group": "seq",
      "aliases": [
        "дополнить байты нулями слева",
        "ведущие нули в байтах"
      ],
      "keywords": [],
      "tags": [
        "bytearray"
      ],
      "examples": [
        "print(bytearray(b'42').zfill(5))   # → bytearray(b'00042')",
        "print(bytearray(b'-42').zfill(5))   # → bytearray(b'-0042')",
        "print(bytearray(b'12345').zfill(3))   # → bytearray(b'12345')",
        "print(bytearray(b'7').zfill(2) + b':' + bytearray(b'5').zfill(2))   # → bytearray(b'07:05')",
        "print(bytearray(b'').zfill(3))   # → bytearray(b'000')"
      ],
      "related": [
        "bytearray.rjust",
        "bytes.zfill",
        "bytearray.center"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.capitalize",
      "title": "bytes.capitalize",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию: первый байт в верхнем регистре, остальные — в нижнем.",
        "en": "Return a copy with the first byte upper-cased and the rest lower-cased."
      },
      "body": {
        "ru": "Регистр меняется только у ASCII-букв: закодированный в UTF-8 текст с кириллицей или умлаутами вернётся неизменным, поэтому меняйте регистр у str и кодируйте уже результат. Второй нюанс — хвост принудительно опускается в нижний регистр, так что внутренние заглавные (аббревиатуры, CamelCase) теряются безвозвратно.",
        "en": "Only ASCII letters are touched: UTF-8-encoded Cyrillic or accented text comes back unchanged, so change case on the str and encode afterwards. Also note the tail is forced to lower case, which silently destroys internal capitals such as acronyms or CamelCase names."
      },
      "syntax": "b.capitalize(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.capitalize",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — регистр",
      "color_group": "seq",
      "aliases": [
        "первый байт с заглавной буквы",
        "байты с большой буквы"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'hello'.capitalize())   # → b'Hello'",
        "print(b'hELLO wORLD'.capitalize())   # → b'Hello world'",
        "print(b'123ABC'.capitalize())   # → b'123abc'",
        "print(b''.capitalize())   # → b''",
        "print(b'hello world'.capitalize(), b'hello world'.title())   # → b'Hello world' b'Hello World'"
      ],
      "related": [
        "bytes.title",
        "bytes.lower",
        "str.capitalize"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.center",
      "title": "bytes.center",
      "kind": "function",
      "summary": {
        "ru": "Центрирует байты в поле заданной ширины, заполняя байтом-заполнителем.",
        "en": "Center the bytes in a field of the given width using a fill byte."
      },
      "body": {
        "ru": "Заполнитель — ровно один байт: b'ab' или пустое значение дадут TypeError, а обычная строка '*' не подойдёт вовсе, нужны именно байты. Метод никогда не обрезает — если width меньше длины данных, вернётся исходный объект без изменений. Когда добивка не делится пополам, лишний байт ложится несимметрично и сторона зависит от чётности ширины, так что для точной вёрстки надёжнее ljust() или rjust().",
        "en": "The fill has to be exactly one byte: b'ab' or an empty value raise TypeError, and a plain str like '*' is rejected outright — it must be bytes. The method never truncates; if width is smaller than the data, the original object comes back unchanged. When the padding does not split evenly the extra byte lands asymmetrically and which side gets it depends on the parity of width, so reach for ljust() or rjust() when the exact layout matters."
      },
      "syntax": "b.center(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.center",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — выравнивание",
      "color_group": "seq",
      "aliases": [
        "выровнять байты по центру",
        "центрировать байтовую строку"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'hi'.center(6, b'*'))   # → b'**hi**'",
        "print(b'hi'.center(6))   # → b'  hi  '",
        "print(b'ab'.center(5, b'-'))   # → b'--ab-'",
        "print(b'hello'.center(3, b'*'))   # → b'hello'",
        "print(b'hi'.rjust(6, b'.'))   # → b'....hi'"
      ],
      "related": [
        "bytes.ljust",
        "bytes.rjust",
        "bytes.zfill",
        "str.center"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.count",
      "title": "bytes.count",
      "kind": "function",
      "summary": {
        "ru": "Считает число непересекающихся вхождений подпоследовательности.",
        "en": "Count non-overlapping occurrences of a subsequence."
      },
      "body": {
        "ru": "Вхождения считаются без наложений: найдя кусок, поиск продолжается сразу за ним, поэтому у самоперекрывающихся образцов результат меньше, чем кажется на глаз. Искать можно любой bytes-подобный объект или целое 0-255 (один байт), а вот str методу передавать нельзя — будет TypeError. Пустая подпоследовательность считается в каждом промежутке и даёт len(b) + 1.",
        "en": "Matches are counted without overlap: after a hit the scan resumes right past it, so self-overlapping patterns yield fewer counts than a naive reading suggests. The argument may be any bytes-like object or an integer in range 0-255 (a single byte), but never a str — that raises TypeError. An empty subsequence matches in every gap and returns len(b) + 1."
      },
      "syntax": "b.count(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.count",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — поиск",
      "color_group": "seq",
      "aliases": [
        "сколько раз встречаются байты",
        "посчитать вхождения в байтовой строке"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'banana'.count(b'a'))   # → 3",
        "print(b'banana'.count(b'an'))   # → 2",
        "print(b'aaaa'.count(b'aa'))   # → 2",
        "print(b'banana'.count(b'a', 2))   # → 2",
        "print(b'banana'.count(97))   # → 3",
        "print(b'abc'.count(b''))   # → 4"
      ],
      "related": [
        "bytes.find",
        "str.count",
        "bytes.replace"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytes.decode",
      "title": "bytes.decode",
      "kind": "function",
      "summary": {
        "ru": "Декодирует байты в строку (str) по заданной кодировке (по умолчанию UTF-8).",
        "en": "Decode the bytes to a str using an encoding (UTF-8 by default)."
      },
      "body": {
        "ru": "По умолчанию errors='strict', и любой байт, не укладывающийся в кодировку, даёт UnicodeDecodeError; режимы 'replace' и 'ignore' гасят исключение ценой порчи текста. Кодировка latin-1 не падает никогда, потому что покрывает все 256 значений байта, — так что кодировка, при которой ошибка «наконец исчезла», чаще всего просто выдаёт мусор. Если байты приходят кусками из сети или файла, разрез посреди многобайтового символа тоже уронит decode — для потока нужен инкрементальный декодер.",
        "en": "The default errors='strict' turns any byte that does not fit the encoding into UnicodeDecodeError; 'replace' and 'ignore' silence it but corrupt the text in the process. latin-1 never fails because it maps all 256 byte values, so an encoding that \"finally stopped erroring\" is usually producing garbage rather than the right characters. Data arriving in chunks breaks as well when a split lands mid-character — that case calls for an incremental decoder instead."
      },
      "syntax": "b.decode(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.decode",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — кодирование",
      "color_group": "seq",
      "aliases": [
        "байты в строку",
        "декодировать байты",
        "преобразовать байты в текст"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'hi'.decode())   # → hi",
        "print(b'\\xd0\\xbf\\xd1\\x80\\xd0\\xb8\\xd0\\xb2\\xd0\\xb5\\xd1\\x82'.decode('utf-8'))   # → привет",
        "print(b'caf\\xe9'.decode('latin-1'))   # → café",
        "print(b'\\xff'.decode('utf-8'))   # → UnicodeDecodeError",
        "print(b'a\\xffb'.decode('utf-8', errors='ignore'))   # → ab",
        "print('hi'.encode())   # → b'hi'"
      ],
      "related": [
        "str.encode",
        "unicodedecodeerror",
        "bytes",
        "b-...-байт-строки"
      ],
      "related_errors": [
        "UnicodeDecodeError"
      ]
    },
    {
      "id": "bytes.endswith",
      "title": "bytes.endswith",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, заканчиваются ли байты заданным суффиксом.",
        "en": "Check whether the bytes end with a suffix."
      },
      "body": {
        "ru": "Суффикс должен быть bytes или кортежем bytes: ни str, ни код байта здесь не примут (в отличие от count/find, которые целое число понимают) — получите TypeError. Аргументы start и end сначала мысленно вырезают срез, и суффикс сверяется с концом этого среза, а не всего объекта.",
        "en": "The suffix must be bytes or a tuple of bytes: a str or a raw byte code is rejected with TypeError, unlike count/find, which do accept an integer. The start and end arguments carve out a slice first, and the suffix is matched against the end of that slice rather than of the whole object."
      },
      "syntax": "b.endswith(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.endswith",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — поиск",
      "color_group": "seq",
      "aliases": [
        "байтовая строка заканчивается на",
        "проверить окончание байтов"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'hello'.endswith(b'lo'))   # → True",
        "print(b'report.txt'.endswith((b'.txt', b'.md')))   # → True",
        "print(b'hello'.endswith(b'hell', 0, 4))   # → True",
        "print(b'hello'.endswith(b''))   # → True",
        "print(b'hello'.endswith('lo'))   # → TypeError",
        "print(b'hello'.startswith(b'he'))   # → True"
      ],
      "related": [
        "bytes.startswith",
        "bytes.removesuffix",
        "bytes.rfind",
        "str.endswith"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytes.expandtabs",
      "title": "bytes.expandtabs",
      "kind": "function",
      "summary": {
        "ru": "Заменяет символы табуляции пробелами до следующей позиции табуляции.",
        "en": "Replace tab characters with spaces up to the next tab stop."
      },
      "body": {
        "ru": "Метод не подменяет каждый таб фиксированным числом пробелов: он добивает до ближайшей позиции, кратной tabsize, поэтому ширина вставки зависит от того, сколько байтов уже стоит слева от табуляции. Счётчик колонки обнуляется на каждом переводе строки и возврате каретки, так что многострочный блок обрабатывается построчно. По умолчанию tabsize равен 8 — отсюда и берутся неожиданно широкие провалы в выводе.",
        "en": "It does not swap each tab for a fixed number of spaces: it pads out to the next multiple of tabsize, so how much is inserted depends on how many bytes already sit to the left of the tab. The column counter resets at every newline and carriage return, so a multi-line block is processed line by line. The default tabsize is 8, which is where surprisingly wide gaps in output usually come from."
      },
      "syntax": "b.expandtabs(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.expandtabs",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — выравнивание",
      "color_group": "seq",
      "aliases": [
        "заменить табуляцию пробелами в байтах",
        "развернуть табы в байтовой строке"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'a\\tb'.expandtabs(4))   # → b'a   b'",
        "print(b'a\\tb'.expandtabs())   # → b'a       b'",
        "print(b'ab\\tc'.expandtabs(4))   # → b'ab  c'",
        "print(b'abcd\\te'.expandtabs(4))   # → b'abcd    e'",
        "print(b'a\\tb\\ncd\\te'.expandtabs(4))   # → b'a   b\\ncd  e'",
        "print(b'a\\tb'.expandtabs(0))   # → b'ab'"
      ],
      "related": [
        "str.expandtabs",
        "bytes.replace",
        "bytes.ljust"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.find",
      "title": "bytes.find",
      "kind": "function",
      "summary": {
        "ru": "Возвращает индекс первого вхождения подпоследовательности или -1.",
        "en": "Return the index of the first occurrence, or -1."
      },
      "body": {
        "ru": "Отсутствие подпоследовательности возвращается как -1, а не как исключение, и это ловушка в условиях: -1 истинно, а совершенно нормальный индекс 0 ложен, поэтому сравнивайте результат явно с -1. Если нужен только факт наличия, читабельнее оператор in; find берут тогда, когда по индексу дальше режут срез.",
        "en": "A miss comes back as -1 rather than an exception, which trips people up in conditions: -1 is truthy while a perfectly valid index of 0 is falsy, so always compare the result to -1 explicitly. If you only need to know whether the subsequence is there, the in operator reads better; reach for find when you will slice at the returned position."
      },
      "syntax": "b.find(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.find",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — поиск",
      "color_group": "seq",
      "aliases": [
        "позиция байтов в байтовой строке",
        "искать байты, минус один если нет"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'banana'.find(b'na'))   # → 2",
        "print(b'banana'.find(b'na', 3))   # → 4",
        "print(b'name=Ann'.find(b'='))   # → 4",
        "code = ord('n')",
        "print(b'banana'.find(code))   # → 2",
        "print(b'banana'.find(b'z'))   # → -1"
      ],
      "related": [
        "bytes.index",
        "bytes.rfind",
        "bytes.startswith",
        "str.find"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytes.fromhex",
      "title": "bytes.fromhex",
      "kind": "function",
      "summary": {
        "ru": "Статический метод: создаёт bytes из шестнадцатеричной строки.",
        "en": "A static method creating bytes from a hex string."
      },
      "body": {
        "ru": "Строка должна содержать чётное число шестнадцатеричных цифр, иначе ValueError; регистр не важен, пробельные символы между парами цифр пропускаются, а вот внутри пары — нет. Это точная обратная операция к b.hex(); такой же метод есть у bytearray и возвращает изменяемый объект.",
        "en": "The string needs an even number of hex digits or you get ValueError; case is irrelevant and ASCII whitespace between digit pairs is skipped, though whitespace inside a pair is not. It is the exact inverse of b.hex(), and bytearray.fromhex() gives you the mutable counterpart."
      },
      "syntax": "b.fromhex(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.fromhex",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — кодирование",
      "color_group": "seq",
      "aliases": [
        "из шестнадцатеричной строки в байты",
        "hex-строка в байты"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(bytes.fromhex('48656c6c6f'))   # → b'Hello'",
        "print(bytes.fromhex('48 65 6c'))   # → b'Hel'",
        "print(bytes.fromhex('ff00'))   # → b'\\xff\\x00'",
        "print(bytes.fromhex('4a2b').hex())   # → 4a2b",
        "print(bytes.fromhex('abc'))   # → ValueError"
      ],
      "related": [
        "bytes.hex",
        "bytes",
        "int.from_bytes"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "bytes.hex",
      "title": "bytes.hex",
      "kind": "function",
      "summary": {
        "ru": "Возвращает шестнадцатеричное представление байтов как строку.",
        "en": "Return a hexadecimal string of the bytes."
      },
      "body": {
        "ru": "Цифры всегда в нижнем регистре — верхний придётся делать .upper() самому. Параметры sep и bytes_per_sep появились в 3.8, причём положительный bytes_per_sep группирует байты с конца, а отрицательный — от начала. Не путать со встроенной hex() для чисел: та даёт одно число с префиксом 0x, а не побайтовую строку.",
        "en": "The digits always come out lowercase, so uppercase takes an explicit .upper(). The sep and bytes_per_sep parameters arrived in 3.8, and a positive bytes_per_sep groups counting from the right end while a negative one counts from the left. Do not mix it up with the built-in hex() for integers, which yields a single 0x-prefixed number rather than a per-byte string."
      },
      "syntax": "b.hex(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.hex",
      "version": "3.5",
      "section": "Байтовые последовательности",
      "subcat": "bytes — кодирование",
      "color_group": "seq",
      "aliases": [
        "байты в шестнадцатеричную строку",
        "шестнадцатеричное представление байтов",
        "байты в hex-строку"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'abc'.hex())   # → 616263",
        "print(b'abc'.hex('-'))   # → 61-62-63",
        "print(b'\\xde\\xad\\xbe\\xef'.hex(' ', 2))   # → dead beef",
        "print(bytes.fromhex(b'abc'.hex()))   # → b'abc'",
        "print(repr(b''.hex()))   # → ''"
      ],
      "related": [
        "bytes.fromhex",
        "hex",
        "bytes.decode"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.index",
      "title": "bytes.index",
      "kind": "function",
      "summary": {
        "ru": "Как find, но бросает ValueError, если подпоследовательность не найдена.",
        "en": "Like find, but raises ValueError if not found."
      },
      "body": {
        "ru": "Выбор между index и find — это выбор политики: index, когда отсутствие подпоследовательности означает битые данные и падение уместно, find — когда отсутствие штатно и обрабатывается веткой. Если оборачиваете в try, ловите именно ValueError, а не Exception: иначе заодно проглотите TypeError от случайно переданного str.",
        "en": "Choosing between index and find is a policy decision: index when a missing subsequence means broken data and failing loudly is right, find when absence is a normal case you branch on. If you wrap it in try, catch ValueError specifically, not Exception — a blanket catch would also swallow the TypeError from accidentally passing a str."
      },
      "syntax": "b.index(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.index",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — поиск",
      "color_group": "seq",
      "aliases": [
        "индекс байтов с ошибкой если не найдено",
        "поиск байтов с исключением"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'banana'.index(b'na'))   # → 2",
        "print(b'banana'.index(b'na', 3))   # → 4",
        "print(b'banana'.index(ord('n')))   # → 2",
        "print(b'banana'.index(b'z'))   # → ValueError",
        "print(b'banana'.find(b'z'))   # → -1"
      ],
      "related": [
        "bytes.find",
        "bytes.rindex",
        "str.index"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "bytes.isalnum",
      "title": "bytes.isalnum",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что все байты — ASCII-буквы или цифры и последовательность непуста.",
        "en": "Check that all bytes are ASCII letters/digits and non-empty."
      },
      "body": {
        "ru": "Проверка строго ASCII-байтовая, в отличие от str.isalnum, который принимает любую Unicode-букву или цифру: слово кириллицей как str даёт True, а те же байты после .encode() — False. Валидатором имён метод тоже не служит: подчёркивание, дефис и точка не проходят, так что для «только буквы и цифры» он годится, а для «допустимое имя файла» — нет.",
        "en": "The test is strictly ASCII, unlike str.isalnum, which accepts any Unicode letter or digit: a non-Latin word is True as a str and False once encoded to bytes. It is also not a name validator — underscore, hyphen and dot all fail, so it fits 'letters and digits only' but not 'valid filename' or 'valid identifier'."
      },
      "syntax": "b.isalnum(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.isalnum",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — проверки is*",
      "color_group": "seq",
      "aliases": [
        "байты только буквы и цифры",
        "проверка байтов на буквы и цифры"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'abc123'.isalnum())   # → True",
        "print('42'.encode().isalnum())   # → True",
        "print(b'abc 123'.isalnum())   # → False",
        "print(b'user_42'.isalnum())   # → False",
        "print(b''.isalnum())   # → False"
      ],
      "related": [
        "bytes.isalpha",
        "bytes.isdigit",
        "bytes.isascii",
        "str.isalnum"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.isalpha",
      "title": "bytes.isalpha",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что все байты — ASCII-буквы и последовательность непуста.",
        "en": "Check that all bytes are ASCII letters and non-empty."
      },
      "body": {
        "ru": "Байты сверяются поштучно с диапазонами A-Z и a-z, поэтому любой нелатинский текст после .encode() даёт False: его UTF-8 байты выходят за 127. Если вам нужны буквы вообще, а не ASCII-буквы, это сигнал сначала декодировать в str и звать str.isalpha.",
        "en": "Each byte is checked against A-Z and a-z only, so any non-Latin text run through .encode() comes back False — its UTF-8 bytes are above 127. If you mean letters in general rather than ASCII letters, decode to str first and call str.isalpha."
      },
      "syntax": "b.isalpha(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.isalpha",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — проверки is*",
      "color_group": "seq",
      "aliases": [
        "байты только из букв",
        "проверка байтов на буквы"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'abc'.isalpha())   # → True",
        "print(b'Hello'.isalpha())   # → True",
        "print(b'abc1'.isalpha())   # → False",
        "print(b'hi there'.isalpha())   # → False",
        "print(b''.isalpha())   # → False",
        "print('привет'.encode().isalpha())   # → False"
      ],
      "related": [
        "bytes.isalnum",
        "bytes.isdigit",
        "bytes.isascii",
        "str.isalpha"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.isascii",
      "title": "bytes.isascii",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что все байты — ASCII (0–127); пустая последовательность тоже True.",
        "en": "Check that all bytes are ASCII (0–127)."
      },
      "body": {
        "ru": "ASCII здесь означает только диапазон 0-127, а не «печатаемый текст»: нулевой байт, перевод строки и прочие управляющие символы проверку проходят. Практическая польза — гарантия: если вернулось True, то decode('ascii') точно не упадёт, и любая ASCII-совместимая кодировка даст тот же результат. Метод появился в Python 3.7.",
        "en": "ASCII here means only the 0-127 range, not 'printable text': a null byte, a newline and other control codes all pass. The practical value is the guarantee — a True result means decode('ascii') cannot fail, and any ASCII-compatible encoding yields the same characters. Added in Python 3.7."
      },
      "syntax": "b.isascii(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.isascii",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — проверки is*",
      "color_group": "seq",
      "aliases": [
        "байты только ascii-символы",
        "проверка байтов на ascii-диапазон"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'abc'.isascii())   # → True",
        "print(b'Hi 123!\\n'.isascii())   # → True",
        "print(b''.isascii())   # → True",
        "print(b'\\x80'.isascii())   # → False",
        "print('привет'.encode().isascii())   # → False"
      ],
      "related": [
        "str.isascii",
        "bytearray.isascii",
        "bytes.decode"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.isdigit",
      "title": "bytes.isdigit",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что все байты — ASCII-цифры и последовательность непуста.",
        "en": "Check that all bytes are ASCII digits and non-empty."
      },
      "body": {
        "ru": "У bytes нет ни isdecimal(), ни isnumeric() — есть только isdigit(), и он признаёт ровно байты от b'0' до b'9', без надстрочных индексов и прочих юникодных цифр, которые пропустил бы str.isdigit(). Это проверка на «чистое поле из цифр», а не на «похоже на число»: знак, точка, пробелы по краям дают False, хотя int() такую строку байтов разберёт спокойно (int(b' -7 ') работает).",
        "en": "The bytes type has no isdecimal() or isnumeric() — only isdigit(), and it accepts exactly the bytes b'0' through b'9', with none of the superscripts or other Unicode digits that str.isdigit() would let through. Treat it as a check for a field of pure digits, not for \"looks like a number\": a sign, a dot or surrounding spaces all give False, even though int() would happily parse such bytes (int(b' -7 ') works)."
      },
      "syntax": "b.isdigit(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.isdigit",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — проверки is*",
      "color_group": "seq",
      "aliases": [
        "байты только из цифр",
        "проверка байтов на цифры"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'123'.isdigit())   # → True",
        "print(b'12.5'.isdigit())   # → False",
        "print(b'-7'.isdigit())   # → False",
        "raw = b'42'",
        "print(int(raw) if raw.isdigit() else 0)   # → 42",
        "print('²'.isdigit(), b'\\xb2'.isdigit())   # → True False"
      ],
      "related": [
        "str.isdigit",
        "bytes.isalnum",
        "bytearray.isdigit"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.islower",
      "title": "bytes.islower",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что все буквенные байты — в нижнем регистре.",
        "en": "Check that all cased bytes are lower-case."
      },
      "body": {
        "ru": "Ловушка: islower() — не отрицание isupper(). Если буквенных байтов нет вовсе (b'123', b'!!!', b''), оба вернут False, поэтому «если не islower(), значит верхний регистр» — неверный вывод. Регистровыми здесь считаются только ASCII-буквы: байты вроде b'\\xe9' (é в latin-1) для этой проверки вообще не буквы, так что для текста с не-ASCII символами сначала декодируйте в str.",
        "en": "A common trap: islower() is not the negation of isupper(). When there are no cased bytes at all (b'123', b'!!!', b''), both return False, so \"not lower-case therefore upper-case\" is a wrong inference. Only ASCII letters count as cased here: a byte like b'\\xe9' (é in latin-1) is not a letter for this check, so decode to str first when the data is not ASCII."
      },
      "syntax": "b.islower(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.islower",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — проверки is*",
      "color_group": "seq",
      "aliases": [
        "байты в нижнем регистре",
        "проверка байтов на строчные буквы"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'abc'.islower())   # → True",
        "print(b'abc1!'.islower())   # → True",
        "print(b'Hello'.lower().islower())   # → True",
        "print(b'Abc'.islower())   # → False",
        "print(b'123'.islower())   # → False",
        "print(b''.islower())   # → False"
      ],
      "related": [
        "bytes.isupper",
        "bytes.lower",
        "str.islower"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.isspace",
      "title": "bytes.isspace",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что все байты — пробельные и последовательность непуста.",
        "en": "Check that all bytes are whitespace and non-empty."
      },
      "body": {
        "ru": "Пробельными считаются только ASCII-байты: пробел, \\t, \\n, \\r, \\x0b и \\x0c. Юникодных пробелов тут нет — b'\\xa0'.isspace() даёт False, хотя у str одноимённый метод на неразрывном пробеле возвращает True. И помните про пустоту: b''.isspace() — False, поэтому «строка пустая или из пробелов» надёжнее проверять как not line.strip().",
        "en": "Only ASCII bytes count as whitespace here: space, \\t, \\n, \\r, \\x0b and \\x0c. Unicode spaces are not included — b'\\xa0'.isspace() is False, while the same method on a str non-breaking space returns True. Watch the empty case too: b''.isspace() is False, so \"blank or whitespace-only line\" is better tested as not line.strip()."
      },
      "syntax": "b.isspace(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.isspace",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — проверки is*",
      "color_group": "seq",
      "aliases": [
        "байты только пробельные",
        "проверка байтов на пробелы"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'   '.isspace())   # → True",
        "print(b' \\t\\n'.isspace())   # → True",
        "print(b'\\x0b\\x0c\\r'.isspace())   # → True",
        "line = b'  \\n'",
        "print('пустая' if line.isspace() else 'данные')   # → пустая",
        "print(b''.isspace())   # → False"
      ],
      "related": [
        "str.isspace",
        "bytes.strip",
        "bytes.split"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.istitle",
      "title": "bytes.istitle",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что последовательность в «заголовочном» регистре.",
        "en": "Check that the bytes are title-cased."
      },
      "body": {
        "ru": "Правило простое: каждая регистровая буква должна быть заглавной, если перед ней не-буква, и строчной, если перед ней буква. Отсюда неожиданности с разделителями: b'Hello2World' — титульный (цифра начинает новое «слово»), а b\"Don't\" — нет, потому что апостроф обрывает слово и istitle() ждёт b\"Don'T\" (именно так и сработает .title()). Проверять этим методом человеческие имена и заголовки — плохая идея.",
        "en": "The rule is: every cased letter must be upper-case when preceded by a non-letter and lower-case when preceded by a letter. That produces surprises around separators: b'Hello2World' is title-cased (the digit starts a new \"word\"), while b\"Don't\" is not, because the apostrophe breaks the word and istitle() expects b\"Don'T\" — which is exactly what .title() produces. Using it to validate human names or headlines is a bad idea."
      },
      "syntax": "b.istitle(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.istitle",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — проверки is*",
      "color_group": "seq",
      "aliases": [
        "байты в заголовочном регистре",
        "каждое слово в байтах с заглавной"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'Hello World'.istitle())   # → True",
        "print(b'hello world'.istitle())   # → False",
        "print(b'HELLO WORLD'.istitle())   # → False",
        "print(b'Hello2World'.istitle())   # → True",
        "print(b''.istitle())   # → False",
        "print(b'hello world'.title())   # → b'Hello World'"
      ],
      "related": [
        "bytes.title",
        "str.istitle",
        "bytes.isupper"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.isupper",
      "title": "bytes.isupper",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что все буквенные байты — в верхнем регистре.",
        "en": "Check that all cased bytes are upper-case."
      },
      "body": {
        "ru": "Метод отвечает не на вопрос «нет ли строчных букв», а «есть ли хотя бы одна регистровая буква и все ли они заглавные» — поэтому b'123' и b'' дают False, и пара isupper()/islower() не покрывает все случаи. Регистр здесь чисто ASCII-шный: не-ASCII байты (например, кириллица в UTF-8) не считаются буквами, так что проверять регистр такого текста нужно после decode().",
        "en": "The method does not ask \"are there no lower-case letters\" but \"is there at least one cased letter and are they all upper-case\" — which is why b'123' and b'' both give False, and the isupper()/islower() pair does not cover every input. Casing is strictly ASCII here: non-ASCII bytes (Cyrillic in UTF-8, say) are not letters at all, so check the case of such text only after decode()."
      },
      "syntax": "b.isupper(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.isupper",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — проверки is*",
      "color_group": "seq",
      "aliases": [
        "проверить, что байты заглавные",
        "все ли байты в верхнем регистре"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'ABC'.isupper())   # → True",
        "print(b'ABC1!'.isupper())   # → True",
        "print(b'Abc'.isupper())   # → False",
        "print(b'123'.isupper())   # → False",
        "print(b''.isupper())   # → False",
        "print(b'abc'.upper())   # → b'ABC'"
      ],
      "related": [
        "bytes.islower",
        "bytes.upper",
        "str.isupper"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.join",
      "title": "bytes.join",
      "kind": "function",
      "summary": {
        "ru": "Соединяет последовательность байтовых объектов через разделитель.",
        "en": "Join a sequence of bytes with this separator."
      },
      "body": {
        "ru": "Элементы должны быть bytes-подобными: передать список чисел или строк str нельзя, будет TypeError. Отсюда частая ловушка — итерация по bytes даёт целые числа, поэтому b'-'.join(b'abc') падает, а не собирает b'a-b-c'. Тип результата определяет разделитель: у bytes получится bytes, даже если внутри были bytearray; накапливать длинную последовательность через join дешевле, чем повторным +=.",
        "en": "Every item must be bytes-like — a list of ints or of str raises TypeError. That is the usual trap: iterating over bytes yields ints, so b'-'.join(b'abc') fails instead of producing b'a-b-c'. The separator decides the result type (bytes.join returns bytes even for bytearray items), and joining once is far cheaper than repeated += in a loop."
      },
      "syntax": "b.join(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.join",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "склеить байты через разделитель",
        "объединить список байтов в одну байтовую строку"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'-'.join([b'a', b'b', b'c']))   # → b'a-b-c'",
        "print(b''.join([b'ab', b'cd']))   # → b'abcd'",
        "print(b'+'.join(b'a-b-c'.split(b'-')))   # → b'a+b+c'",
        "print(b'-'.join([bytearray(b'x'), b'y']))   # → b'x-y'",
        "print(b', '.join([]))   # → b''",
        "print(b'-'.join([b'a', 'b']))   # → TypeError"
      ],
      "related": [
        "bytes.split",
        "str.join",
        "bytearray.join"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytes.ljust",
      "title": "bytes.ljust",
      "kind": "function",
      "summary": {
        "ru": "Выравнивает по левому краю в поле заданной ширины.",
        "en": "Left-justify in a field of the given width."
      },
      "body": {
        "ru": "Если ширина не больше текущей длины, метод молча возвращает исходные байты — обрезки нет, и колонка в самодельной таблице разъедется на длинном значении. Заполнитель должен быть ровно одним байтом, иначе TypeError. Исходный объект не меняется: возвращается новая копия.",
        "en": "When width is not greater than the current length, the original bytes come back untouched — there is no truncation, so one long value will break the alignment of a hand-made table. The fill argument must be exactly one byte, otherwise you get a TypeError. Nothing is modified in place: a new copy is returned."
      },
      "syntax": "b.ljust(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.ljust",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — выравнивание",
      "color_group": "seq",
      "aliases": [
        "выровнять байты по левому краю",
        "дополнить байты справа до нужной ширины"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'hi'.ljust(5, b'.'))   # → b'hi...'",
        "print(b'hi'.ljust(5))   # → b'hi   '",
        "print(b'7'.ljust(4, b'0'))   # → b'7000'",
        "print(b'hello'.ljust(3))   # → b'hello'",
        "print(b'hi'.rjust(5, b'.'))   # → b'...hi'",
        "print(b'hi'.ljust(5, b'..'))   # → TypeError"
      ],
      "related": [
        "bytes.rjust",
        "bytes.center",
        "bytes.zfill",
        "str.ljust"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.lower",
      "title": "bytes.lower",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию, где ASCII-буквы приведены к нижнему регистру.",
        "en": "Return a copy with ASCII letters lower-cased."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "b.lower(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.lower",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — регистр",
      "color_group": "seq",
      "aliases": [
        "байты в нижний регистр",
        "сделать байты строчными"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'ABC'.lower())   # → b'abc'",
        "print(b'Hello, World!'.lower())   # → b'hello, world!'",
        "print(b'ABC123'.lower())   # → b'abc123'",
        "print('ÄÖ'.encode('utf-8').lower() == 'ÄÖ'.encode('utf-8'))   # → True",
        "print(b'Yes'.lower() in (b'yes', b'y'))   # → True",
        "print(b'abc'.upper())   # → b'ABC'"
      ],
      "related": [
        "bytes.upper",
        "bytes.islower",
        "str.lower"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.lstrip",
      "title": "bytes.lstrip",
      "kind": "function",
      "summary": {
        "ru": "Убирает заданные байты слева.",
        "en": "Strip given bytes from the left."
      },
      "body": {
        "ru": "Аргумент — набор байтов, а не префикс: обрезка идёт слева побайтно, пока очередной байт входит в набор, поэтому b'abcabd'.lstrip(b'ab') оставляет b'cabd'. При чистке ведущих нулей это выстреливает: b'0000'.lstrip(b'0') даст пустое b'', а не b'0'. Нужно снять ровно известное начало — removeprefix().",
        "en": "The argument is a set of byte values, not a prefix: stripping proceeds byte by byte from the left while each byte belongs to that set, so b'abcabd'.lstrip(b'ab') leaves b'cabd'. Trimming leading zeros bites here — b'0000'.lstrip(b'0') yields an empty b'', not b'0'. To drop one known leading chunk, use removeprefix()."
      },
      "syntax": "b.lstrip(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.lstrip",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — обрезка",
      "color_group": "seq",
      "aliases": [
        "убрать байты слева",
        "обрезать байтовую строку слева"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'  hi'.lstrip())   # → b'hi'",
        "print(b'xxxdata'.lstrip(b'x'))  # → b'data'",
        "print(b'0042'.lstrip(b'0'))  # → b'42'",
        "print(b'abcabd'.lstrip(b'ab'))  # → b'cabd'",
        "print(b'  hi  '.lstrip())  # → b'hi  '"
      ],
      "related": [
        "bytes.rstrip",
        "bytes.strip",
        "bytes.removeprefix",
        "str.lstrip"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.maketrans",
      "title": "bytes.maketrans",
      "kind": "function",
      "summary": {
        "ru": "Статический метод: строит таблицу перевода из двух байтовых объектов (для translate).",
        "en": "A static method building a translation table for translate()."
      },
      "body": {
        "ru": "Обе последовательности должны быть одной длины, иначе ValueError; на выходе — таблица ровно из 256 байт, где неупомянутые байты отображаются сами в себя. Перевод идёт побайтово, а не посимвольно, поэтому на UTF-8-тексте такая таблица разрушит многобайтовые символы — для строк есть str.maketrans со словарём кодпоинтов. Удалять байты через таблицу нельзя: для этого у translate есть отдельный параметр delete.",
        "en": "Both arguments must have the same length or you get a ValueError; the result is a table of exactly 256 bytes in which every byte you did not mention maps to itself. Translation happens byte by byte, not character by character, so applying such a table to UTF-8 text will wreck multi-byte characters — for text use str.maketrans, which builds a mapping of code points. The table cannot delete anything: translate takes a separate delete argument for that."
      },
      "syntax": "b.maketrans(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.maketrans",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — замена и перевод",
      "color_group": "seq",
      "aliases": [
        "таблица замены байтов",
        "построить таблицу перевода для байтов"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(len(bytes.maketrans(b'ab', b'AB')))   # → 256",
        "print(b'hello'.translate(bytes.maketrans(b'el', b'ip')))  # → b'hippo'",
        "print(b'AATTGC'.translate(bytes.maketrans(b'ATGC', b'TACG')))  # → b'TTAACG'",
        "print(bytes.maketrans(b'a', b'A')[97])  # → 65",
        "print(bytes.maketrans(b'ab', b'A'))  # → ValueError"
      ],
      "related": [
        "bytes.translate",
        "str.maketrans",
        "bytes.replace"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "bytes.partition",
      "title": "bytes.partition",
      "kind": "function",
      "summary": {
        "ru": "Делит по первому вхождению разделителя на тройку (до, разделитель, после).",
        "en": "Split at the first separator into (head, sep, tail)."
      },
      "body": {
        "ru": "Всегда возвращается тройка, поэтому распаковка в три имени безопасна — проверять «нашёлся ли разделитель» надо по среднему элементу, а не по длине результата. В отличие от split, разделитель сохраняется и делит ровно один раз, так что это правильный инструмент для разбора вида ключ=значение, где в значении тоже могут быть знаки равенства. Пустой разделитель запрещён — ValueError.",
        "en": "The result is always a 3-tuple, so unpacking into three names is safe; to tell whether the separator was found, test the middle element, not the length. Unlike split it keeps the separator and cuts exactly once, which makes it the right tool for key=value parsing where the value may itself contain the separator. An empty separator raises ValueError."
      },
      "syntax": "b.partition(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.partition",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "разделить байты по первому вхождению",
        "разбить байты на три части"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'a=b=c'.partition(b'='))   # → (b'a', b'=', b'b=c')",
        "print(b'name=Ann'.partition(b'=')[2])  # → b'Ann'",
        "print(b'abc'.partition(b'='))  # → (b'abc', b'', b'')",
        "print(b'=abc'.partition(b'='))  # → (b'', b'=', b'abc')",
        "print(b'a=b=c'.rpartition(b'='))  # → (b'a=b', b'=', b'c')"
      ],
      "related": [
        "bytes.rpartition",
        "bytes.split",
        "str.partition"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.removeprefix",
      "title": "bytes.removeprefix",
      "kind": "function",
      "summary": {
        "ru": "Убирает заданный префикс, если он есть (Python 3.9+).",
        "en": "Remove a given prefix if present (3.9+)."
      },
      "body": {
        "ru": "В отличие от lstrip() сравнивается вся последовательность целиком и снимается ровно одно вхождение: b'foofoobar' станет b'foobar', а не b'bar'. Если префикса нет, исключения не будет — вернётся исходное значение, так что предварительный endswith()/startswith() не нужен. Аргумент обязан быть bytes-подобным: str на входе даёт TypeError.",
        "en": "Unlike lstrip() this matches the whole sequence and removes exactly one occurrence: b'foofoobar' becomes b'foobar', not b'bar'. A missing prefix is not an error — the value comes back unchanged, so guarding with startswith() first is pointless. The argument must be bytes-like; passing a str raises TypeError."
      },
      "syntax": "b.removeprefix(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.removeprefix",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — обрезка",
      "color_group": "seq",
      "aliases": [
        "убрать префикс у байтов",
        "отрезать начало байтовой строки"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'foobar'.removeprefix(b'foo'))   # → b'bar'",
        "print(b'foobar'.removeprefix(b'baz'))  # → b'foobar'",
        "print(b'GET /index'.removeprefix(b'GET '))  # → b'/index'",
        "print(b'foofoobar'.removeprefix(b'foo'))  # → b'foobar'",
        "print(b'xxfoo'.lstrip(b'xf'))  # → b'oo'"
      ],
      "related": [
        "bytes.removesuffix",
        "bytes.lstrip",
        "bytes.startswith",
        "str.removeprefix"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.removesuffix",
      "title": "bytes.removesuffix",
      "kind": "function",
      "summary": {
        "ru": "Убирает заданный суффикс, если он есть (Python 3.9+).",
        "en": "Remove a given suffix if present (3.9+)."
      },
      "body": {
        "ru": "Хвост сравнивается целиком и снимается ровно один раз, а при несовпадении значение возвращается как было, без исключения. Для отрезания расширения или перевода строки это безопаснее rstrip(): тот трактует аргумент как набор байтов и на b'report.tttt' или на несколько подряд идущих b'\\n' отгрызёт лишнее.",
        "en": "The suffix is compared as a whole and removed exactly once; if it does not match, the value is returned unchanged rather than raising. For cutting an extension or a line ending this is safer than rstrip(), which treats its argument as a set of bytes and will happily eat more than one trailing byte."
      },
      "syntax": "b.removesuffix(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.removesuffix",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — обрезка",
      "color_group": "seq",
      "aliases": [
        "убрать суффикс у байтов",
        "отрезать заданный конец байтовой строки"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'foobar'.removesuffix(b'bar'))   # → b'foo'",
        "print(b'report.txt'.removesuffix(b'.txt'))  # → b'report'",
        "print(b'report.txt'.removesuffix(b'.csv'))  # → b'report.txt'",
        "print(b'line\\n'.removesuffix(b'\\n'))  # → b'line'",
        "print(b'banana'.removesuffix(b'na'))  # → b'bana'",
        "print(b'banana'.rstrip(b'na'))  # → b'b'"
      ],
      "related": [
        "bytes.removeprefix",
        "bytes.rstrip",
        "bytes.endswith",
        "str.removesuffix"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.replace",
      "title": "bytes.replace",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию с заменой всех (или первых count) вхождений.",
        "en": "Return a copy with occurrences of a subsequence replaced."
      },
      "body": {
        "ru": "Оба аргумента обязаны быть байтовыми: b'aaa'.replace('a', 'b') не сработает молча, а упадёт с TypeError — типичный сюрприз после чтения файла в бинарном режиме. Поиск идёт слева направо, вхождения не пересекаются, а исходный объект не меняется: даже у bytearray этот метод возвращает новый объект, а не правит на месте.",
        "en": "Both arguments must be bytes-like; handing in a str raises TypeError rather than quietly working, which is the usual stumble right after opening a file in binary mode. Matching runs left to right with no overlaps, and nothing is modified in place — even on a bytearray the method hands back a fresh object."
      },
      "syntax": "b.replace(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.replace",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — замена и перевод",
      "color_group": "seq",
      "aliases": [
        "заменить подстроку в байтах",
        "замена вхождений в байтовой строке"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'aaa'.replace(b'a', b'b'))   # → b'bbb'",
        "print(b'aaa'.replace(b'a', b'b', 2))   # → b'bba'",
        "print(b'2026-07-27'.replace(b'-', b''))   # → b'20260727'",
        "print(b'abc'.replace(b'z', b'!'))   # → b'abc'",
        "print(b'ab'.replace(b'', b'-'))   # → b'-a-b-'",
        "print(b'abc'.replace('a', 'x'))   # → TypeError"
      ],
      "related": [
        "bytes.translate",
        "str.replace",
        "bytes.removeprefix"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.rfind",
      "title": "bytes.rfind",
      "kind": "function",
      "summary": {
        "ru": "Возвращает индекс последнего вхождения подпоследовательности или -1.",
        "en": "Return the index of the last occurrence, or -1."
      },
      "body": {
        "ru": "Поиск идёт справа налево, но возвращается обычный индекс от начала объекта, так что результат сразу годится для среза. Аргументы start и end по-прежнему задают окно слева направо, и найдётся самое правое вхождение, целиком помещающееся в это окно. Для типовой задачи «отрезать по последнему разделителю» обычно чище rpartition или rsplit, чем ручная арифметика с индексом.",
        "en": "The scan runs right to left, but the returned index is still counted from the start of the object, so it plugs straight into a slice. The start and end arguments still define a left-to-right window, and you get the rightmost match that fits entirely inside it. For the usual job of cutting at the last separator, rpartition or rsplit is cleaner than doing index arithmetic by hand."
      },
      "syntax": "b.rfind(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.rfind",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — поиск",
      "color_group": "seq",
      "aliases": [
        "последнее вхождение байтов",
        "искать байты с конца"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'banana'.rfind(b'na'))   # → 4",
        "print(b'banana'.find(b'na'))   # → 2",
        "print(b'banana'.rfind(b'na', 0, 4))   # → 2",
        "name = b'archive.tar.gz'",
        "print(name[name.rfind(b'.') + 1:])   # → b'gz'",
        "print(b'banana'.rfind(b'z'))   # → -1"
      ],
      "related": [
        "bytes.find",
        "bytes.rindex",
        "str.rfind"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.rindex",
      "title": "bytes.rindex",
      "kind": "function",
      "summary": {
        "ru": "Как rfind, но бросает ValueError, если не найдено.",
        "en": "Like rfind, but raises ValueError if not found."
      },
      "body": {
        "ru": "Берите rindex, когда отсутствие подстроки означает поломку данных: rfind в этом случае вернёт -1, а -1 молча сработает как валидный отрицательный индекс в срезе и даст правдоподобный, но неверный результат. Возвращается позиция от начала последовательности — справа налево идёт только сам поиск. Искать можно bytes-подобный объект или целое 0-255; str на входе даст TypeError.",
        "en": "Reach for rindex when a missing separator means the data is broken: rfind returns -1 instead, and -1 quietly works as a valid negative index in a slice, producing a plausible but wrong result. The number it returns is counted from the left — only the search direction is right-to-left. The argument must be a bytes-like object or an int in range 0-255; a str raises TypeError."
      },
      "syntax": "b.rindex(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.rindex",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — поиск",
      "color_group": "seq",
      "aliases": [
        "последнее вхождение байтов с ошибкой если нет",
        "поиск байтов справа с исключением"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'banana'.rindex(b'na'))   # → 4",
        "print(b'banana'.rindex(b'a'))   # → 5",
        "path = b'/usr/local/bin'",
        "print(path[path.rindex(b'/') + 1:])   # → b'bin'",
        "print(b'banana'.rindex(b'z'))   # → ValueError",
        "print(b'banana'.rfind(b'z'))   # → -1"
      ],
      "related": [
        "bytes.rfind",
        "bytes.index",
        "str.rindex"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "bytes.rjust",
      "title": "bytes.rjust",
      "kind": "function",
      "summary": {
        "ru": "Выравнивает по правому краю в поле заданной ширины.",
        "en": "Right-justify in a field of the given width."
      },
      "body": {
        "ru": "Для чисел со знаком результат отличается от zfill: rjust с заполнителем-нулём поставит нули перед минусом, а zfill вставит их после знака. Как и ljust, метод ничего не обрезает — если байтов уже не меньше ширины, вернётся исходное значение, а заполнитель обязан быть ровно одним байтом.",
        "en": "For signed numbers the result differs from zfill: rjust with a zero fill puts the zeros before the minus, while zfill inserts them after the sign. Like ljust it never truncates — if the value is already at least as long as width it is returned unchanged — and the fill must be exactly one byte."
      },
      "syntax": "b.rjust(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.rjust",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — выравнивание",
      "color_group": "seq",
      "aliases": [
        "выровнять байты по правому краю",
        "дополнить байты слева до нужной ширины"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'hi'.rjust(5, b'.'))   # → b'...hi'",
        "print(b'7'.rjust(3))   # → b'  7'",
        "print(b'42'.rjust(5, b'0'))   # → b'00042'",
        "print(b'hello'.rjust(3, b'.'))   # → b'hello'",
        "print(b'hi'.ljust(5, b'.'))   # → b'hi...'",
        "print(b'hi'.rjust(5, b'..'))   # → TypeError"
      ],
      "related": [
        "bytes.ljust",
        "bytes.zfill",
        "bytes.center",
        "str.rjust"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.rpartition",
      "title": "bytes.rpartition",
      "kind": "function",
      "summary": {
        "ru": "Делит по последнему вхождению разделителя на тройку.",
        "en": "Split at the last separator into (head, sep, tail)."
      },
      "body": {
        "ru": "Когда разделитель не найден, пустыми оказываются первые два элемента, а исходные данные попадают в третий — зеркально к partition, где целое остаётся в первом. Из-за этого распаковка вида name, _, ext = ... на строке без точки даст пустое имя и всё содержимое в ext; если это не то, что нужно, проверяйте средний элемент перед использованием.",
        "en": "When the separator is missing, the two empty pieces come first and the original data lands in the third element — mirroring partition, where the whole value stays in the first. So name, _, ext = ... on input without a dot leaves name empty and puts everything into ext; check the middle element before trusting the split."
      },
      "syntax": "b.rpartition(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.rpartition",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "разделить байты по последнему вхождению",
        "разбить байты на тройку с конца"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'a=b=c'.rpartition(b'='))   # → (b'a=b', b'=', b'c')",
        "print(b'a=b=c'.partition(b'='))   # → (b'a', b'=', b'b=c')",
        "name, _, ext = b'archive.tar.gz'.rpartition(b'.')",
        "print(ext)   # → b'gz'",
        "print(b'abc'.rpartition(b'='))   # → (b'', b'', b'abc')",
        "print(b'abc'.rpartition(b''))   # → ValueError"
      ],
      "related": [
        "bytes.partition",
        "bytes.rsplit",
        "str.rpartition"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.rsplit",
      "title": "bytes.rsplit",
      "kind": "function",
      "summary": {
        "ru": "Как split, но разбивает справа и с ограничением числа разбиений.",
        "en": "Like split, but splits from the right with a max count."
      },
      "body": {
        "ru": "Без maxsplit результат в точности совпадает с split — направление начинает играть роль только когда число разбиений ограничено. Поэтому rsplit берут ровно тогда, когда интересен хвост: расширение файла, последнее поле строки. С разделителем None пробельные последовательности схлопываются и крайние пробелы игнорируются, а с явным разделителем каждое вхождение режет отдельно.",
        "en": "Without maxsplit the result is identical to split — the direction only matters once you cap the number of splits. That is exactly when to reach for it: when you care about the tail, such as a file extension or the last field of a line. With None as separator runs of whitespace collapse and leading/trailing whitespace is ignored; with an explicit separator every occurrence cuts on its own."
      },
      "syntax": "b.rsplit(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.rsplit",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "разбить байты справа",
        "разделить байты с конца с ограничением"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'a b c'.rsplit(None, 1))   # → [b'a b', b'c']",
        "print(b'a,b,c'.rsplit(b','))   # → [b'a', b'b', b'c']",
        "print(b'a,b,c'.rsplit(b',', 1))   # → [b'a,b', b'c']",
        "print(b'archive.tar.gz'.rsplit(b'.', 1))   # → [b'archive.tar', b'gz']",
        "print(b''.rsplit(b','))   # → [b'']",
        "print(b'a,b'.rsplit(','))   # → TypeError"
      ],
      "related": [
        "bytes.split",
        "bytes.rpartition",
        "str.rsplit"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.rstrip",
      "title": "bytes.rstrip",
      "kind": "function",
      "summary": {
        "ru": "Убирает заданные байты справа.",
        "en": "Strip given bytes from the right."
      },
      "body": {
        "ru": "Типовое применение — снять b'\\r\\n' у строки, прочитанной из файла или сокета. Но голый rstrip() заодно съест значимые хвостовые пробелы и табы, а rstrip(b'\\r\\n') снимет все повторы этих байтов, а не один перевод строки. Когда важно убрать ровно один известный хвост, берите removesuffix().",
        "en": "The usual use is dropping b'\\r\\n' from a line read out of a file or socket. But bare rstrip() also eats meaningful trailing spaces and tabs, and rstrip(b'\\r\\n') removes every repetition of those bytes, not a single line ending. When exactly one known tail must go, use removesuffix()."
      },
      "syntax": "b.rstrip(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.rstrip",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — обрезка",
      "color_group": "seq",
      "aliases": [
        "убрать байты справа",
        "убрать перевод строки в конце байтов"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'hi  '.rstrip())   # → b'hi'",
        "print(b'  hi  '.rstrip())   # → b'  hi'",
        "print(b'42\\r\\n'.rstrip())   # → b'42'",
        "print(b'xxhixx'.rstrip(b'x'))   # → b'xxhi'",
        "print(b'text.txt'.rstrip(b'.txt'))   # → b'te'"
      ],
      "related": [
        "bytes.lstrip",
        "bytes.strip",
        "bytes.removesuffix",
        "str.rstrip"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytes.split",
      "title": "bytes.split",
      "kind": "function",
      "summary": {
        "ru": "Разбивает байты по разделителю (по умолчанию — по пробельным).",
        "en": "Split the bytes on a separator (whitespace by default)."
      },
      "body": {
        "ru": "Разделитель обязан быть bytes-подобным: split(',') со строкой или split(44) с кодом символа не сработает. Вызов без аргументов и вызов с b' ' — разное поведение: без аргументов подряд идущие пробелы схлопываются и крайние отбрасываются, а с явным разделителем каждое вхождение даёт отдельный кусок, в том числе пустой. На пустых данных b''.split(b',') вернёт список из одного пустого элемента, а b''.split() — пустой список.",
        "en": "The separator must be bytes-like: split(',') with a str, or split(44) with a byte value, will not work. Calling it with no argument differs from passing b' ': the no-argument form collapses runs of whitespace and drops leading and trailing ones, while an explicit separator cuts at every occurrence and can yield empty pieces. On empty input b''.split(b',') returns a list with one empty item, whereas b''.split() returns an empty list."
      },
      "syntax": "b.split(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.split",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "разбить байты по разделителю",
        "разделить байтовую строку на части"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'a b c'.split())   # → [b'a', b'b', b'c']",
        "print(b'a,b,c'.split(b','))   # → [b'a', b'b', b'c']",
        "print(b'a,b,c'.split(b',', 1))   # → [b'a', b'b,c']",
        "print(b'key=value'.split(b'=', 1))   # → [b'key', b'value']",
        "print(b'a,,b'.split(b','))   # → [b'a', b'', b'b']",
        "print(b'  a  b  '.split(b' '))   # → [b'', b'', b'a', b'', b'b', b'', b'']"
      ],
      "related": [
        "bytes.rsplit",
        "bytes.join",
        "bytes.splitlines",
        "str.split"
      ],
      "related_errors": [
        "TypeError",
        "ValueError"
      ]
    },
    {
      "id": "bytes.splitlines",
      "title": "bytes.splitlines",
      "kind": "function",
      "summary": {
        "ru": "Разбивает байты по границам строк.",
        "en": "Split the bytes at line boundaries."
      },
      "body": {
        "ru": "Главное отличие от b.split(b'\\n') — разделителем считается любая ASCII-граница строки, включая пару \\r\\n целиком, и завершающий перевод строки не порождает лишний пустой элемент в конце. У пустых байтов результат — пустой список, а не список из одного пустого элемента, так что счётчик строк по файлу с ним не соврёт. Границы тут только ASCII-овские, в отличие от str.splitlines, который делит текст ещё и по редким юникодным разделителям.",
        "en": "Unlike b.split(b'\\n'), this treats any ASCII line boundary as a separator, handles \\r\\n as one break, and a trailing newline does not add a spurious empty last item. Empty bytes give an empty list rather than a one-element list, so counting lines this way stays honest. The boundary set is ASCII-only here, narrower than str.splitlines, which also breaks on rarer Unicode separators."
      },
      "syntax": "b.splitlines(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.splitlines",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — разбиение и соединение",
      "color_group": "seq",
      "aliases": [
        "разбить байты на строки",
        "разделить байты по переводу строки"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'a\\nb'.splitlines())   # → [b'a', b'b']",
        "print(b'a\\nb'.splitlines(keepends=True))   # → [b'a\\n', b'b']",
        "print(b'a\\r\\nb'.splitlines())   # → [b'a', b'b']",
        "print(b'a\\nb\\n'.splitlines())   # → [b'a', b'b']",
        "print(b'a\\nb\\n'.split(b'\\n'))   # → [b'a', b'b', b'']",
        "print(b''.splitlines())   # → []"
      ],
      "related": [
        "bytes.split",
        "str.splitlines",
        "bytes.join"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.startswith",
      "title": "bytes.startswith",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, начинаются ли байты с заданного префикса.",
        "en": "Check whether the bytes start with a prefix."
      },
      "body": {
        "ru": "Префикс обязан быть bytes-подобным объектом или кортежем таких: b'GET /'.startswith('GET') с обычной строкой падает с TypeError — классическая ошибка при разборе сетевых данных. Кортеж означает «любой из перечисленных», а необязательные start и end сужают зону проверки, не создавая срез-копию, так что на длинных данных это дешевле, чем b[2:5] == prefix. Убрать найденный префикс метод не умеет — для этого есть removeprefix (Python 3.9+).",
        "en": "The prefix must be a bytes-like object, or a tuple of them: calling b'GET /'.startswith('GET') with a text string raises TypeError, a classic slip when parsing network data. A tuple means 'any of these', and the optional start and end narrow the region without building a sliced copy, which beats b[2:5] == prefix on large buffers. It only tests; to strip the prefix use removeprefix (Python 3.9+)."
      },
      "syntax": "b.startswith(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.startswith",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — поиск",
      "color_group": "seq",
      "aliases": [
        "байтовая строка начинается с",
        "проверить префикс байтов"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'hello'.startswith(b'he'))   # → True",
        "print(b'hello'.startswith((b'hi', b'he')))   # → True",
        "print(b'hello'.startswith(b'llo', 2))   # → True",
        "print(b'GET /index.html'.startswith(b'GET '))   # → True",
        "print(b'hello'.startswith(b''))   # → True",
        "print(b'hello'.startswith('he'))   # → TypeError"
      ],
      "related": [
        "bytes.endswith",
        "bytes.removeprefix",
        "bytes.find",
        "str.startswith"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytes.strip",
      "title": "bytes.strip",
      "kind": "function",
      "summary": {
        "ru": "Убирает заданные байты (по умолчанию пробельные) с обоих концов.",
        "en": "Strip given bytes (whitespace by default) from both ends."
      },
      "body": {
        "ru": "Аргумент — набор байтов, а не подстрока: с каждого конца отбрасываются байты, пока они входят в набор, поэтому b'banana'.strip(b'ba') превращается в b'nan'. Снять фиксированное начало или конец — это removeprefix()/removesuffix(). Метод ничего не меняет на месте: и bytes, и bytearray возвращают новый объект.",
        "en": "The argument is a set of byte values, not a substring: bytes are dropped from each end as long as they belong to that set, which is why b'banana'.strip(b'ba') gives b'nan'. To remove a fixed head or tail, reach for removeprefix()/removesuffix(). Nothing is modified in place — both bytes and bytearray return a new object."
      },
      "syntax": "b.strip(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.strip",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — обрезка",
      "color_group": "seq",
      "aliases": [
        "убрать пробелы с обоих концов байтовой строки",
        "обрезать байты с двух сторон"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'  hi  '.strip())   # → b'hi'",
        "print(b'xxhixx'.strip(b'x'))   # → b'hi'",
        "print(b'\\t hi \\n'.strip())   # → b'hi'",
        "print(b'banana'.strip(b'ba'))   # → b'nan'",
        "print(b'  hi  '.rstrip())   # → b'  hi'",
        "print(b'   '.strip())   # → b''"
      ],
      "related": [
        "bytes.lstrip",
        "bytes.rstrip",
        "str.strip"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "bytes.swapcase",
      "title": "bytes.swapcase",
      "kind": "function",
      "summary": {
        "ru": "Меняет регистр ASCII-букв на противоположный.",
        "en": "Swap the case of ASCII letters."
      },
      "body": {
        "ru": "Затрагиваются только ASCII-буквы; любые прочие байты, в том числе байты UTF-8-кодировки нелатинских букв, проходят насквозь без изменений. В отличие от str.swapcase, для байтов двойной вызов всегда возвращает исходное значение: у строк это не гарантировано, потому что у некоторых символов верхний регистр длиннее нижнего.",
        "en": "Only ASCII letters flip; every other byte, including the bytes of UTF-8-encoded non-Latin letters, passes through untouched. Unlike str.swapcase, applying it twice to bytes always restores the original — for strings that is not guaranteed, since some characters expand when upper-cased."
      },
      "syntax": "b.swapcase(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.swapcase",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — регистр",
      "color_group": "seq",
      "aliases": [
        "поменять регистр байтов на противоположный",
        "инвертировать регистр байтов"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'AbC'.swapcase())   # → b'aBc'",
        "print(b'Hello World'.swapcase())   # → b'hELLO wORLD'",
        "print(b'Py3.12!'.swapcase())   # → b'pY3.12!'",
        "print(b'AbC'.swapcase().swapcase())   # → b'AbC'",
        "print('привет'.encode().swapcase() == 'привет'.encode())   # → True"
      ],
      "related": [
        "bytes.upper",
        "bytes.lower",
        "str.swapcase"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.title",
      "title": "bytes.title",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию в «заголовочном» регистре (каждое слово с большой буквы).",
        "en": "Return a title-cased copy (each word capitalized)."
      },
      "body": {
        "ru": "Словом здесь считается максимальная цепочка ASCII-букв, а любой другой байт — разделитель, поэтому апостроф или цифра разрывают слово и следующая за ними буква снова становится заглавной. Для настоящих заголовков метод не годится: он калечит сокращения и слова с апострофом — там нужен свой проход по словам или регулярное выражение.",
        "en": "A word here is just a maximal run of ASCII letters, and any other byte counts as a separator, so an apostrophe or a digit ends the word and the next letter gets capitalized again. That makes it unfit for real headings — contractions and abbreviations come out mangled, so use a regex or your own word-by-word pass instead."
      },
      "syntax": "b.title(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.title",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — регистр",
      "color_group": "seq",
      "aliases": [
        "каждое слово байтов с заглавной",
        "заголовочный регистр байтов"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'hello world'.title())   # → b'Hello World'",
        "print(b'HELLO WORLD'.title())   # → b'Hello World'",
        "print(b\"they're ok\".title())   # → b\"They'Re Ok\"",
        "print(b'x2y'.title())   # → b'X2Y'",
        "print(b'hello world'.capitalize())   # → b'Hello world'"
      ],
      "related": [
        "bytes.istitle",
        "bytes.capitalize",
        "str.title"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.translate",
      "title": "bytes.translate",
      "kind": "function",
      "summary": {
        "ru": "Заменяет байты по таблице перевода (256 байт).",
        "en": "Map bytes through a 256-byte translation table."
      },
      "body": {
        "ru": "Таблица должна быть длиной ровно 256 байт (её удобно собрать через bytes.maketrans) либо None, если нужен только второй аргумент — удаление байтов. Перевод идёт побайтово, поэтому на UTF-8 тексте с кириллицей translate калечит многобайтовые символы: там сначала decode, потом str.translate. Зато один проход переписывает сразу много разных байтов там, где иначе понадобилась бы цепочка replace.",
        "en": "The table has to be exactly 256 bytes long (bytes.maketrans builds it for you), or None when all you want is the delete argument. Translation happens byte by byte, so running it over UTF-8 text with non-ASCII characters mangles multi-byte sequences — decode first and use str.translate there. The payoff is that a single pass rewrites many byte values at once, where you would otherwise chain several replace calls."
      },
      "syntax": "b.translate(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.translate",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — замена и перевод",
      "color_group": "seq",
      "aliases": [
        "заменить байты по таблице перевода",
        "удалить или подменить отдельные байты"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'abc'.translate(bytes.maketrans(b'a', b'X')))   # → b'Xbc'",
        "print(b'hello'.translate(bytes.maketrans(b'el', b'ip')))   # → b'hippo'",
        "print(b'a-b-c'.translate(None, b'-'))   # → b'abc'",
        "print(b'a1b2'.translate(bytes.maketrans(b'ab', b'AB'), b'12'))   # → b'AB'",
        "print(b'abc'.translate(None))   # → b'abc'",
        "print(b'abc'.translate(b'xy'))   # → ValueError"
      ],
      "related": [
        "bytes.maketrans",
        "bytes.replace",
        "str.translate"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "bytes.upper",
      "title": "bytes.upper",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию, где ASCII-буквы приведены к верхнему регистру.",
        "en": "Return a copy with ASCII letters upper-cased."
      },
      "body": {
        "ru": "Меняются только ASCII-буквы A-Z/a-z, потому что объект bytes не знает своей кодировки: у 'привет'.encode('utf-8') вызов upper() не тронет ни одного байта, и результат будет выглядеть неизменным. Если нужен настоящий верхний регистр для не-ASCII текста, сначала decode() в str, потом str.upper(), потом обратно encode().",
        "en": "Only ASCII A-Z/a-z change, because a bytes object carries no information about its encoding: calling upper() on UTF-8 encoded Cyrillic or accented text leaves every byte exactly as it was. For real Unicode case conversion, decode to str first, call str.upper() there, and encode the result back."
      },
      "syntax": "b.upper(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.upper",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — регистр",
      "color_group": "seq",
      "aliases": [
        "перевести байты в верхний регистр",
        "сделать байты заглавными"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'abc'.upper())   # → b'ABC'",
        "print(b'py 3.12 rocks'.upper())   # → b'PY 3.12 ROCKS'",
        "print(b'MiXeD'.lower())   # → b'mixed'",
        "print(b'Yes'.upper() == b'YES')   # → True",
        "print('привет'.encode().upper() == 'привет'.encode())   # → True"
      ],
      "related": [
        "bytes.lower",
        "bytes.isupper",
        "str.upper"
      ],
      "related_errors": []
    },
    {
      "id": "bytes.zfill",
      "title": "bytes.zfill",
      "kind": "function",
      "summary": {
        "ru": "Дополняет слева нулями до заданной длины (учитывает ведущий знак).",
        "en": "Pad on the left with zeros to a given length."
      },
      "body": {
        "ru": "Ведущий плюс или минус остаётся на первой позиции, а нули вставляются после него — именно этим zfill отличается от выравнивания нулями через rjust. Знаком считается только один такой байт в самом начале; всё прочее, включая пробел или префикс 0x, воспринимается как обычное содержимое. Короче ширины строка не станет, но и длиннее не обрежется.",
        "en": "A leading plus or minus stays in first position and the zeros are inserted after it — that is exactly what separates zfill from padding with zeros via rjust. Only one such sign byte at the very start is recognised; anything else, including a space or a 0x prefix, counts as ordinary content. A value already at least width bytes long is returned as is, never truncated."
      },
      "syntax": "b.zfill(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes.zfill",
      "version": "",
      "section": "Байтовые последовательности",
      "subcat": "bytes — выравнивание",
      "color_group": "seq",
      "aliases": [
        "дополнить байты нулями слева",
        "ведущие нули в байтовой строке"
      ],
      "keywords": [],
      "tags": [
        "bytes"
      ],
      "examples": [
        "print(b'42'.zfill(5))   # → b'00042'",
        "print(b'-42'.zfill(5))   # → b'-0042'",
        "print(b'+7'.zfill(6))   # → b'+00007'",
        "print(b'12345'.zfill(3))   # → b'12345'",
        "print(b'7'.zfill(3) + b'.jpg')   # → b'007.jpg'",
        "print(b''.zfill(2))   # → b'00'"
      ],
      "related": [
        "bytes.rjust",
        "bytes.center",
        "str.zfill"
      ],
      "related_errors": []
    },
    {
      "id": "end",
      "title": "end=",
      "kind": "term",
      "summary": {
        "ru": "Параметр функции print(), задающий строку, добавляемую в конец вывода. По умолчанию — символ новой строки '\\n'. Позволяет выводить на одной строке.",
        "en": "Parameter of print() that sets the string appended after the output. Defaults to the newline character '\\n'. Lets you print on a single line."
      },
      "body": {
        "ru": "sep вставляется между аргументами, а end добавляется один раз в самом конце — эти два параметра постоянно путают. При end=\"\" текст остаётся в буфере stdout, пока не встретится перенос строки, поэтому индикаторы прогресса в консоли дополняют вызовом flush=True; сам end принимает любую строку, включая \"\\r\" и несколько символов сразу.",
        "en": "sep goes between the arguments, while end is appended once after all of them — a pair that gets mixed up constantly. With end=\"\" the text sits in the stdout buffer until a newline appears, so console progress indicators add flush=True; end itself accepts any string, including \"\\r\" or several characters at once."
      },
      "syntax": "print(*objects, end='строка')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#print",
      "version": "",
      "section": "Ввод и вывод",
      "subcat": "вывод",
      "color_group": "builtin",
      "aliases": [
        "вывод без переноса строки",
        "печать в одну строку",
        "убрать перевод строки при выводе"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(\"Загрузка\", end=\"...\"); print(\"готово\")  # → Загрузка...готово",
        "for i in range(5): print(i, end=\" \")  # → 0 1 2 3 4",
        "print(\"строка без переноса\", end=\"\")  # курсор остаётся на той же строке",
        "print(\"раз\", end=\"\\t\"); print(\"два\")  # → раз\tдва",
        "print(\"конец\", end=\"!\\n\")  # → конец!"
      ],
      "related": [
        "sep",
        "print",
        "print-file-flush"
      ],
      "related_errors": []
    },
    {
      "id": "input",
      "title": "input()",
      "kind": "function",
      "summary": {
        "ru": "Встроенная функция для чтения строки с клавиатуры. Выводит необязательный приглашающий текст и возвращает введённую строку без символа новой строки.",
        "en": "Built-in function that reads a line from the keyboard. It prints an optional prompt and returns the line entered, without the trailing newline."
      },
      "body": {
        "ru": "input() всегда отдаёт строку: даже \"42\" остаётся строкой, а int() на пустом или нечисловом вводе бросит ValueError. Когда поток ввода кончился (файл или пайп в тестирующей системе), input() бросает EOFError, а не возвращает пустую строку — поэтому цикл чтения ограничивают известным числом строк или ловят EOFError. Приглашающий текст уходит в тот же stdout, что и ответ, так что в задачах с автопроверкой input() вызывают без аргумента.",
        "en": "input() always hands back a string — \"42\" is still text, and int() on empty or non-numeric input raises ValueError. When the stream runs out (a file or pipe in an autograder) it raises EOFError rather than returning an empty string, so bound the read loop by a known count or catch EOFError. The prompt goes to the very same stdout as your answer, which is why graded solutions call input() with no argument."
      },
      "syntax": "input([prompt]) -> str",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#input",
      "version": "",
      "section": "Ввод и вывод",
      "subcat": "ввод",
      "color_group": "builtin",
      "aliases": [
        "ввод с клавиатуры",
        "считать строку от пользователя",
        "запросить данные у пользователя"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "# name = input(\"Введите имя: \")  # пользователь вводит строку",
        "# age = int(input(\"Возраст: \"))  # → преобразование в int",
        "# price = float(input(\"Цена: \"))  # → преобразование в float",
        "# Проверка: x = int(input()) if input().isdigit() else 0",
        "# line1 = input(\"Строка 1: \"); line2 = input(\"Строка 2: \")",
        "# s = input(\"  hello  \").strip()  # → \"hello\" (убирает пробелы)",
        "# Симуляция ввода без клавиатуры:",
        "import io, sys",
        "sys.stdin = io.StringIO('Alice\\n')",
        "print(input())  # → Alice",
        "sys.stdin = sys.__stdin__"
      ],
      "related": [
        "print",
        "int",
        "преобразование-типов",
        "str.split"
      ],
      "related_errors": [
        "EOFError"
      ]
    },
    {
      "id": "print",
      "title": "print()",
      "kind": "function",
      "summary": {
        "ru": "Встроенная функция для вывода данных на стандартный вывод (экран). Принимает любое количество аргументов, разделяет их параметром sep и заканчивает строку параметром end.",
        "en": "Built-in function that writes data to standard output (the screen). It takes any number of arguments, joins them with the sep parameter and ends the line with the end parameter."
      },
      "body": {
        "ru": "print сам прогоняет каждый аргумент через str(), поэтому print(\"x =\", 42) работает без ручного преобразования — в отличие от \"x =\" + 42, где будет TypeError. Возвращает None: строка x = print(a) кладёт в x не текст, а None; если результат нужен как значение, собирайте его f-строкой или \" \".join(...).",
        "en": "print runs str() over every argument for you, so print(\"x =\", 42) needs no manual conversion, unlike \"x =\" + 42 which raises TypeError. It returns None, so x = print(a) stores None rather than the text — build the string with an f-string or \" \".join(...) when you need it as a value."
      },
      "syntax": "print(*objects, sep=' ', end='\\n', file=sys.stdout, flush=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#print",
      "version": "",
      "section": "Ввод и вывод",
      "subcat": "вывод",
      "color_group": "builtin",
      "aliases": [
        "вывод на экран",
        "печать в консоль",
        "напечатать результат"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(\"Hello, World!\")  # → Hello, World!",
        "print(\"a\", \"b\", \"c\", sep=\"-\")  # → a-b-c",
        "print(\"Нет переноса\", end=\"\")  # → Нет переноса",
        "print(\"x =\", 42, \"y =\", 3.14)  # → x = 42 y = 3.14",
        "name = \"Анна\"; age = 25",
        "print(f\"{name} — {age} лет\")  # → Анна — 25 лет",
        "print(f\"{3.14159:.2f}\")  # → 3.14",
        "with open(\"_test.txt\", \"w\") as f:",
        "    print(\"в файл\", file=f)  # записывает строку в файл",
        "    import sys; print(\"!\", flush=True, end=\"\", file=sys.stdout)  # → !",
        "    import sys; print(\"ошибка\", file=sys.stderr)  # → в поток ошибок"
      ],
      "related": [
        "input",
        "sep",
        "end",
        "f-строки"
      ],
      "related_errors": []
    },
    {
      "id": "print-file-flush",
      "title": "print(file=, flush=)",
      "kind": "function",
      "summary": {
        "ru": "Параметр file= перенаправляет вывод в произвольный файловый объект (по умолчанию sys.stdout). flush=True немедленно сбрасывает внутренний буфер потока, не дожидаясь его заполнения.",
        "en": "The file= parameter redirects the output to any file object (sys.stdout by default). flush=True flushes the stream's internal buffer immediately instead of waiting for it to fill up."
      },
      "body": {
        "ru": "Буферизация зависит от того, куда идёт вывод: в терминале stdout сбрасывается построчно, а при перенаправлении в файл или пайп копится блоками по несколько килобайт, поэтому вперемешку со stderr (он всегда построчный) строки в логе могут встать в неожиданном порядке. flush=True это чинит, но заметно замедляет вывод, если звать его в цикле на миллион строк. file= ничего не открывает и не закрывает — объект вы открываете сами, лучше через with.",
        "en": "Buffering depends on where the output goes: stdout flushes line by line on a terminal, but piles up in multi-kilobyte blocks once redirected to a file or a pipe, so lines interleaved with stderr (always line-buffered) can land in a surprising order. flush=True fixes that ordering but costs real time in a million-iteration loop. file= neither opens nor closes anything — you supply an already-open object, ideally from a with block."
      },
      "syntax": "print(*objects, sep=\" \", end=\"\\n\", file=sys.stdout, flush=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#print",
      "version": "",
      "section": "Ввод и вывод",
      "subcat": "вывод",
      "color_group": "builtin",
      "aliases": [
        "печать в файл",
        "перенаправление вывода",
        "сброс буфера вывода"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "import sys",
        "print(\"ошибка!\", file=sys.stderr)  # → выводит в stderr",
        "print(\".\", end=\"\", flush=True)  # → без буфера, без переноса строки",
        "with open(\"_gloss_test.txt\", \"w\") as f:",
        "    print(\"запись\", file=f)  # → пишет строку в файл",
        "    import io; buf = io.StringIO()",
        "    print(\"в буфер\", file=buf); print(buf.getvalue())  # → в буфер",
        "    print(1, 2, 3, sep=\"-\", file=sys.stdout, flush=True)  # → 1-2-3"
      ],
      "related": [
        "print",
        "sys.stdin-sys.stdout-sys.stderr",
        "file-flush",
        "буферизация"
      ],
      "related_errors": []
    },
    {
      "id": "repr-vs-str",
      "title": "repr() vs str()",
      "kind": "term",
      "summary": {
        "ru": "str() возвращает «читаемое» представление объекта для вывода пользователю. repr() возвращает «однозначное» представление, пригодное для воспроизведения объекта в коде; используется в интерактивной консоли и при отладке.",
        "en": "str() returns the 'readable' representation of an object, meant for the user. repr() returns the 'unambiguous' representation, suitable for recreating the object in code; it is what the interactive console and debugging show."
      },
      "body": {
        "ru": "Внутри контейнеров элементы всегда показываются через repr — поэтому print(\"привет\") даёт голый текст, а print([\"привет\"]) выводит строку в кавычках, и это не баг. В своих классах достаточно определить __repr__: если __str__ нет, str() и print() падают обратно на него, а вот обратной подстановки не бывает. В f-строках отладочное представление включается суффиксом !r.",
        "en": "Inside containers elements are always shown via repr — that is why print(\"hi\") gives bare text while print([\"hi\"]) shows the string in quotes; not a bug. In your own classes defining __repr__ is enough: when __str__ is missing, str() and print() fall back to it, but there is no fallback the other way round. In f-strings the debug form is requested with the !r suffix."
      },
      "syntax": "str(object)  repr(object)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#repr",
      "version": "",
      "section": "Ввод и вывод",
      "subcat": "строки",
      "color_group": "builtin",
      "aliases": [
        "строковое представление объекта",
        "отладочное представление объекта",
        "как объект печатается в консоли"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(str(\"привет\"))    # → привет",
        "print(repr(\"привет\"))   # → 'привет'",
        "print(str(None))        # → None",
        "print(repr(None))       # → None",
        "print(str([1, 2]))      # → [1, 2]",
        "print(repr([1, 2]))     # → [1, 2]"
      ],
      "related": [
        "repr",
        "__str__-__repr__",
        "str",
        "print"
      ],
      "related_errors": []
    },
    {
      "id": "sep",
      "title": "sep=",
      "kind": "term",
      "summary": {
        "ru": "Параметр функции print(), задающий разделитель между аргументами. По умолчанию — пробел. Может быть любой строкой, включая пустую.",
        "en": "Parameter of print() that sets the separator between arguments. Defaults to a space. Can be any string, including an empty one."
      },
      "body": {
        "ru": "Разделитель вставляется только МЕЖДУ аргументами, поэтому при единственном аргументе sep не делает ничего: print(nums, sep=\", \") напечатает список целиком со скобками, а не через запятую — нужно распаковать его звёздочкой или собрать строку через str.join(). Для хвоста строки sep не отвечает, там работает end. Значением может быть только строка или None: sep=0 даёт TypeError.",
        "en": "The separator goes only BETWEEN arguments, so with a single argument sep does nothing: print(nums, sep=\", \") prints the list with its brackets — unpack it with a star or build the string with str.join() instead. What comes after the last item is end's job, not sep's. The value must be a string or None: sep=0 raises TypeError."
      },
      "syntax": "print(*objects, sep='разделитель')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#print",
      "version": "",
      "section": "Ввод и вывод",
      "subcat": "вывод",
      "color_group": "builtin",
      "aliases": [
        "разделитель при печати",
        "вывод через запятую",
        "убрать пробел между аргументами"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(1, 2, 3, sep=\", \")  # → 1, 2, 3",
        "print(\"a\", \"b\", \"c\", sep=\"\")  # → abc",
        "print(\"год\", \"месяц\", \"день\", sep=\"-\")  # → год-месяц-день",
        "print(10, 20, 30, sep=\"\\n\")  # каждое число на новой строке",
        "print(*[1, 2, 3, 4], sep=\" | \")  # → 1 | 2 | 3 | 4"
      ],
      "related": [
        "end",
        "print",
        "str.join"
      ],
      "related_errors": []
    },
    {
      "id": "переменные",
      "title": "Переменные",
      "kind": "term",
      "summary": {
        "ru": "Именованные области памяти для хранения значений. В Python переменные динамически типизированы — тип определяется значением, а не объявлением.",
        "en": "Named places in memory that hold values. Python variables are dynamically typed — the type comes from the value, not from a declaration."
      },
      "body": {
        "ru": "Имя в Python — это ярлык на объект, а не коробка со значением: b = a ничего не копирует, и если объект изменяемый (список, словарь), правка через одно имя видна через второе. Обращение к имени, которому ещё ничего не присвоили, даёт NameError, а не None или ноль. При множественном присваивании количество имён слева и значений справа должно совпадать, иначе ValueError.",
        "en": "A name is a label attached to an object, not a box holding a value: b = a copies nothing, and if the object is mutable (a list, a dict), a change made through one name is visible through the other. Reading a name that was never assigned raises NameError — not None, not zero. In multiple assignment the number of names on the left must match the number of values on the right, otherwise you get ValueError."
      },
      "syntax": "имя_переменной = значение",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#",
      "version": "",
      "section": "Ввод и вывод",
      "subcat": "переменные",
      "color_group": "builtin",
      "aliases": [
        "присваивание значения",
        "объявить переменную",
        "динамическая типизация"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "x = 10  # целое число",
        "pi = 3.14  # float",
        "greeting = \"Привет\"  # строка",
        "a, b, c = 1, 2, 3  # множественное присваивание",
        "a, b = b, a  # обмен значений без temp",
        "x += 5  # x = x + 5 → 15 (составное присваивание)",
        "del x  # удаление переменной"
      ],
      "related": [
        "операторы-присваивания",
        "преобразование-типов",
        "локальные-и-глобальные-переменные",
        "type"
      ],
      "related_errors": []
    },
    {
      "id": "числовые-литералы",
      "title": "Числовые литералы",
      "kind": "term",
      "summary": {
        "ru": "Python поддерживает запись чисел в двоичной (0b), восьмеричной (0o) и шестнадцатеричной (0x) системах счисления, разделитель _ для читаемости, а также экспоненциальную нотацию.",
        "en": "Python can write numbers in binary (0b), octal (0o) and hexadecimal (0x), allows the _ separator for readability, and supports exponential notation."
      },
      "body": {
        "ru": "Все эти записи дают обычный int — «двоичного числа» как отдельного типа нет, и печатается оно всегда в десятичном виде; обратный перевод в запись с префиксом делают bin(), oct(), hex(), и они возвращают строку. Экспоненциальная форма — исключение: 1e3 это float 1000.0, а не int. Ведущий ноль в десятичном литерале запрещён (010 — SyntaxError), для восьмеричной нужен явный 0o.",
        "en": "All these forms produce a plain int — there is no separate \"binary number\" type, and it always prints in decimal; to get the prefixed form back use bin(), oct() or hex(), which return strings. Exponential notation is the exception: 1e3 is the float 1000.0, not an int. A leading zero in a decimal literal is a SyntaxError (010); octal needs the explicit 0o prefix."
      },
      "syntax": "0b<биты>  0o<цифры>  0x<цифры>  <цифры>_<цифры>  <число>e<порядок>",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#",
      "version": "",
      "section": "Ввод и вывод",
      "subcat": "литералы",
      "color_group": "builtin",
      "aliases": [
        "двоичная запись числа",
        "шестнадцатеричное число",
        "экспоненциальная запись числа"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(0b1010)      # → 10",
        "print(0o17)        # → 15",
        "print(0xFF)        # → 255",
        "print(1_000_000)   # → 1000000",
        "print(1.5e3)       # → 1500.0",
        "print(0b1111 == 15 == 0xF)  # → True"
      ],
      "related": [
        "системы-счисления",
        "bin",
        "hex",
        "int"
      ],
      "related_errors": []
    },
    {
      "id": "slice",
      "title": "slice",
      "kind": "term",
      "summary": {
        "ru": "Встроенный тип объекта среза: slice(start, stop, step). Создаётся неявно синтаксисом a[start:stop:step] и передаётся в __getitem__.",
        "en": "The built-in slice object type: slice(start, stop, step), created by a[start:stop:step]."
      },
      "body": {
        "ru": "Напрямую создавать slice нужно редко: он полезен, когда один и тот же срез переиспользуется под именем в нескольких местах, или когда вы пишете свой __getitem__ и разбираете, что пришло — целый индекс или объект среза. Как и у range, один аргумент означает stop, а не start: slice(5) это start=None, stop=5. Реальные границы под конкретную длину даёт метод indices(): s.indices(len(seq)) вернёт готовую тройку с учётом None и отрицательных значений.",
        "en": "You rarely build a slice by hand: it pays off when the same slice is reused under a name in several places, or when you write your own __getitem__ and need to tell an integer index from a slice object. As with range, a single argument is stop, not start: slice(5) means start=None, stop=5. To resolve it against a concrete length use indices(): s.indices(len(seq)) returns a ready triple with None and negative values already sorted out."
      },
      "syntax": "slice(start, stop, step)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#slice",
      "version": "",
      "section": "Встроенные типы",
      "subcat": "срезы",
      "color_group": "module",
      "aliases": [
        "объект среза",
        "срез как отдельный объект"
      ],
      "keywords": [],
      "tags": [
        "slice"
      ],
      "examples": [
        "s = slice(1, 5, 2)",
        "print((s.start, s.stop, s.step))   # → (1, 5, 2)",
        "print('abcdefgh'[s])   # → bd",
        "print([0, 1, 2, 3, 4][slice(2, 4)])   # → [2, 3]",
        "print('abcdef'[slice(None, None, -1)])   # → fedcba",
        "print(slice(5).indices(3))   # → (0, 3, 1)"
      ],
      "related": [
        "срезы-списка",
        "срезы-строк",
        "срезы-с-шагом-2-1",
        "__len__-__getitem__-__setitem__-__contai"
      ],
      "related_errors": []
    },
    {
      "id": "__import__",
      "title": "__import__",
      "kind": "term",
      "summary": {
        "ru": "Низкоуровневый импорт модуля. Обычно используй importlib.import_module.",
        "en": "Low-level module import. Normally use importlib.import_module instead."
      },
      "body": {
        "ru": "В обычном коде эта функция не нужна: она существует для внутренностей оператора import, а имя модуля из строки правильно превращать в модуль через importlib.import_module. Отдельная ловушка — точечное имя: без непустого fromlist возвращается верхний пакет, а не вложенный модуль, тогда как import_module отдаёт ровно то, что попросили.",
        "en": "Application code almost never needs this — the function exists to back the import statement, and turning a module name held in a string into a module is importlib.import_module's job. There is also a dotted-name trap: with an empty fromlist you get the top-level package back, not the submodule, whereas import_module returns exactly what you asked for."
      },
      "syntax": "__import__(name, ...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#import__",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "модули",
      "color_group": "builtin",
      "aliases": [
        "импорт модуля по имени-строке",
        "динамический импорт модуля"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "math = __import__('math')",
        "print(math.pi)  # → 3.14159...",
        "os = __import__('os')",
        "print(os.sep)  # → /",
        "json = __import__('json')",
        "print(json.dumps({'a':1}))  # → {\"a\":1}",
        "import importlib",
        "re = importlib.import_module('re')",
        "print(re.match(r'\\d+','123').group())  # → 123"
      ],
      "related": [
        "sys.modules",
        "modulenotfounderror",
        "importerror"
      ],
      "related_errors": []
    },
    {
      "id": "aiter",
      "title": "aiter()",
      "kind": "function",
      "summary": {
        "ru": "Асинхронный аналог iter(): возвращает асинхронный итератор объекта (вызывает __aiter__). Работает с async for / anext().",
        "en": "Async analog of iter(): return an async iterator for the object (calls __aiter__)."
      },
      "body": {
        "ru": "Появилась в Python 3.10. Возвращает сам асинхронный итератор, а не awaitable — писать await aiter(obj) не нужно, await ставится уже перед anext(). Работает только с объектами, у которых есть __aiter__ (асинхронные генераторы, асинхронные потоки); для обычного списка это TypeError — там iter(). Явный вызов нужен редко: async for делает его за вас.",
        "en": "Added in Python 3.10. It hands back the async iterator itself, not an awaitable — await aiter(obj) is wrong; the await belongs in front of anext(). It only accepts objects that define __aiter__ (async generators, async streams); a plain list raises TypeError and needs iter(). You rarely call it by hand, since async for does it for you."
      },
      "syntax": "aiter(async_iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#aiter",
      "version": "3.10",
      "section": "Встроенные функции",
      "subcat": "Встроенные функции",
      "color_group": "builtin",
      "aliases": [
        "получить асинхронный итератор",
        "перебор асинхронной последовательности"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "import asyncio",
        "async def main():",
        "    async def agen():",
        "        yield 1",
        "        yield 2",
        "    it = aiter(agen())",
        "    print(await anext(it))   # → 1",
        "asyncio.run(main())"
      ],
      "related": [
        "anext",
        "iter",
        "async-for-async-with",
        "collections.abc.AsyncIterator"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "all",
      "title": "all",
      "kind": "term",
      "summary": {
        "ru": "Возвращает True, если все элементы итерируемого истинны, или если итерируемый пуст. Использует ленивое вычисление.",
        "en": "Returns True if all elements of the iterable are true, or if the iterable is empty. Evaluates lazily."
      },
      "body": {
        "ru": "Истинность считается через bool(), а не «элемент есть»: ноль среди чисел или пустая строка среди строк уронят проверку в False, хотя значения на месте — если вы имели в виду именно наличие, пишите условие явно, через is not None. И осторожнее с отрицанием: not all(...) означает «хотя бы один ложный», а вовсе не «все ложные» — для второго нужен any по отрицанию условия.",
        "en": "Truthiness goes through bool(), which is not the same as «the value is present»: a zero among numbers or an empty string among strings turns the check into False even though nothing is missing, so spell the condition out with is not None when presence is what you mean. Mind the negation too: not all(...) says «at least one is false», not «all are false» — the latter needs any() over the negated condition."
      },
      "syntax": "all(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#all",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "логика",
      "color_group": "builtin",
      "aliases": [
        "все элементы истинны",
        "проверить, что все условия выполнены",
        "истинны ли все значения"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(all([True,True,True]))  # → True",
        "print(all([True,False,True]))  # → False",
        "print(all([]))  # → True (пустой)",
        "print(all(x>0 for x in [1,2,3]))  # → True",
        "print(all(x>0 for x in [1,-1,3]))  # → False"
      ],
      "related": [
        "any"
      ],
      "related_errors": []
    },
    {
      "id": "anext",
      "title": "anext()",
      "kind": "function",
      "summary": {
        "ru": "Асинхронный аналог next(): возвращает awaitable для следующего элемента async-итератора; с default не поднимает StopAsyncIteration.",
        "en": "Async analog of next(): return an awaitable for the next item of an async iterator."
      },
      "body": {
        "ru": "Появилась в Python 3.10. Возвращает не элемент, а awaitable: без await получите объект корутины и предупреждение 'coroutine was never awaited', а элемент так и не прочитается. Второй аргумент работает как у next(): вместо StopAsyncIteration отдаёт значение по умолчанию — короче, чем оборачивать вызов в try/except.",
        "en": "Added in Python 3.10. It returns an awaitable, not the item: forget the await and you get a coroutine object plus a 'coroutine was never awaited' warning, while nothing is actually consumed. The second argument behaves like the one in next() — it yields a default instead of raising StopAsyncIteration, which is tidier than wrapping the call in try/except."
      },
      "syntax": "anext(async_iterator, default)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#anext",
      "version": "3.10",
      "section": "Встроенные функции",
      "subcat": "Встроенные функции",
      "color_group": "builtin",
      "aliases": [
        "следующий элемент асинхронного итератора",
        "асинхронное получение следующего значения"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "import asyncio",
        "async def main():",
        "    async def agen():",
        "        yield 10",
        "    it = aiter(agen())",
        "    print(await anext(it))          # → 10",
        "    print(await anext(it, 'конец'))  # → конец",
        "asyncio.run(main())"
      ],
      "related": [
        "aiter",
        "next",
        "stopasynciteration",
        "async-for-async-with"
      ],
      "related_errors": [
        "StopAsyncIteration",
        "TypeError"
      ]
    },
    {
      "id": "any",
      "title": "any",
      "kind": "term",
      "summary": {
        "ru": "Возвращает True, если хотя бы один элемент итерируемого истинен. Использует ленивое вычисление.",
        "en": "Returns True if at least one element of the iterable is true. Evaluates lazily."
      },
      "body": {
        "ru": "any отвечает только «да или нет» и не отдаёт сам подошедший элемент — за элементом идут к next() с генератором и значением по умолчанию. И передавайте именно генераторное выражение: со списковым включением внутри скобок Python сначала честно посчитает все элементы, и ленивая остановка на первом истинном пропадёт.",
        "en": "any() gives you a yes-or-no answer and never the element that matched; when you need the element itself, reach for next() over a generator with a default value. Pass a generator expression rather than a list comprehension inside the parentheses — the comprehension builds every result first, so the early exit on the first true item is lost."
      },
      "syntax": "any(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#any",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "логика",
      "color_group": "builtin",
      "aliases": [
        "хотя бы один элемент истинен",
        "есть ли подходящий элемент",
        "выполняется ли хоть одно условие"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(any([False,True,False]))  # → True",
        "print(any([False,False]))  # → False",
        "print(any([]))  # → False",
        "print(any(x<0 for x in [1,-1,3]))  # → True",
        "print(any(x==5 for x in range(10)))  # → True",
        "# ленивое вычисление — остановится на первом True",
        "print(any(x > 100 for x in range(10**6)))  # → False"
      ],
      "related": [
        "all"
      ],
      "related_errors": []
    },
    {
      "id": "ascii",
      "title": "ascii",
      "kind": "term",
      "summary": {
        "ru": "Возвращает repr() объекта, но заменяет все не-ASCII символы на escape-последовательности \\xNN, \\uNNNN или \\UNNNNNNNN.",
        "en": "Returns repr() of the object but replaces every non-ASCII character with a \\xNN, \\uNNNN or \\UNNNNNNNN escape sequence."
      },
      "body": {
        "ru": "Возвращается именно repr, то есть строка вместе с кавычками — для сравнения или подсчёта символов это не годится, только для показа. Берите её, когда вывод обязан быть чисто ASCII (лог, консоль без юникода) или когда надо разглядеть невидимые символы и подмену похожих букв; в f-строках то же самое даёт конверсия !a.",
        "en": "What you get back is a repr, quotes included — fine for display, useless for comparing or counting characters. Reach for it when the output must be pure ASCII (a log file, a console that mangles Unicode) or when you need to spot invisible characters and look-alike letters; the !a conversion inside an f-string does exactly the same thing."
      },
      "syntax": "ascii(object)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#ascii",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "строки",
      "color_group": "builtin",
      "aliases": [
        "экранирование юникод-символов",
        "показать коды символов в строке"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(ascii('hello'))  # → 'hello'",
        "print(ascii('привет'))  # → '\\u043f\\u0440...' (экранировано)",
        "print(ascii([1,'a']))  # → [1, 'a']",
        "print(ascii('café'))  # → 'caf\\xe9'",
        "print(ascii(None))  # → None"
      ],
      "related": [
        "repr",
        "repr-vs-str",
        "str.isascii"
      ],
      "related_errors": []
    },
    {
      "id": "bin",
      "title": "bin",
      "kind": "term",
      "summary": {
        "ru": "Преобразует целое число в строку двоичного представления с префиксом '0b'. Отрицательные числа: bin(-5) → '-0b101'.",
        "en": "Converts an integer to its binary string representation with the '0b' prefix. Negative numbers: bin(-5) → '-0b101'."
      },
      "body": {
        "ru": "Возвращается строка, а не число: складывать и сравнивать её с числами бессмысленно, обратное преобразование — int(s, 2). Отрицательные значения записываются знаком минус перед модулем, никакого дополнительного кода «как в памяти» здесь нет. Аргумент обязан быть целым (или иметь __index__), float даёт TypeError; если нужна фиксированная ширина без префикса 0b, берите форматирование по коду b.",
        "en": "The result is a string, not a number — arithmetic on it makes no sense, and int(s, 2) converts back. Negative values get a leading minus sign in front of the magnitude; this is not two's-complement machine representation. The argument must be an integer (or expose __index__), a float raises TypeError, and for a fixed-width value without the 0b prefix use the b format code instead."
      },
      "syntax": "bin(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#bin",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "числа",
      "color_group": "builtin",
      "aliases": [
        "перевести число в двоичную систему",
        "двоичное представление числа",
        "двоичный код числа"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(bin(10))   # → 0b1010",
        "print(bin(255))  # → 0b11111111",
        "print(bin(-5))   # → -0b101",
        "print(bin(0))    # → 0b0",
        "print(int(bin(42),2))  # → 42"
      ],
      "related": [
        "hex",
        "oct",
        "системы-счисления",
        "int.bit_length"
      ],
      "related_errors": []
    },
    {
      "id": "breakpoint",
      "title": "breakpoint",
      "kind": "term",
      "summary": {
        "ru": "Вызывает встроенный отладчик pdb в месте вызова. Python 3.7+. Поведение можно переопределить через PYTHONBREAKPOINT.",
        "en": "Drops into the built-in pdb debugger at the point of the call. Python 3.7+. The behavior can be overridden through PYTHONBREAKPOINT."
      },
      "body": {
        "ru": "Самая частая беда на курсе — забытый breakpoint() в отправленном решении: программа уходит в отладчик и начинает читать строки тестового ввода как команды pdb, после чего падает или зависает по таймауту. Обезвредить сразу все точки останова, ничего не вырезая из кода, помогает переменная окружения PYTHONBREAKPOINT=0; ею же можно подставить свой отладчик вместо pdb.",
        "en": "The classic accident is a forgotten breakpoint() in a submitted solution: the program drops into the debugger and starts consuming the test input as pdb commands, then dies or hangs until the timeout. Setting PYTHONBREAKPOINT=0 disables every breakpoint call without editing a single line, and the same variable can point at a different debugger instead of pdb."
      },
      "syntax": "breakpoint()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#breakpoint",
      "version": "3.7",
      "section": "Встроенные функции",
      "subcat": "отладка",
      "color_group": "builtin",
      "aliases": [
        "точка останова",
        "запустить отладчик",
        "пошаговая отладка кода"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "# breakpoint() — вставляет точку останова",
        "# Устанавливается через PYTHONBREAKPOINT env",
        "import os",
        "os.environ['PYTHONBREAKPOINT']='0'  # отключить",
        "# breakpoint()  # → войти в pdb",
        "print('Debugging would start here')  # → debug point"
      ],
      "related": [
        "print",
        "logging-debug",
        "assert"
      ],
      "related_errors": []
    },
    {
      "id": "callable",
      "title": "callable",
      "kind": "term",
      "summary": {
        "ru": "Возвращает True, если объект можно вызвать: функция, класс, лямбда или объект с методом __call__.",
        "en": "Returns True if the object can be called: a function, a class, a lambda or an object with a __call__ method."
      },
      "body": {
        "ru": "Проверка идёт по типу объекта, а не по самому объекту: приписать экземпляру атрибут obj.__call__ = f недостаточно — Python ищет __call__ у класса, и callable(obj) останется False, как и сам вызов. True тоже ничего не обещает: объект вызываемый, но с другим числом аргументов вызов упадёт TypeError. В обычном коде проверка почти не нужна — прямее просто вызвать и поймать TypeError либо разделять ветки по isinstance; callable полезен на границе, где приходит то ли значение, то ли фабрика.",
        "en": "The check looks at the object's type, not the object: assigning obj.__call__ = f to an instance is not enough, because Python looks up __call__ on the class, so callable(obj) stays False and the call fails too. A True answer promises little either — the object is callable, but calling it with the wrong arguments still raises TypeError. In everyday code you rarely need the check: calling and handling TypeError, or branching on isinstance, is more direct; callable earns its keep at an API boundary where a value or a factory may arrive."
      },
      "syntax": "callable(object)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#callable",
      "version": "3.2",
      "section": "Встроенные функции",
      "subcat": "интроспекция",
      "color_group": "builtin",
      "aliases": [
        "можно ли вызвать объект",
        "проверить что объект функция"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(callable(print))    # → True",
        "print(callable(len))      # → True",
        "print(callable(42))       # → False",
        "print(callable(lambda: 0))  # → True",
        "class A:",
        "def __call__(self): pass",
        "print(callable(A()))  # → True"
      ],
      "related": [
        "вызываемые-объекты-__call__",
        "isinstance",
        "callable-arg-ret"
      ],
      "related_errors": []
    },
    {
      "id": "chr",
      "title": "chr",
      "kind": "term",
      "summary": {
        "ru": "Возвращает строку из одного символа по его Unicode кодовой точке (целое число). Обратная функция для ord().",
        "en": "Returns a one-character string for the given Unicode code point (an integer). The inverse of ord()."
      },
      "body": {
        "ru": "Аргумент — номер символа в Unicode, а не байт: chr(200) даёт 'È', а байт со значением 200 получают через bytes([200]). Допустим диапазон 0..0x10FFFF, за его пределами ValueError; коды суррогатов 0xD800-0xDFFF формально принимаются, но такую строку потом не удастся закодировать в UTF-8.",
        "en": "The argument is a Unicode code point, not a byte: chr(200) gives 'È', while a byte with value 200 comes from bytes([200]). Valid values are 0 through 0x10FFFF and anything else raises ValueError; surrogate codes 0xD800-0xDFFF are accepted but the resulting string cannot be encoded to UTF-8 afterwards."
      },
      "syntax": "chr(i)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#chr",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "строки",
      "color_group": "builtin",
      "aliases": [
        "символ по коду",
        "преобразовать число в символ",
        "буква по номеру в таблице юникода"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(chr(65))   # → A",
        "print(chr(97))   # → a",
        "print(chr(1072)) # → а (кириллица)",
        "print(chr(9829)) # → ♥",
        "print(''.join(chr(i) for i in range(65,70)))  # → ABCDE"
      ],
      "related": [
        "ord",
        "ascii",
        "str.isascii"
      ],
      "related_errors": []
    },
    {
      "id": "compile",
      "title": "compile",
      "kind": "term",
      "summary": {
        "ru": "Компилирует строку или AST в объект кода (code object). Используется с exec() или eval() для динамического кода.",
        "en": "Compiles a string or an AST into a code object. Used with exec() or eval() to run code built at runtime."
      },
      "body": {
        "ru": "Аргумент mode обязан совпадать с тем, что внутри: 'eval' принимает ровно одно выражение (строка с присваиванием сразу даст SyntaxError), 'exec' — любой набор инструкций, 'single' — одну инструкцию с печатью результата, как в интерактивной оболочке. Второй аргумент filename нигде не открывается — это просто подпись, которая всплывёт в трейсбеке и в отладчике, поэтому осмысленное имя вроде '<user-formula>' помогает потом читать ошибки. Смысл отдельного compile — разобрать исходник один раз и потом гонять готовый code object в цикле; на безопасность это не влияет никак, опасен по-прежнему момент exec/eval.",
        "en": "The mode argument must match what you actually pass: 'eval' takes exactly one expression (a string containing an assignment raises SyntaxError right away), 'exec' takes any sequence of statements, 'single' takes one statement and echoes its result like the interactive shell. The filename argument is never opened — it is only a label that shows up in tracebacks and debuggers, so something descriptive like '<user-formula>' pays off when errors appear. The point of compiling separately is to parse the source once and reuse the code object in a loop; it adds no safety whatsoever, since the dangerous step is still the exec/eval call."
      },
      "syntax": "compile(source, filename, mode)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#compile",
      "version": "3.8",
      "section": "Встроенные функции",
      "subcat": "выполнение",
      "color_group": "builtin",
      "aliases": [
        "скомпилировать строку в код",
        "объект кода"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "code = compile('1+2', '<string>', 'eval')",
        "print(eval(code))  # → 3",
        "stmt = compile('x=5\\nprint(x)', '<s>', 'exec')",
        "exec(stmt)  # → 5",
        "code2 = compile('[x**2 for x in range(5)]','<s>','eval')",
        "print(eval(code2))  # → [0,1,4,9,16]",
        "print(type(code))  # → <class 'code'>"
      ],
      "related": [
        "eval",
        "exec",
        "syntaxerror"
      ],
      "related_errors": []
    },
    {
      "id": "delattr",
      "title": "delattr",
      "kind": "term",
      "summary": {
        "ru": "Удаляет атрибут объекта по имени. Эквивалент del obj.attr, но позволяет передать имя атрибута как строку динамически.",
        "en": "Deletes the named attribute of an object. Equivalent to del obj.attr, but lets you pass the attribute name dynamically as a string."
      },
      "body": {
        "ru": "Смысл в delattr есть только тогда, когда имя атрибута вычисляется в рантайме; если имя известно заранее, пишите del obj.attr. Удаляется именно атрибут экземпляра: если x объявлен в теле класса, а не присвоен через self, то delattr(экземпляр, 'x') упадёт с AttributeError, хотя чтение obj.x до этого работало. Отсутствующий атрибут — тоже AttributeError, так что подстраховывайтесь hasattr или try/except.",
        "en": "delattr earns its keep only when the attribute name is computed at run time; if you know the name in advance, write del obj.attr. It removes an instance attribute: if x lives in the class body rather than being assigned through self, delattr(instance, 'x') raises AttributeError even though reading obj.x worked fine. A missing attribute raises AttributeError too, so guard the call with hasattr or try/except."
      },
      "syntax": "delattr(object, name)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#delattr",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "ООП",
      "color_group": "builtin",
      "aliases": [
        "удалить атрибут объекта",
        "удалить свойство по имени"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "class A:",
        "x = 1",
        "def __init__(self): self.y = 2",
        "a = A()",
        "delattr(a,'y')",
        "print(hasattr(a,'y'))  # → False",
        "delattr(A,'x')",
        "print(hasattr(A,'x'))  # → False",
        "try:",
        "delattr(a,'z')",
        "except AttributeError as e: print(e)"
      ],
      "related": [
        "setattr",
        "getattr",
        "hasattr"
      ],
      "related_errors": []
    },
    {
      "id": "dict",
      "title": "dict",
      "kind": "term",
      "summary": {
        "ru": "Изменяемый маппинг: хранит пары ключ–значение. Ключи уникальны и хешируемы. С Python 3.7 сохраняет порядок вставки.",
        "en": "A mutable mapping: stores key–value pairs. Keys are unique and hashable. Since Python 3.7 it preserves insertion order."
      },
      "body": {
        "ru": "Обращение d[k] к отсутствующему ключу — это KeyError; если ключа может не быть, берите d.get(k) или d.get(k, значение_по_умолчанию). Ключ обязан быть хешируемым, поэтому кортеж ключом быть может, а список — нет. Поиск и вставка в среднем O(1), а вот сравнение словарей порядок вставки игнорирует: {'a':1,'b':2} == {'b':2,'a':1} истинно, хотя порядок при переборе разный.",
        "en": "Indexing a missing key with d[k] raises KeyError; when the key may be absent, use d.get(k) or d.get(k, default) instead. Keys must be hashable, so a tuple can be a key but a list cannot. Lookup and insertion are O(1) on average, and equality ignores insertion order — {'a':1,'b':2} == {'b':2,'a':1} is True even though iteration order differs."
      },
      "syntax": "dict(**kwargs)\ndict(mapping)\ndict(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#func-dict",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "типы",
      "color_group": "builtin",
      "aliases": [
        "словарь",
        "ассоциативный массив",
        "пары ключ-значение"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(dict(a=1,b=2))  # → {'a':1,'b':2}",
        "print(dict([('x',1),('y',2)]))  # → {'x':1,'y':2}",
        "print(dict.fromkeys(['a','b'],0))  # → {'a':0,'b':0}",
        "d=dict(zip('abc',[1,2,3]))",
        "print(d)  # → {'a':1,'b':2,'c':3}",
        "print(dict({'a':1},b=2))  # → {'a':1,'b':2}"
      ],
      "related": [
        "создание-словаря",
        "доступ-d-key",
        "dict.get",
        "collections.defaultdict"
      ],
      "related_errors": []
    },
    {
      "id": "dir",
      "title": "dir",
      "kind": "term",
      "summary": {
        "ru": "Список атрибутов объекта (или локального пространства имён).",
        "en": "Lists the attributes of an object (or the names of the current local namespace)."
      },
      "body": {
        "ru": "dir() возвращает имена (строки), а не сами атрибуты — чтобы добраться до значения, нужен getattr(obj, name). Список отсортирован по алфавиту и намеренно неполон: он собирается через __dir__ и не видит атрибуты, которые класс создаёт на лету в __getattr__. Это инструмент для разглядывания объекта в REPL, а не опора для логики программы.",
        "en": "dir() hands back names as strings, not the attributes themselves — reach the value with getattr(obj, name). The list is alphabetised and deliberately approximate: it is built from __dir__ and misses anything a class conjures dynamically in __getattr__. Treat it as a REPL exploration tool, not something program logic should depend on."
      },
      "syntax": "dir([object])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#dir",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "интроспекция",
      "color_group": "builtin",
      "aliases": [
        "список атрибутов объекта",
        "какие методы есть у объекта",
        "посмотреть содержимое объекта"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(dir([]))[:5]  # → первые 5 атрибутов списка",
        "print('append' in dir([]))  # → True",
        "class A:",
        "x = 1",
        "def f(self): pass",
        "print('x' in dir(A))  # → True",
        "print('f' in dir(A))  # → True",
        "print(type(dir([])))  # → list"
      ],
      "related": [
        "vars",
        "getattr",
        "help"
      ],
      "related_errors": []
    },
    {
      "id": "eval",
      "title": "eval",
      "kind": "term",
      "summary": {
        "ru": "Вычисляет выражение из строки. ВНИМАНИЕ: небезопасен для ненадёжного ввода!",
        "en": "Evaluates an expression given as a string. WARNING: unsafe for untrusted input!"
      },
      "body": {
        "ru": "eval считает ровно одно выражение и возвращает его значение — присваивания, if и циклы туда не пролезут, для них нужен exec. Ограничить вред подменой globals не выйдет: даже из урезанного словаря добираются до встроенных функций и __import__, поэтому единственная защита — не пускать в eval чужие строки. Когда надо разобрать введённое число или литерал, берите int(), float() или ast.literal_eval(), который понимает только константы и не исполняет код.",
        "en": "eval evaluates exactly one expression and returns its value — assignments, if statements and loops will not go through it; those need exec. Handing it a stripped-down globals dict is not a sandbox: builtins and __import__ remain reachable from almost any expression, so the only real defence is never feeding eval a string you did not write. To turn user input into a number or a literal, use int(), float() or ast.literal_eval(), which understands constants only and executes nothing."
      },
      "syntax": "eval(expression[, globals[, locals]])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#eval",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "выполнение",
      "color_group": "builtin",
      "aliases": [
        "вычислить выражение из строки",
        "выполнить строку как выражение",
        "превратить строку в число или список"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(eval('2**10'))  # → 1024",
        "print(eval('[x**2 for x in range(5)]'))  # → [0,1,4,9,16]",
        "x = 10",
        "print(eval('x + 5'))  # → 15",
        "print(eval('1+2', {}))  # → 3 (пустой globals)",
        "print(eval('abs(-5)', {'abs':abs}))  # → 5"
      ],
      "related": [
        "exec",
        "compile",
        "input"
      ],
      "related_errors": []
    },
    {
      "id": "exec",
      "title": "exec",
      "kind": "term",
      "summary": {
        "ru": "Выполняет динамически созданный Python-код: строку или code object. Изменяет переданное пространство имён.",
        "en": "Executes dynamically created Python code: a string or a code object. Modifies the namespace it is given."
      },
      "body": {
        "ru": "Главная ловушка — вызвать exec('x = 1') внутри функции и ждать, что появится локальная переменная x: компилятор распределяет локальные ещё до запуска, и созданное exec'ом имя коду функции не видно (на уровне модуля, где пространство имён и есть словарь, всё работает). Надёжный приём — отдать exec отдельный словарь и потом достать результат из него: exec(src, g), затем g['x']. Возвращает exec всегда None — значение выражения так не получить, для этого eval.",
        "en": "The classic trap is calling exec('x = 1') inside a function and expecting a local x to appear: locals are laid out at compile time, so a name created by exec is invisible to the function's own code (at module level, where the namespace is a real dict, it does work). The reliable pattern is to give exec its own dict and read the result out of it: exec(src, g), then g['x']. exec always returns None — it cannot hand you the value of an expression; that is what eval is for."
      },
      "syntax": "exec(object[, globals[, locals]])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#exec",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "выполнение",
      "color_group": "builtin",
      "aliases": [
        "выполнить код из строки",
        "запустить строку как программу",
        "динамическое выполнение кода"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "exec('x = 1 + 2')",
        "# print(x)  # x есть в локальном пространстве?",
        "g = {}",
        "exec('y = 42', g)",
        "print(g['y'])  # → 42",
        "exec('for i in range(3): print(i)')  # → 0 1 2",
        "def make_func(name):",
        "    exec(f'def {name}(): return \"{name}\"', globals())",
        "    make_func('hello')",
        "    print(hello())  # → hello"
      ],
      "related": [
        "eval",
        "compile",
        "globals",
        "locals"
      ],
      "related_errors": []
    },
    {
      "id": "format",
      "title": "format",
      "kind": "term",
      "summary": {
        "ru": "Форматирует значение по строке спецификации формата (format spec). Вызывает метод __format__ объекта.",
        "en": "Formats a value according to a format specification string (format spec). Calls the object's __format__ method."
      },
      "body": {
        "ru": "Это ровно то, что происходит внутри фигурных скобок f-строки, поэтому отдельный вызов оправдан лишь тогда, когда сама спецификация считается в рантайме (например, число знаков после точки берётся из переменной). Не путайте с методом str.format(): здесь первый аргумент — форматируемое значение, а не шаблон. Округление у '.2f' идёт от двоичного представления float, так что format(2.675, '.2f') честно выдаёт 2.67.",
        "en": "This is precisely what happens inside the braces of an f-string, so a separate call earns its keep only when the spec itself is computed at runtime — say, the number of decimals comes from a variable. Do not mix it up with the str.format() method: here the first argument is the value being formatted, not a template. Rounding for '.2f' follows the binary float value, which is why format(2.675, '.2f') honestly yields 2.67."
      },
      "syntax": "format(value, format_spec='')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#format",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "строки",
      "color_group": "builtin",
      "aliases": [
        "форматирование числа",
        "вывести число с двумя знаками после запятой",
        "выравнивание при выводе"
      ],
      "keywords": [
        "str.format"
      ],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(format(3.14159, '.2f'))  # → 3.14",
        "print(format(42, '08b'))  # → 00101010",
        "print(format(1000000, ','))  # → 1,000,000",
        "print(format('hello', '^10'))  # →   hello",
        "print(format(0.25, '%'))  # → 25.000000%"
      ],
      "related": [
        "format-метод-форматирования",
        "f-строки",
        "форматирование-старый-стиль",
        "str.format_map"
      ],
      "related_errors": []
    },
    {
      "id": "frozenset",
      "title": "frozenset",
      "kind": "term",
      "summary": {
        "ru": "Неизменяемое множество. Поддерживает все операции set (объединение, пересечение и т.д.), но не поддерживает add() и remove(). Хешируемо — можно использовать как ключ словаря.",
        "en": "An immutable set. Supports every set operation (union, intersection and so on) but has no add() or remove(). Hashable — it can be used as a dictionary key."
      },
      "body": {
        "ru": "frozenset берут тогда, когда множество нужно положить ключом в словарь или элементом другого множества — обычный set туда не пустят, он нехешируемый. Литерала у frozenset нет, только вызов конструктора: фигурные скобки всегда дают set (а пустые — вообще пустой словарь). Ловушка: frozenset({1,2}) == {1,2} истинно, но достать значение по обычному set из такого словаря нельзя — d[{1,2}] упадёт с TypeError, ведь ключ сначала надо захешировать.",
        "en": "Use frozenset when a set has to become a dictionary key or an element of another set — a plain set is rejected there because it is unhashable. There is no literal syntax for it, only the constructor call: braces always build a set, and empty braces build a dict. A trap: frozenset({1,2}) == {1,2} is True, yet you still cannot look the entry up with a plain set — d[{1,2}] raises TypeError, because the key has to be hashed first."
      },
      "syntax": "frozenset(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#func-frozenset",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "типы",
      "color_group": "builtin",
      "aliases": [
        "замороженное множество",
        "хешируемое множество",
        "множество как ключ словаря"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "fs = frozenset([1,2,3,2,1])",
        "print(fs)  # → frozenset({1, 2, 3})",
        "print(sorted(frozenset('hello')))  # → ['e', 'h', 'l', 'o'] (порядок множества не определён, поэтому сортируем)",
        "d = {frozenset([1,2]): 'value'}",
        "print(d)  # → {frozenset({1, 2}): 'value'}",
        "print(1 in fs)  # → True",
        "print(fs & frozenset([2,3,4]))  # → frozenset({2, 3})"
      ],
      "related": [
        "frozenset-неизменяемое-множество",
        "set",
        "hash"
      ],
      "related_errors": []
    },
    {
      "id": "getattr",
      "title": "getattr",
      "kind": "term",
      "summary": {
        "ru": "Получает атрибут объекта по имени как строке. Третий аргумент — значение по умолчанию, если атрибут не существует (иначе AttributeError).",
        "en": "Gets an attribute of an object by its name given as a string. The third argument is the default value used when the attribute does not exist (otherwise AttributeError)."
      },
      "body": {
        "ru": "Значение по умолчанию вычисляется всегда, ещё до самого поиска атрибута, — если оно дорогое или имеет побочный эффект, лучше сначала спросить hasattr. Вторая ловушка: default подставляется на любой AttributeError, включая тот, который бросил код внутри property, поэтому настоящая ошибка в свойстве тихо превращается в «атрибута нет».",
        "en": "The default is evaluated eagerly, before the lookup even happens — if computing it is expensive or has side effects, check with hasattr instead. The subtler trap: the default kicks in on any AttributeError, including one raised inside a property's own code, so a real bug in that property silently turns into \"attribute missing\"."
      },
      "syntax": "getattr(object, name[, default])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#getattr",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "ООП",
      "color_group": "builtin",
      "aliases": [
        "получить атрибут по имени",
        "обращение к полю объекта через строку",
        "динамический доступ к атрибуту"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "class A:",
        "x = 42",
        "a = A()",
        "print(getattr(a,'x'))  # → 42",
        "print(getattr(a,'y',None))  # → None",
        "getattr([1,2,3],'append')(4)  # → [1,2,3,4]",
        "print(getattr('hello','upper')())  # → HELLO",
        "name = 'x'; print(getattr(a, name))  # → 42"
      ],
      "related": [
        "setattr",
        "hasattr",
        "delattr",
        "attributeerror"
      ],
      "related_errors": []
    },
    {
      "id": "globals",
      "title": "globals",
      "kind": "term",
      "summary": {
        "ru": "Возвращает словарь глобального пространства имён текущего модуля.",
        "en": "Returns the dictionary of the global namespace of the current module."
      },
      "body": {
        "ru": "Это не копия, а живой словарь модуля: присваивание в него действительно создаёт глобальную переменную — в отличие от locals() внутри функции, где правки в общем случае никуда не доедут. Отсюда соблазн лепить имена переменных на лету, но почти всегда вместо этого нужен обычный словарь: сгенерированные имена не видит ни читатель, ни линтер. Внутри функции globals() отдаёт пространство имён модуля, где функция определена, а не того, кто её вызвал.",
        "en": "What you get is the module's live namespace dictionary, not a snapshot, so assigning into it really does create a global — unlike writing into locals() inside a function, which generally has no effect. That tempts people into manufacturing variable names at runtime; a plain dictionary is nearly always the better answer, since generated names are invisible to readers and linters alike. Called inside a function, globals() still points at the module where that function was defined, not at the caller's module."
      },
      "syntax": "globals()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#globals",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "пространства имён",
      "color_group": "builtin",
      "aliases": [
        "глобальные переменные модуля",
        "словарь глобальных имён",
        "обратиться к переменной по её имени-строке"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "x = 42",
        "print('x' in globals())  # → True",
        "print(globals()['x'])  # → 42",
        "globals()['new_var'] = 99",
        "print(new_var)  # → 99",
        "print(type(globals()))  # → dict",
        "print('print' in dir(__builtins__))  # → True"
      ],
      "related": [
        "locals",
        "локальные-и-глобальные-переменные",
        "global-nonlocal",
        "vars"
      ],
      "related_errors": []
    },
    {
      "id": "hasattr",
      "title": "hasattr",
      "kind": "term",
      "summary": {
        "ru": "Возвращает True, если у объекта есть атрибут с указанным именем. Реализован через попытку getattr() — проверяет наличие без исключения.",
        "en": "Returns True if the object has an attribute with the given name. Implemented as a getattr() attempt — checks presence without raising."
      },
      "body": {
        "ru": "hasattr не заглядывает в устройство объекта, а действительно запрашивает атрибут: у property это выполнит её тело со всеми побочными эффектами и затратами. False возвращается только когда пойман AttributeError — исключение любого другого типа улетит наружу. Если значение атрибута всё равно понадобится дальше, дешевле один getattr(obj, name, default), чем hasattr плюс повторное обращение.",
        "en": "hasattr does not inspect the object's structure — it actually fetches the attribute, so for a property it runs that property's body, side effects and cost included. It returns False only when an AttributeError was caught; any other exception propagates. And if you need the value afterwards anyway, a single getattr(obj, name, default) is cheaper than hasattr plus a second lookup."
      },
      "syntax": "hasattr(object, name)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#hasattr",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "ООП",
      "color_group": "builtin",
      "aliases": [
        "проверить наличие атрибута",
        "есть ли у объекта поле",
        "проверка существования свойства"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "class A:",
        "x = 1",
        "a = A()",
        "print(hasattr(a,'x'))  # → True",
        "print(hasattr(a,'y'))  # → False",
        "print(hasattr([],'append'))  # → True",
        "print(hasattr([],'nonexistent'))  # → False",
        "print(hasattr('str','upper'))  # → True"
      ],
      "related": [
        "getattr",
        "setattr",
        "attributeerror"
      ],
      "related_errors": []
    },
    {
      "id": "hash",
      "title": "hash",
      "kind": "term",
      "summary": {
        "ru": "Возвращает целочисленный хеш объекта. Объект должен быть хешируемым (неизменяемым). Используется словарями и множествами.",
        "en": "Returns the integer hash of an object. The object must be hashable (immutable). Used by dictionaries and sets."
      },
      "body": {
        "ru": "У str и bytes хеш заново рандомизируется при каждом запуске интерпретатора (PYTHONHASHSEED) — это защита от атак на коллизии, а не контрольная сумма, поэтому сохранять его в файл или сравнивать между процессами нельзя; для стабильного отпечатка берите hashlib. И действует правило «равные объекты — равные хеши»: hash(1) == hash(1.0) == hash(True), а свой класс становится нехешируемым, как только вы определили __eq__ и не определили __hash__.",
        "en": "For str and bytes the hash is randomised on every interpreter start (PYTHONHASHSEED) — it is a collision-attack defence, not a checksum, so never persist it or compare it across processes; use hashlib for a stable fingerprint. The contract also says equal objects must hash equally: hash(1) == hash(1.0) == hash(True), and your own class turns unhashable the moment you define __eq__ without __hash__."
      },
      "syntax": "hash(object)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#hash",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "интроспекция",
      "color_group": "builtin",
      "aliases": [
        "хеш объекта",
        "хешируемый объект",
        "почему список не может быть ключом словаря"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(hash(42))      # → 42",
        "print(hash('hello')) # → число",
        "print(hash((1,2,3))) # → число",
        "try:",
        "hash([1,2,3])  # → TypeError: unhashable type: 'list'",
        "except TypeError as e: print(e)",
        "print(hash(42) == hash(42.0))  # → True (равные объекты)"
      ],
      "related": [
        "collections.abc.Hashable",
        "хеш-таблица-dict",
        "кортеж-как-ключ-словаря",
        "frozenset"
      ],
      "related_errors": []
    },
    {
      "id": "help",
      "title": "help",
      "kind": "term",
      "summary": {
        "ru": "Выводит встроенную справку по объекту, функции или модулю. В интерактивном режиме без аргумента запускает интерактивную справку.",
        "en": "Prints the built-in help for an object, function or module. With no argument in interactive mode it starts the interactive help."
      },
      "body": {
        "ru": "help ничего не возвращает, а печатает, поэтому print(help(len)) покажет справку и следом None. Текст берётся из docstring — у ваших собственных функций справка будет пустой, пока docstring не написан. И не оставляйте голый help() в сдаваемом решении: без аргумента он запускает интерактивную справку и повиснет, читая stdin.",
        "en": "help prints and returns None, so print(help(len)) shows the page and then None underneath. What it prints is the docstring, which is why your own functions look empty until you write one. And never leave a bare help() in a submitted solution — with no argument it starts the interactive helper and blocks reading stdin."
      },
      "syntax": "help([object])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#help",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "интроспекция",
      "color_group": "builtin",
      "aliases": [
        "встроенная справка",
        "документация по функции",
        "как узнать что делает функция"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "help(len)  # → выводит справку о len",
        "help(str.upper)  # → справка о методе",
        "help(list)  # → все методы списка",
        "print(len.__doc__[:30])  # → Return the number of items...",
        "import math; help(math.sqrt)  # → sqrt справка"
      ],
      "related": [
        "dir",
        "type"
      ],
      "related_errors": []
    },
    {
      "id": "hex",
      "title": "hex",
      "kind": "term",
      "summary": {
        "ru": "Преобразует целое число в строку шестнадцатеричного представления с префиксом '0x'. Буквы в нижнем регистре.",
        "en": "Converts an integer to its hexadecimal string representation with the '0x' prefix. The letters are lower-case."
      },
      "body": {
        "ru": "Буквы всегда в нижнем регистре и префикс 0x приклеен намертво — если нужен верхний регистр или строка без префикса, используйте форматирование по кодам x и X. Функция работает только с целыми: у float есть собственный метод float.hex(), а байты переводит в hex-строку bytes.hex(). Обратно разбирает int(s, 16), причём он принимает строку и с префиксом 0x, и без него.",
        "en": "The digits are always lower-case and the 0x prefix is always there — for upper-case or prefix-free output use the x and X format codes. The function accepts integers only: floats have their own float.hex() method, and byte data is converted by bytes.hex(). To parse it back use int(s, 16), which accepts the string with or without the 0x prefix."
      },
      "syntax": "hex(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#hex",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "числа",
      "color_group": "builtin",
      "aliases": [
        "перевести число в шестнадцатеричную систему",
        "шестнадцатеричное представление числа"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(hex(255))  # → 0xff",
        "print(hex(16))   # → 0x10",
        "print(hex(-42))  # → -0x2a",
        "print(hex(0))    # → 0x0",
        "print(int(hex(255), 16))  # → 255"
      ],
      "related": [
        "bin",
        "oct",
        "системы-счисления",
        "int"
      ],
      "related_errors": []
    },
    {
      "id": "id",
      "title": "id",
      "kind": "term",
      "summary": {
        "ru": "Возвращает уникальный идентификатор объекта (адрес в памяти).",
        "en": "Returns the unique identifier of an object (its address in memory)."
      },
      "body": {
        "ru": "Идентификатор уникален только пока объект жив: после сборки мусора то же число может достаться новому объекту, так что запомнить id и сравнить его позже — ненадёжно. Практический смысл один — понять, один это объект или два, и для этого читабельнее a is b, тогда как равенство значений проверяют через ==. Осторожно с интернированием: CPython кеширует маленькие целые (от -5 до 256) и короткие строки, поэтому у одинаковых литералов id часто совпадает — строить на этом логику нельзя.",
        "en": "An id is unique only while the object is alive; once it is collected the same number can be handed to a new object, so storing an id and comparing it later is unreliable. Its one real use is asking whether two names point to the same object — which a is b expresses more readably — while == compares values. Watch out for interning as well: CPython caches small ints (-5 to 256) and short strings, so identical literals often share an id, and no logic should rest on that."
      },
      "syntax": "id(object)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#id",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "интроспекция",
      "color_group": "builtin",
      "aliases": [
        "идентификатор объекта",
        "адрес объекта в памяти",
        "это один и тот же объект"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "x = 42",
        "print(id(x))  # → большое целое число",
        "y = x",
        "print(id(x) == id(y))  # → True (один объект)",
        "print(id(42) == id(42))  # → True (интернирование)",
        "print(id([]) == id([]))  # → False (разные объекты)",
        "a = 'hello'; b = 'hello'",
        "print(id(a) == id(b))  # → True (интернирование строк)"
      ],
      "related": [
        "is-is-not",
        "hash",
        "строки-в-памяти-интернирование-неизменяе"
      ],
      "related_errors": []
    },
    {
      "id": "issubclass",
      "title": "issubclass",
      "kind": "term",
      "summary": {
        "ru": "Возвращает True, если класс является подклассом указанного класса или одного из классов в кортеже.",
        "en": "Returns True if the class is a subclass of the given class or of one of the classes in the tuple."
      },
      "body": {
        "ru": "Первый аргумент — именно класс, а не экземпляр: передадите объект — получите TypeError, для объектов есть isinstance. Вторым можно дать кортеж классов, тогда True вернётся при наследовании хотя бы от одного из них. И помните про bool: он подкласс int, поэтому проверка на int пропустит True и False там, где ждали только числа.",
        "en": "The first argument must be a class, not an instance — passing an object raises TypeError, and objects are checked with isinstance instead. The second argument may be a tuple of classes, in which case inheriting from any one of them is enough. Watch out for bool: it is a subclass of int, so a check against int also accepts True and False."
      },
      "syntax": "issubclass(class, classinfo)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#issubclass",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "интроспекция/ООП",
      "color_group": "builtin",
      "aliases": [
        "проверка подкласса",
        "является ли класс наследником",
        "проверить наследование класса"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(issubclass(bool, int))   # → True",
        "print(issubclass(int, object))  # → True",
        "print(issubclass(list, (list,tuple)))  # → True",
        "class A: pass",
        "class B(A): pass",
        "print(issubclass(B, A))  # → True",
        "print(issubclass(A, B))  # → False"
      ],
      "related": [
        "isinstance",
        "наследование",
        "type",
        "abc.ABC"
      ],
      "related_errors": []
    },
    {
      "id": "iter",
      "title": "iter",
      "kind": "term",
      "summary": {
        "ru": "Возвращает итератор. С двумя аргументами — вызывает функцию до значения sentinel.",
        "en": "Returns an iterator. With two arguments it calls the function until it returns the sentinel value."
      },
      "body": {
        "ru": "Итератор одноразовый, и iter от уже полученного итератора вернёт его же — второй проход по тому же объекту даст пустоту, нужен новый iter от исходной коллекции. Форма с двумя аргументами вызывает функцию без аргументов снова и снова, пока та не вернёт значение sentinel; сам sentinel в выдачу не попадает. Это удобный способ читать поток до маркера конца, не городя while True с проверкой и break.",
        "en": "An iterator is single-use, and calling iter on an existing iterator gives back the same object — a second pass yields nothing, so build a fresh iterator from the original collection. The two-argument form calls the zero-argument callable over and over until it returns the sentinel, and the sentinel itself is not yielded. That turns a read-until-marker loop into a plain for loop instead of while True with a manual break."
      },
      "syntax": "iter(object)\niter(callable, sentinel)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#iter",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "итераторы",
      "color_group": "builtin",
      "aliases": [
        "получить итератор",
        "создать итератор из последовательности",
        "ручной перебор элементов"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "it = iter([1,2,3])",
        "print(next(it), next(it))  # → 1 2",
        "it2 = iter('hello')",
        "print(list(it2))  # → ['h','e','l','l','o']",
        "# sentinel-вариант",
        "from io import StringIO",
        "f = StringIO('a\\nb\\nc')",
        "for line in iter(f.readline, ''):",
        "    print(line.strip())",
        "    print(list(iter(range(5))))  # → [0,1,2,3,4]"
      ],
      "related": [
        "next",
        "итератор-__iter__-__next__",
        "stopiteration",
        "iter-next"
      ],
      "related_errors": []
    },
    {
      "id": "len",
      "title": "len()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает число элементов в объекте (длину): последовательности, множества, словаря, строки.",
        "en": "Return the number of items in a container (its length)."
      },
      "body": {
        "ru": "len() ничего не пересчитывает: контейнеры хранят свою длину, поэтому вызов работает за O(1) хоть на миллионе элементов. Но он требует наличия __len__ — у генератора и вообще у любого итератора длины нет, и вы получите TypeError; длину потока считают через sum(1 for _ in it) или сначала материализуют его в список.",
        "en": "len() does not count anything: containers keep their size, so the call is O(1) even for a million items. It does require __len__, though — a generator or any other iterator has no length and raises TypeError, so count a stream with sum(1 for _ in it) or materialise it into a list first."
      },
      "syntax": "len(obj)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#len",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "Встроенные функции",
      "color_group": "builtin",
      "aliases": [
        "длина строки",
        "количество элементов",
        "размер списка"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(len([1, 2, 3]))     # → 3",
        "print(len('abcd'))        # → 4",
        "print(len({'a': 1}))      # → 1"
      ],
      "related": [
        "min",
        "max",
        "sum"
      ],
      "related_errors": []
    },
    {
      "id": "list",
      "title": "list",
      "kind": "term",
      "summary": {
        "ru": "Создаёт изменяемый список из итерируемого или пустой список. list() без аргументов эквивалентен [].",
        "en": "Creates a mutable list from an iterable, or an empty list. list() with no arguments is equivalent to []."
      },
      "body": {
        "ru": "list(другой_список) делает поверхностную копию: внешний список новый, а вложенные списки и объекты те же самые, поэтому правка copy[0][0] отзовётся в оригинале. От генератора, map или filter list() забирает все элементы разом, и повторный вызов вернёт уже пустой список — итератор исчерпан. От строки получаются отдельные символы, а не ['hello']: если нужен список из одного элемента, пишите ['hello'].",
        "en": "list(other_list) makes a shallow copy: the outer list is new, but nested lists and objects are the same ones, so editing copy[0][0] shows up in the original. Applied to a generator, map or filter, list() drains it in one go, and a second call returns an empty list because the iterator is exhausted. Applied to a string it yields individual characters, not ['hello'] — for a one-element list write the brackets yourself."
      },
      "syntax": "list(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#func-list",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "типы",
      "color_group": "builtin",
      "aliases": [
        "список",
        "массив в питоне",
        "преобразовать в список"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(list('hello'))  # → ['h','e','l','l','o']",
        "print(list(range(5)))  # → [0,1,2,3,4]",
        "print(list({1,2,3}))  # → [1,2,3]",
        "print(list({'a':1,'b':2}))  # → ['a','b']",
        "print(list(zip([1,2],[3,4])))  # → [(1,3),(2,4)]"
      ],
      "related": [
        "создание-списка",
        "tuple",
        "list.append",
        "списочные-выражения-list-comprehension"
      ],
      "related_errors": []
    },
    {
      "id": "locals",
      "title": "locals",
      "kind": "term",
      "summary": {
        "ru": "Возвращает словарь текущего локального пространства имён. В модуле совпадает с globals(). Копия, не ссылка.",
        "en": "Returns a dictionary of the current local namespace. At module level it matches globals(). A copy, not a reference."
      },
      "body": {
        "ru": "Внутри функции это снимок: записать что-то в полученный словарь и ждать появления новой локальной переменной бесполезно — обратной связи с кадром нет (в Python 3.13 это закреплено PEP 667, раньше поведение зависело от реализации). На уровне модуля и в теле класса locals() отдаёт настоящий словарь пространства имён, и правки в нём как раз видны — отсюда путаница. В функции попадают только те имена, которым уже что-то присвоено к моменту вызова.",
        "en": "Inside a function the result is a snapshot: writing into that dict will not create or change a local variable, because there is no link back to the frame (Python 3.13 guarantees this via PEP 667; before that it was implementation-defined). At module level and in a class body locals() hands you the real namespace mapping, where writes do take effect — which is the usual source of confusion. Only names already assigned at the moment of the call show up inside a function."
      },
      "syntax": "locals()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#locals",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "пространства имён",
      "color_group": "builtin",
      "aliases": [
        "локальные переменные функции",
        "словарь локальных имён",
        "какие переменные видны внутри функции"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "def f():",
        "x = 10; y = 20",
        "return locals()",
        "print(f())  # → {'x':10,'y':20}",
        "def g(a, b):",
        "c = a+b",
        "print('c' in locals())  # → True",
        "return locals()",
        "print(g(1,2))  # → {'a':1,'b':2,'c':3}",
        "print(type(locals()))  # → dict"
      ],
      "related": [
        "globals",
        "vars",
        "локальные-и-глобальные-переменные",
        "global-nonlocal"
      ],
      "related_errors": []
    },
    {
      "id": "max",
      "title": "max",
      "kind": "term",
      "summary": {
        "ru": "Возвращает наибольший элемент итерируемого или наибольший из переданных аргументов. Поддерживает key-функцию.",
        "en": "Returns the largest item of an iterable, or the largest of the arguments given. Supports a key function."
      },
      "body": {
        "ru": "На пустом итерируемом max падает с ValueError — если пустота возможна, передайте default. Аргумент key меняет только критерий сравнения, а возвращается сам элемент, а не значение key: именно так по словарю получают ключ с наибольшим значением, а не само значение. Если максимум достигается на нескольких элементах, вернётся первый встреченный.",
        "en": "max raises ValueError on an empty iterable, so pass default whenever the input may be empty. key only changes what is compared — you still get the element back, not the key value, which is how you obtain the dict key with the largest value rather than the value itself. When several items tie for largest, the first one encountered wins."
      },
      "syntax": "max(iterable, *, key=None)\nmax(a, b, ...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#max",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "числа/коллекции",
      "color_group": "builtin",
      "aliases": [
        "максимум",
        "наибольшее значение",
        "найти самое большое число"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(max(3,1,4,1,5))  # → 5",
        "print(max([10,20,5]))  # → 20",
        "print(max('apple','banana','cherry'))  # → cherry",
        "print(max([1,2,3], key=lambda x:-x))  # → 1",
        "print(max([], default=0))  # → 0"
      ],
      "related": [
        "min",
        "sum",
        "len"
      ],
      "related_errors": []
    },
    {
      "id": "memoryview",
      "title": "memoryview",
      "kind": "term",
      "summary": {
        "ru": "Создаёт объект представления буфера без копирования данных. Позволяет работать со срезами бинарных данных эффективно.",
        "en": "Creates a view into an object's buffer without copying the data. Lets you work with slices of binary data efficiently."
      },
      "body": {
        "ru": "memoryview работает только с объектами, поддерживающими буферный протокол — bytes, bytearray, array.array; на str или список будет TypeError. Писать через представление можно лишь тогда, когда исходный объект изменяемый: у memoryview(b'...') присваивание запрещено, потому что bytes только для чтения. Срез представления — снова memoryview, а не bytes (отсюда обёртка bytes(...)), и пока представление живо, bytearray нельзя менять в размере: append или расширение упадут с BufferError.",
        "en": "memoryview only accepts objects that support the buffer protocol — bytes, bytearray, array.array; a str or a list raises TypeError. Writing through the view works only if the underlying object is mutable: assigning into memoryview(b'...') fails because bytes is read-only. Slicing a view gives another memoryview rather than bytes (hence the bytes(...) wrapper), and while a view is alive the bytearray cannot be resized — append or extend raise BufferError."
      },
      "syntax": "memoryview(object)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#func-memoryview",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "типы",
      "color_group": "builtin",
      "aliases": [
        "работа с байтами без копирования",
        "срез бинарных данных без копирования",
        "представление памяти"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "b = bytearray(b'hello world')",
        "mv = memoryview(b)",
        "print(bytes(mv[6:11]))  # → b'world'",
        "mv[0] = ord('H')",
        "print(b)  # → bytearray(b'Hello world')",
        "print(mv.format)  # → B (unsigned char)",
        "print(mv.itemsize)  # → 1",
        "print(len(mv))  # → 11"
      ],
      "related": [
        "bytearray",
        "bytes",
        "buffererror",
        "collections.abc.Buffer"
      ],
      "related_errors": []
    },
    {
      "id": "min",
      "title": "min",
      "kind": "term",
      "summary": {
        "ru": "Возвращает наименьший элемент итерируемого или наименьший из переданных аргументов. Поддерживает key-функцию.",
        "en": "Returns the smallest item of an iterable, or the smallest of the arguments given. Supports a key function."
      },
      "body": {
        "ru": "min проходит последовательность ровно один раз за O(n), поэтому для одного крайнего элемента он дешевле, чем sorted(...)[0]. Разнотипные значения не сравниваются «как-нибудь»: число со строкой даст TypeError, а строки сравниваются посимвольно по кодовым точкам, из-за чего '10' оказывается меньше '9'.",
        "en": "min makes a single O(n) pass, so it beats sorted(...)[0] when you only need one extreme element. Mixed types are not silently ordered: comparing a number with a string raises TypeError, and strings are compared code point by code point, which is why '10' comes out smaller than '9'."
      },
      "syntax": "min(iterable, *, key=None)\nmin(a, b, ...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#min",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "числа/коллекции",
      "color_group": "builtin",
      "aliases": [
        "минимум",
        "наименьшее значение",
        "найти самое маленькое число"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(min(3,1,4,1,5))  # → 1",
        "print(min([10,20,5]))  # → 5",
        "print(min('apple','banana','cherry'))  # → apple",
        "print(min([1,2,3], key=lambda x:-x))  # → 3",
        "print(min([], default=0))  # → 0"
      ],
      "related": [
        "max",
        "sum",
        "len"
      ],
      "related_errors": []
    },
    {
      "id": "next",
      "title": "next",
      "kind": "term",
      "summary": {
        "ru": "Возвращает следующий элемент итератора. default — если итератор исчерпан.",
        "en": "Returns the next item of an iterator. default is returned if the iterator is exhausted."
      },
      "body": {
        "ru": "next принимает итератор, а не коллекцию: next([1, 2, 3]) — это TypeError, сначала нужен iter(). Без второго аргумента исчерпанный итератор поднимает StopIteration, поэтому либо ловите её, либо передавайте default. Внутри генератора это особенно важно: незамеченная StopIteration там не всплывает наружу, а превращается в RuntimeError (PEP 479), и источник ошибки становится неочевидным.",
        "en": "next takes an iterator, not a container: next([1, 2, 3]) is a TypeError — wrap the list in iter() first. Without the second argument an exhausted iterator raises StopIteration, so either catch it or pass a default. Inside a generator this matters even more: an unhandled StopIteration is converted into RuntimeError (PEP 479), which hides where the problem actually came from."
      },
      "syntax": "next(iterator[, default])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#next",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "итераторы",
      "color_group": "builtin",
      "aliases": [
        "следующий элемент итератора",
        "взять следующее значение генератора",
        "первый подходящий элемент"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "it = iter([1,2,3])",
        "print(next(it))  # → 1",
        "print(next(it))  # → 2",
        "print(next(it))  # → 3",
        "print(next(it, 'end'))  # → end",
        "g = (x**2 for x in range(5))",
        "print(next(g), next(g))  # → 0 1"
      ],
      "related": [
        "iter",
        "stopiteration",
        "anext"
      ],
      "related_errors": []
    },
    {
      "id": "object",
      "title": "object",
      "kind": "term",
      "summary": {
        "ru": "Базовый класс всех классов Python. Предоставляет методы по умолчанию: __str__, __repr__, __eq__, __hash__ и другие магические методы.",
        "en": "The base class of every Python class. Provides the default __str__, __repr__, __eq__, __hash__ and other magic methods."
      },
      "body": {
        "ru": "Голый object() — не универсальный контейнер для полей: у него нет __dict__, поэтому o.foo = 1 сразу падает с AttributeError; зато он идеален как уникальный маркер-заглушка, когда None — допустимое значение. Унаследованные по умолчанию __eq__ и __hash__ смотрят на тождество объекта: два одинаковых по смыслу экземпляра не равны, пока вы не определите __eq__ — а как только определите, класс перестаёт быть хешируемым, если не задать и __hash__.",
        "en": "A bare object() is not a general-purpose bag of fields: it has no __dict__, so o.foo = 1 fails with AttributeError; it is, however, the perfect unique sentinel when None is a legitimate value. The inherited __eq__ and __hash__ compare identity, so two semantically equal instances are unequal until you define __eq__ — and the moment you do, the class stops being hashable unless you define __hash__ as well."
      },
      "syntax": "object()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#object",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "ООП",
      "color_group": "builtin",
      "aliases": [
        "базовый класс всех объектов",
        "родитель всех классов",
        "корень иерархии классов"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "o = object()",
        "print(type(o))  # → <class 'object'>",
        "print(isinstance(42, object))  # → True",
        "print(isinstance('hi', object))  # → True",
        "class A(object): pass  # явно наследуется",
        "print(A.__bases__)  # → (<class 'object'>,)",
        "print(dir(object())[:2])  # → ['__class__', '__delattr__']"
      ],
      "related": [
        "class",
        "наследование",
        "type",
        "__str__-__repr__"
      ],
      "related_errors": []
    },
    {
      "id": "oct",
      "title": "oct",
      "kind": "term",
      "summary": {
        "ru": "Преобразует целое число в строку восьмеричного представления с префиксом '0o'.",
        "en": "Converts an integer to its octal string representation with the '0o' prefix."
      },
      "body": {
        "ru": "В Python 3 восьмеричный литерал пишется только с префиксом 0o — привычная по старым примерам запись 0777 это SyntaxError. Типичная ошибка с правами доступа: os.chmod ждёт целое число 0o755, а не строку, которую вернул oct(); чтобы получить число из строки, нужен int(s, 8).",
        "en": "In Python 3 an octal literal must carry the 0o prefix — the old 0777 form seen in legacy examples is a SyntaxError. A common slip with file permissions: os.chmod expects the integer 0o755, not the string that oct() produced; use int(s, 8) to turn such a string back into a number."
      },
      "syntax": "oct(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#oct",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "числа",
      "color_group": "builtin",
      "aliases": [
        "перевести число в восьмеричную систему",
        "восьмеричное представление числа"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(oct(8))    # → 0o10",
        "print(oct(255))  # → 0o377",
        "print(oct(64))   # → 0o100",
        "print(oct(-8))   # → -0o10",
        "print(int(oct(8),8))  # → 8"
      ],
      "related": [
        "bin",
        "hex",
        "системы-счисления",
        "int"
      ],
      "related_errors": []
    },
    {
      "id": "ord",
      "title": "ord",
      "kind": "term",
      "summary": {
        "ru": "Возвращает Unicode кодовую точку (целое число) символа. Принимает строку длиной 1. Обратная функция для chr().",
        "en": "Returns the Unicode code point (an integer) of a character. Takes a string of length 1. The inverse of chr()."
      },
      "body": {
        "ru": "Строки сравниваются и сортируются как раз по этим кодам, поэтому 'Z' < 'a' (все заглавные латинские идут раньше строчных), а кириллица больше любой латиницы — вот откуда «странный» порядок в sorted(). Аргумент — ровно один символ: ord('ab') и ord('') дают TypeError, так что перебирать строку надо посимвольно.",
        "en": "String comparison and sorting run on exactly these numbers, which is why 'Z' < 'a' (every uppercase Latin letter precedes every lowercase one) and why Cyrillic sorts after all Latin — the source of most \"weird\" sorted() results. The argument must be exactly one character: ord('ab') and ord('') both raise TypeError, so iterate over the string character by character."
      },
      "syntax": "ord(c)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#ord",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "строки",
      "color_group": "builtin",
      "aliases": [
        "код символа",
        "номер символа в юникоде",
        "преобразовать букву в число"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(ord('A'))   # → 65",
        "print(ord('a'))   # → 97",
        "print(ord('а'))   # → 1072 (кириллица)",
        "print(ord('0'))   # → 48",
        "print([ord(c) for c in 'ABC'])  # → [65,66,67]"
      ],
      "related": [
        "chr",
        "ascii",
        "str.isascii"
      ],
      "related_errors": []
    },
    {
      "id": "repr",
      "title": "repr",
      "kind": "term",
      "summary": {
        "ru": "Возвращает строку с официальным представлением объекта — такую, по которой можно воссоздать объект (по возможности).",
        "en": "Returns the official string representation of an object — one the object can be recreated from where possible."
      },
      "body": {
        "ru": "repr пригождается, когда print врёт: он показывает кавычки и экранированные символы, поэтому сразу видно, где '1' — строка, а где число, и есть ли на конце лишний пробел или перевод строки. Внутри контейнеров Python всегда зовёт repr элементов — вот почему print(['a']) печатает с кавычками, а print('a') нет. У собственных классов repr по умолчанию выдаёт бесполезное <Foo object at 0x...>, пока не определён метод __repr__.",
        "en": "Reach for repr when print is hiding something: it shows quotes and escape sequences, so you can tell the string '1' from the number 1 and spot a stray trailing space or newline. Inside containers Python always calls repr on the elements — that is why print(['a']) shows quotes while print('a') does not. For your own classes repr falls back to the useless <Foo object at 0x...> until you define __repr__."
      },
      "syntax": "repr(object)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#repr",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "строки",
      "color_group": "builtin",
      "aliases": [
        "строковое представление объекта",
        "отладочный вывод объекта",
        "показать строку с кавычками"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(repr('hello'))  # → \"'hello'\"",
        "print(repr([1,2,3]))  # → '[1, 2, 3]'",
        "print(repr(None))     # → 'None'",
        "print(repr(3.14))     # → '3.14'",
        "print(repr({'a':1}))  # → \"{'a': 1}\""
      ],
      "related": [
        "repr-vs-str",
        "__str__-__repr__",
        "str",
        "ascii"
      ],
      "related_errors": []
    },
    {
      "id": "reversed",
      "title": "reversed",
      "kind": "term",
      "summary": {
        "ru": "Возвращает обратный итератор для последовательности. Не копирует данные — ленивое вычисление.",
        "en": "Returns a reverse iterator over a sequence. Does not copy the data — evaluated lazily."
      },
      "body": {
        "ru": "Подходит не любому итерируемому: нужна последовательность — с методом __reversed__ либо с парой __len__ и __getitem__. Множества и генераторы дадут TypeError, а словарь (начиная с 3.8) обходится по ключам в обратном порядке вставки. От среза [::-1] отличается тем, что не создаёт копию: правки исходного списка по ходу обхода итератор увидит.",
        "en": "Not every iterable qualifies: reversed needs a sequence — one that defines __reversed__, or both __len__ and __getitem__. Sets and generators raise TypeError, while dicts (since 3.8) walk their keys in reverse insertion order. Unlike the [::-1] slice it makes no copy, so mutating the underlying list during iteration is visible to the iterator."
      },
      "syntax": "reversed(sequence)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#reversed",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "итераторы",
      "color_group": "builtin",
      "aliases": [
        "перебор в обратном порядке",
        "пройти список с конца",
        "обратный обход последовательности"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(list(reversed([1,2,3,4,5])))  # → [5,4,3,2,1]",
        "print(list(reversed('hello')))  # → ['o','l','l','e','h']",
        "print(list(reversed(range(5))))  # → [4,3,2,1,0]",
        "for x in reversed([1,2,3]):",
        "    print(x, end=' ')  # → 3 2 1",
        "    print(''.join(reversed('hello')))  # → olleh"
      ],
      "related": [
        "zip",
        "enumerate"
      ],
      "related_errors": []
    },
    {
      "id": "set",
      "title": "set",
      "kind": "term",
      "summary": {
        "ru": "Изменяемое множество уникальных хешируемых элементов без определённого порядка. Поддерживает операции объединения, пересечения и разности.",
        "en": "A mutable set of unique hashable items with no defined order. Supports union, intersection and difference operations."
      },
      "body": {
        "ru": "Пустое множество создаётся только через set() — фигурные скобки без содержимого дают пустой словарь. Внутрь кладутся лишь хешируемые объекты: список вызовет TypeError, его надо превратить в кортеж или frozenset. Порядок обхода не определён и не связан с порядком добавления, поэтому для стабильного вывода печатайте sorted(s), а не само множество.",
        "en": "An empty set can only be built with set(); empty curly braces give you a dict instead. Only hashable objects fit inside — a list raises TypeError, so convert it to a tuple or frozenset first. Iteration order is arbitrary and unrelated to insertion order, so print sorted(s) when the output has to be reproducible."
      },
      "syntax": "set(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#func-set",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "типы",
      "color_group": "builtin",
      "aliases": [
        "множество",
        "уникальные элементы",
        "убрать дубликаты из списка"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(set([1,2,2,3]))  # → {1,2,3}",
        "s1={1,2,3}; s2={2,3,4}",
        "print(s1|s2)  # → {1,2,3,4} объединение",
        "print(s1&s2)  # → {2,3} пересечение",
        "print(s1-s2)  # → {1} разность",
        "print(s1^s2)  # → {1,4} симм.разность"
      ],
      "related": [
        "создание-множества",
        "операции-над-множествами",
        "frozenset",
        "set.add"
      ],
      "related_errors": []
    },
    {
      "id": "setattr",
      "title": "setattr",
      "kind": "term",
      "summary": {
        "ru": "Устанавливает атрибут объекта по имени. Эквивалент obj.name = value, но позволяет задать имя атрибута динамически как строку.",
        "en": "Sets an attribute of an object by name. Equivalent to obj.name = value, but lets you give the attribute name dynamically as a string."
      },
      "body": {
        "ru": "Брать setattr стоит только когда имя атрибута реально вычисляется в рантайме (пришло из конфига, цикла, пользовательского ввода); если имя известно заранее, обычное obj.x = value читается лучше и опечатку в нём заметит IDE, а в строке — никто. Имя не проверяется на то, что оно валидный идентификатор: setattr(obj, 'my attr', 1) спокойно создаст запись в __dict__, до которой потом не добраться иначе как через getattr. На объектах без __dict__ — со __slots__ или встроенных типах вроде int и str — попытка упирается в AttributeError.",
        "en": "Reach for setattr only when the attribute name is genuinely computed at runtime (from a config, a loop, user input); when you know the name up front, plain obj.x = value reads better and a typo in it gets flagged, while a typo inside a string does not. The name is not checked for being a valid identifier: setattr(obj, 'my attr', 1) happily lands in __dict__ and can then only be read back through getattr. Objects without a __dict__ — those with __slots__, or built-in types like int and str — raise AttributeError instead."
      },
      "syntax": "setattr(object, name, value)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#setattr",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "ООП",
      "color_group": "builtin",
      "aliases": [
        "установить атрибут по имени",
        "задать поле объекта динамически"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "class A: pass",
        "a=A()",
        "setattr(a,'x',42)",
        "print(a.x)  # → 42",
        "for name,val in [('p',1),('q',2)]:",
        "setattr(a,name,val)",
        "print(a.p, a.q)  # → 1 2",
        "setattr(a,'lst',[])",
        "a.lst.append(1)",
        "print(a.lst)  # → [1]"
      ],
      "related": [
        "getattr",
        "delattr",
        "hasattr",
        "vars"
      ],
      "related_errors": []
    },
    {
      "id": "sorted",
      "title": "sorted",
      "kind": "term",
      "summary": {
        "ru": "Возвращает новый отсортированный список. Принимает key и reverse. Не изменяет исходную последовательность.",
        "en": "Returns a new sorted list. Accepts key and reverse. Does not modify the original sequence."
      },
      "body": {
        "ru": "Сортировка устойчивая: элементы с одинаковым ключом сохраняют исходный взаимный порядок — на этом строят сортировку по нескольким критериям, делая проходы от менее важного к более важному. key вычисляется ровно один раз на элемент, так что даже дорогая функция там не страшна. В отличие от list.sort(), который меняет список на месте и возвращает None, sorted берёт любой итерируемый объект и всегда отдаёт новый список.",
        "en": "The sort is stable: items with equal keys keep their relative order, which is what makes multi-key sorting work when you sort by the least significant key first. The key function is evaluated exactly once per element, so an expensive key is not a performance trap. Unlike list.sort(), which reorders in place and returns None, sorted accepts any iterable and always hands back a new list."
      },
      "syntax": "sorted(iterable, key=None, reverse=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#sorted",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "итераторы",
      "color_group": "builtin",
      "aliases": [
        "сортировка",
        "отсортировать список",
        "упорядочить по возрастанию"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(sorted([3,1,4,1,5]))  # → [1,1,3,4,5]",
        "print(sorted('hello'))  # → ['e','h','l','l','o']",
        "print(sorted([3,1,2],reverse=True))  # → [3,2,1]",
        "print(sorted(['b','aaa','cc'],key=len))  # → ['b','cc','aaa']",
        "print(sorted({'c':3,'a':1,'b':2}))  # → ['a','b','c']"
      ],
      "related": [
        "list.sort",
        "sorted-с-key",
        "сортировка-ключом-key-lambda",
        "reversed"
      ],
      "related_errors": []
    },
    {
      "id": "sum",
      "title": "sum",
      "kind": "term",
      "summary": {
        "ru": "Суммирует элементы итерируемого. start — начальное значение.",
        "en": "Sums the items of an iterable. start is the initial value."
      },
      "body": {
        "ru": "Строки sum складывать отказывается — будет TypeError с подсказкой использовать ''.join(). Склейка списков через sum(списки, []) формально работает, но на каждом шаге создаёт новый список и растёт квадратично; для длинных данных берите itertools.chain.",
        "en": "sum refuses to concatenate strings — it raises TypeError and points you at ''.join() instead. Flattening lists with sum(lists, []) does work, but it builds a fresh list at every step and scales quadratically, so reach for itertools.chain on anything long."
      },
      "syntax": "sum(iterable, start=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#sum",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "числа/коллекции",
      "color_group": "builtin",
      "aliases": [
        "сумма элементов",
        "суммировать список",
        "посчитать сумму"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(sum([1,2,3,4,5]))  # → 15",
        "print(sum(range(101)))  # → 5050",
        "print(sum([[1,2],[3,4]], []))  # → [1,2,3,4]",
        "print(sum([0.1]*10))  # → 1.0 (примерно)",
        "print(sum({'a':1,'b':2}.values()))  # → 3"
      ],
      "related": [
        "min",
        "max",
        "len"
      ],
      "related_errors": []
    },
    {
      "id": "tuple",
      "title": "tuple",
      "kind": "term",
      "summary": {
        "ru": "Неизменяемая упорядоченная последовательность элементов. Хешируема (если элементы хешируемы) — можно использовать как ключ словаря.",
        "en": "An immutable ordered sequence of items. Hashable (if its items are) — it can be used as a dictionary key."
      },
      "body": {
        "ru": "Кортеж делает запятая, а не скобки: (1) — обычное число, кортеж из одного элемента пишется (1,). Неизменяемость поверхностная — если внутри лежит список, его содержимое менять можно, и такой кортеж перестаёт быть хешируемым: попытка использовать его ключом словаря даст TypeError.",
        "en": "It is the comma, not the parentheses, that makes a tuple: (1) is just a number, while a one-element tuple is written (1,). Immutability is shallow — a list stored inside can still be mutated, and such a tuple is no longer hashable, so using it as a dict key raises TypeError."
      },
      "syntax": "tuple(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#func-tuple",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "типы",
      "color_group": "builtin",
      "aliases": [
        "кортеж",
        "неизменяемый список",
        "преобразовать в кортеж"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "print(tuple('abc'))    # → ('a','b','c')",
        "print(tuple([1,2,3]))  # → (1,2,3)",
        "t = (1,2,3)",
        "a,b,c = t",
        "print(a,b,c)  # → 1 2 3",
        "print(tuple(zip([1,2],[3,4])))  # → ((1,3),(2,4))"
      ],
      "related": [
        "создание-кортежа",
        "list",
        "неизменяемость-кортежа",
        "namedtuple"
      ],
      "related_errors": []
    },
    {
      "id": "vars",
      "title": "vars",
      "kind": "term",
      "summary": {
        "ru": "Возвращает __dict__ объекта или словарь текущего локального пространства имён (без аргументов — аналог locals()).",
        "en": "Returns the __dict__ of an object, or a dictionary of the current local namespace (with no argument — the same as locals())."
      },
      "body": {
        "ru": "vars(экземпляра) показывает только его собственные атрибуты — атрибуты и методы класса туда не попадут, они лежат в vars(типа). Возвращается не копия, а живой __dict__: правка этого словаря действительно меняет объект. А если у класса объявлены __slots__ (или это встроенный тип вроде int), никакого __dict__ нет и vars бросит TypeError.",
        "en": "vars(instance) shows only the instance's own attributes — class attributes and methods are not there, they live in vars(type). What you get is the live __dict__ rather than a copy, so mutating it genuinely changes the object. And a class declaring __slots__ (or a built-in type such as int) has no __dict__ at all, so vars raises TypeError."
      },
      "syntax": "vars([object])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#vars",
      "version": "",
      "section": "Встроенные функции",
      "subcat": "интроспекция",
      "color_group": "builtin",
      "aliases": [
        "словарь атрибутов объекта",
        "получить поля объекта как словарь"
      ],
      "keywords": [],
      "tags": [
        "builtin"
      ],
      "examples": [
        "class A:",
        "def __init__(self): self.x=1; self.y=2",
        "a=A()",
        "print(vars(a))  # → {'x':1,'y':2}",
        "print('x' in vars(a))  # → True",
        "print(vars(int))['__add__']  # → метод int.__add__",
        "print(type(vars()))  # → dict",
        "print('print' in vars(__builtins__) if isinstance(vars(__builtins__),dict) else True)"
      ],
      "related": [
        "dir",
        "locals",
        "getattr"
      ],
      "related_errors": []
    },
    {
      "id": "_incompleteinputerror",
      "title": "_IncompleteInputError",
      "kind": "exception",
      "summary": {
        "ru": "Внутреннее: код синтаксически неполон (используется REPL для многострочного ввода).",
        "en": "Internal: source is syntactically incomplete (used by the REPL for multi-line input)."
      },
      "body": {
        "ru": "Подчёркивание в начале имени — знак, что это внутренняя кухня CPython: класс нужен интерпретатору, чтобы отличить «код просто оборвался на середине» от настоящей синтаксической ошибки и дорисовать приглашение продолжения вместо сообщения об ошибке. Это подкласс SyntaxError, так что обычный except SyntaxError его и так поймает. В своём коде на это имя опираться не стоит: приватные имена появляются и исчезают между версиями без предупреждения.",
        "en": "The leading underscore marks it as CPython plumbing: the interpreter uses it to tell \"the source simply ran out mid-construct\" apart from a real syntax error, so the interactive prompt asks for a continuation line instead of reporting a failure. It is a subclass of SyntaxError, so a plain except SyntaxError already covers it. Do not reference the name in your own code — private names come and go between versions without notice."
      },
      "syntax": "raise _IncompleteInputError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#SyntaxError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise _IncompleteInputError",
        "except _IncompleteInputError as e:",
        "    print(type(e).__name__)   # → _IncompleteInputError"
      ],
      "related": [
        "syntaxerror",
        "compile",
        "eoferror"
      ],
      "related_errors": []
    },
    {
      "id": "argparse.argumenterror",
      "title": "argparse.ArgumentError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка значения аргумента командной строки при разборе (argparse).",
        "en": "An error from creating or using an argument (optional or positional)"
      },
      "body": {
        "ru": "Своими руками её поднимают редко — её кидает сам argparse, например когда две опции претендуют на один флаг или add_argument получил несовместимые параметры. Внутри parse_args она перехватывается и превращается в сообщение об ошибке плюс выход с кодом 2, так что до вашего try обычно не долетает; чтобы вмешаться, наследуйте ArgumentParser и переопределите метод error(). Конструктор ждёт два аргумента: объект действия (или None) и текст сообщения.",
        "en": "You rarely raise it yourself — argparse raises it, for example when two options claim the same flag or add_argument gets contradictory parameters. Inside parse_args it is caught and turned into an error message plus exit code 2, so your try block usually never sees it; to intercept it, subclass ArgumentParser and override error(). The constructor expects two arguments: the offending action (or None) and the message."
      },
      "syntax": "raise argparse.ArgumentError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/argparse.html#argparse.ArgumentError",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка аргумента командной строки",
        "неверный аргумент командной строки"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import argparse",
        "try:",
        "    raise argparse.ArgumentError(None, 'плохой аргумент')",
        "except argparse.ArgumentError as e:",
        "    print(type(e).__name__)   # → ArgumentError"
      ],
      "related": [
        "argparse.argumenttypeerror",
        "valueerror",
        "sys.argv"
      ],
      "related_errors": []
    },
    {
      "id": "argparse.argumenttypeerror",
      "title": "argparse.ArgumentTypeError",
      "kind": "exception",
      "summary": {
        "ru": "type-функция аргумента argparse отвергла значение.",
        "en": "An error from trying to convert a command line string to a type"
      },
      "body": {
        "ru": "Поднимайте её внутри своей функции type=, чтобы argparse напечатал ваш текст как обычную ошибку разбора и вышел с кодом 2, а не вывалил трейсбек. ValueError и TypeError из type-функции argparse тоже перехватывает, но подменяет сообщение шаблонным «invalid ... value»; любое другое исключение проходит насквозь и роняет программу.",
        "en": "Raise it from your own type= callable so argparse prints your wording as an ordinary parse error and exits with code 2 instead of dumping a traceback. ValueError and TypeError from a type callable are caught as well, but argparse replaces your text with the generic \"invalid ... value\" message; anything else propagates and crashes the program."
      },
      "syntax": "raise argparse.ArgumentTypeError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/argparse.html#argparse.ArgumentTypeError",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "неверный тип аргумента командной строки",
        "ошибка преобразования аргумента командной строки"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import argparse",
        "try:",
        "    raise argparse.ArgumentTypeError",
        "except argparse.ArgumentTypeError as e:",
        "    print(type(e).__name__)   # → ArgumentTypeError"
      ],
      "related": [
        "argparse.argumenterror",
        "valueerror",
        "sys.argv"
      ],
      "related_errors": []
    },
    {
      "id": "arithmeticerror",
      "title": "ArithmeticError",
      "kind": "exception",
      "summary": {
        "ru": "Базовый класс арифметических ошибок: ZeroDivisionError, OverflowError, FloatingPointError.",
        "en": "Base class for arithmetic errors: ZeroDivisionError, OverflowError, FloatingPointError."
      },
      "body": {
        "ru": "Ловить базовый класс целиком почти никогда не нужно: на практике из этой ветки прилетает только ZeroDivisionError, его и указывай в except. Соседи обманчивы: переполнение в обычной арифметике float даёт не ошибку, а inf (1e308 * 10), OverflowError выпадает лишь в узких местах вроде возведения float в большую степень и функций math, а FloatingPointError сам CPython не возбуждает вовсе.",
        "en": "Catching the base class is rarely what you want — in practice only ZeroDivisionError ever surfaces here, so name it directly in your except. Its siblings are misleading: ordinary float arithmetic that goes too far yields inf rather than an error (1e308 * 10), OverflowError shows up only in narrow spots such as float exponentiation and math functions, and FloatingPointError is never raised by CPython itself."
      },
      "syntax": "raise ArithmeticError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ArithmeticError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "арифметическая ошибка",
        "ошибка при вычислениях"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise ArithmeticError",
        "except ArithmeticError as e:",
        "    print(type(e).__name__)   # → ArithmeticError"
      ],
      "related": [
        "zerodivisionerror",
        "overflowerror",
        "floatingpointerror",
        "иерархия-исключений"
      ],
      "related_errors": []
    },
    {
      "id": "assert",
      "title": "assert",
      "kind": "exception",
      "summary": {
        "ru": "Утверждение — проверяет условие и вызывает AssertionError при False. Используется для отладки и тестов. Отключается с -O.",
        "en": "An assertion — checks a condition and raises AssertionError when it is False. Used for debugging and tests. Disabled with -O."
      },
      "body": {
        "ru": "Главная ловушка — лишние скобки: assert (x > 0, 'сообщение') проверяет непустой кортеж, а он всегда истинен, так что такая проверка не сработает никогда (интерпретатор лишь предупредит). И раз под python -O все assert вырезаются, ими нельзя проверять пользовательский ввод, права или данные извне — там нужен обычный if с raise; assert описывает инварианты, которые не могут нарушиться, если код верен.",
        "en": "The classic trap is an extra pair of parentheses: assert (x > 0, 'message') tests a non-empty tuple, which is always truthy, so the check silently never fires (you only get a syntax warning). And because python -O strips assertions entirely, they must not guard user input, permissions or anything coming from outside — use a plain if with raise for that; assert is for invariants that cannot break unless the code itself is wrong."
      },
      "syntax": "assert condition, 'message'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/simple_stmts.html#assert",
      "version": "",
      "section": "Исключения",
      "subcat": "проверка",
      "color_group": "exc",
      "aliases": [
        "утверждение в коде",
        "проверка условия в тестах",
        "отладочная проверка"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "def divide(a, b):",
        "    assert b != 0, 'Denominator must not be zero'",
        "    return a / b",
        "print(divide(10, 2))  # → 5.0",
        "try:",
        "    divide(1, 0)",
        "except AssertionError as e:",
        "    print(e)  # → Denominator must not be zero",
        "def get_positive(n):",
        "    assert isinstance(n, int) and n > 0, f'Expected positive int, got {n!r}'",
        "    return n",
        "print(get_positive(5))  # → 5",
        "def test_add():",
        "    assert 1 + 1 == 2",
        "    assert 'hello'.upper() == 'HELLO'",
        "    assert len([1,2,3]) == 3",
        "    test_add()  # → OK",
        "# assert с коллекцией",
        "def first(lst):",
        "    assert len(lst) > 0, 'List is empty'",
        "    return lst[0]",
        "print(first([10,20]))  # → 10",
        "# ВАЖНО: assert отключается при python -O",
        "# Не используй для валидации входных данных в продакшн!",
        "x = 10",
        "assert x > 0  # только для разработки"
      ],
      "related": [
        "assertionerror",
        "raise",
        "unittest.assertRaises"
      ],
      "related_errors": []
    },
    {
      "id": "assertionerror",
      "title": "AssertionError",
      "kind": "exception",
      "summary": {
        "ru": "Условие оператора assert оказалось ложным: assert x > 0.",
        "en": "An assert statement's condition was false."
      },
      "body": {
        "ru": "assert — проверка собственных допущений, а не валидация пользовательского ввода: при запуске интерпретатора с флагом -O все assert выбрасываются из байткода, и проверка бесследно исчезает. Классическая ловушка — assert (x > 0, 'сообщение'): скобки превращают аргументы в непустой кортеж, который всегда истинен, поэтому такой assert не сработает никогда. Правильно писать без скобок: assert x > 0, 'сообщение'.",
        "en": "assert is for checking your own assumptions, not for validating user input: running Python with -O strips every assert out of the bytecode and the check silently vanishes. The classic trap is assert (x > 0, 'message') — the parentheses build a non-empty tuple, which is always truthy, so the assertion can never fail. Drop them: assert x > 0, 'message'."
      },
      "syntax": "raise AssertionError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#AssertionError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "проверка не прошла",
        "ошибка утверждения",
        "условие оказалось ложным"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    assert 1 == 2",
        "except AssertionError as e:",
        "    print(type(e).__name__)   # → AssertionError"
      ],
      "related": [
        "assert",
        "unittest.assertEqual",
        "raise"
      ],
      "related_errors": []
    },
    {
      "id": "attributeerror",
      "title": "AttributeError",
      "kind": "exception",
      "summary": {
        "ru": "Вызывается при обращении к несуществующему атрибуту или методу объекта.",
        "en": "Raised when an attribute or method that the object does not have is accessed."
      },
      "body": {
        "ru": "Большая часть студенческих AttributeError — это на самом деле None: методы, меняющие объект на месте (list.sort(), list.append(), random.shuffle()), возвращают None, и после строки lst = lst.sort() следующее же обращение падает с «'NoneType' object has no attribute ...». Сообщение стоит дочитывать до конца: в нём назван и тип объекта, и имя атрибута, а начиная с 3.10 интерпретатор ещё и подсказывает похожее имя, если это была опечатка.",
        "en": "Most student AttributeErrors are really None in disguise: in-place methods such as list.sort(), list.append() and random.shuffle() return None, so after lst = lst.sort() the very next call dies with \"'NoneType' object has no attribute ...\". Read the message to the end — it names both the object's type and the attribute, and since 3.10 the interpreter also suggests a close match when you merely mistyped."
      },
      "syntax": "raise AttributeError('сообщение')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#AttributeError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "нет такого атрибута",
        "нет такого метода",
        "объект не имеет атрибута"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "x = 42",
        "x.upper()            # AttributeError: 'int' has no 'upper'",
        "class A: pass",
        "A().run()            # AttributeError"
      ],
      "related": [
        "getattr",
        "hasattr",
        "setattr",
        "nameerror"
      ],
      "related_errors": []
    },
    {
      "id": "baseexception",
      "title": "BaseException",
      "kind": "exception",
      "summary": {
        "ru": "Корень всей иерархии исключений; ловит абсолютно всё, включая SystemExit/KeyboardInterrupt. В прикладном коде почти никогда не ловят напрямую.",
        "en": "The root of the exception hierarchy; catches everything, including SystemExit/KeyboardInterrupt."
      },
      "body": {
        "ru": "От Exception её отделяют ровно те исключения, которые говорят не «данные плохие», а «программу пора останавливать»: SystemExit, KeyboardInterrupt и GeneratorExit наследуются напрямую от BaseException. Поэтому except BaseException (и его близнец — голый except:) съедает Ctrl+C и sys.exit(), и процесс перестаёт завершаться. Если всё же ловишь его ради уборки — заверши обработчик голым raise.",
        "en": "What separates it from Exception is exactly the set of \"stop the program\" signals: SystemExit, KeyboardInterrupt and GeneratorExit inherit from BaseException directly. That is why except BaseException — and its twin, a bare except: — swallows Ctrl+C and sys.exit(), leaving the process unable to quit. If you do catch it for cleanup, end the handler with a bare raise."
      },
      "syntax": "raise BaseException",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#BaseException",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "самый общий класс исключений",
        "перехват абсолютно всех ошибок",
        "корень исключений"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise BaseException",
        "except BaseException as e:",
        "    print(type(e).__name__)   # → BaseException"
      ],
      "related": [
        "exception",
        "иерархия-исключений",
        "keyboardinterrupt",
        "systemexit"
      ],
      "related_errors": []
    },
    {
      "id": "baseexceptiongroup",
      "title": "BaseExceptionGroup",
      "kind": "exception",
      "summary": {
        "ru": "Базовая группа исключений, включающая системные (SystemExit/KeyboardInterrupt); родитель ExceptionGroup (3.11+).",
        "en": "A group that may include system-exiting exceptions; parent of ExceptionGroup (3.11+)."
      },
      "body": {
        "ru": "Главная ловушка: это потомок BaseException, а не Exception, поэтому обычный except Exception группу не поймает — даже если внутри лежат самые обычные ValueError. Конструктор к тому же хитрый: если все вложенные исключения — подклассы Exception, BaseExceptionGroup(...) вернёт вам ExceptionGroup. Разбирать группу по частям нужно через except*, обычный except срабатывает на всю группу целиком.",
        "en": "The main trap: it derives from BaseException, not Exception, so a plain except Exception will not catch the group even when every exception inside it is an ordinary ValueError. The constructor is also sneaky — if all nested exceptions are Exception subclasses, BaseExceptionGroup(...) hands back an ExceptionGroup instead. To handle individual members use except*; a regular except clause matches the whole group at once."
      },
      "syntax": "raise BaseExceptionGroup",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#BaseExceptionGroup",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "базовая группа исключений",
        "группа исключений с системными"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise BaseExceptionGroup('группа', [KeyboardInterrupt()])",
        "except BaseExceptionGroup as e:",
        "    print(type(e).__name__)   # → BaseExceptionGroup"
      ],
      "related": [
        "exceptiongroup",
        "baseexception",
        "иерархия-исключений"
      ],
      "related_errors": []
    },
    {
      "id": "blockingioerror",
      "title": "BlockingIOError",
      "kind": "exception",
      "summary": {
        "ru": "Неблокирующая операция ввода-вывода заблокировалась бы (подкласс OSError).",
        "en": "A non-blocking I/O operation would have blocked (a subclass of OSError)."
      },
      "body": {
        "ru": "Возникает не сама по себе, а только если файл или сокет переведён в неблокирующий режим (socket.setblocking(False), флаг O_NONBLOCK): данных ещё нет или буфер отправки заполнен, системный errno — EAGAIN/EWOULDBLOCK. Это не поломка, а сигнал «попробуй позже»: правильная реакция — дождаться готовности дескриптора через selectors или asyncio, а не крутить повтор в пустом цикле. При частичной записи смотрите атрибут characters_written — сколько байт всё-таки ушло.",
        "en": "It never appears on its own: you only see it after putting a socket or file into non-blocking mode (socket.setblocking(False), the O_NONBLOCK flag), when no data has arrived yet or the send buffer is full — the underlying errno is EAGAIN/EWOULDBLOCK. Treat it as \"try again later\", not as breakage: wait for readiness with selectors or asyncio instead of spinning in a tight retry loop. After a partial write, characters_written tells you how many bytes did get through."
      },
      "syntax": "raise BlockingIOError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#BlockingIOError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise BlockingIOError",
        "except BlockingIOError as e:",
        "    print(type(e).__name__)   # → BlockingIOError"
      ],
      "related": [
        "oserror",
        "interruptederror",
        "os.set_blocking"
      ],
      "related_errors": []
    },
    {
      "id": "brokenpipeerror",
      "title": "BrokenPipeError",
      "kind": "exception",
      "summary": {
        "ru": "Запись в закрытый на другом конце канал/сокет (подкласс ConnectionError).",
        "en": "Writing to a pipe/socket closed on the other end (a subclass of ConnectionError)."
      },
      "body": {
        "ru": "Классический случай — скрипт печатает много строк, а его вывод передан по конвейеру в head: потребитель закрылся, и очередная запись падает. Ошибка говорит о собеседнике, а не о ваших данных, поэтому повторять запись бессмысленно — соединение нужно закрыть или установить заново. Иногда она всплывает уже на выходе из интерпретатора, когда сбрасывается буфер stdout, и выглядит как «ошибка на пустом месте».",
        "en": "The classic case: your script prints a lot of lines and the output is piped into head — the reader goes away, and the next write blows up. The error is about the peer, not about your data, so retrying the write is pointless; close the connection or open a new one. It sometimes surfaces only at interpreter shutdown, when the stdout buffer is flushed, which makes it look like an error out of nowhere."
      },
      "syntax": "raise BrokenPipeError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#BrokenPipeError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "запись в закрытый канал",
        "обрыв канала"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise BrokenPipeError",
        "except BrokenPipeError as e:",
        "    print(type(e).__name__)   # → BrokenPipeError"
      ],
      "related": [
        "connectionerror",
        "connectionreseterror",
        "oserror"
      ],
      "related_errors": []
    },
    {
      "id": "buffererror",
      "title": "BufferError",
      "kind": "exception",
      "summary": {
        "ru": "Операция с buffer-протоколом невозможна (напр. изменение размера при экспортированном буфере).",
        "en": "A buffer-related operation could not be performed."
      },
      "body": {
        "ru": "На практике почти всегда один и тот же сценарий: пока на bytearray жив memoryview, менять размер нельзя — append, extend, del и resize упадут. Лечится это не обёрткой в try/except, а освобождением представления: mv.release() или удаление последней ссылки на него, после чего буфер снова можно изменять.",
        "en": "In practice it almost always means one thing: while a memoryview onto a bytearray is still alive, the buffer cannot be resized — append, extend, del and resize all fail. Wrapping the call in try/except is the wrong fix; release the view first with mv.release() or by dropping the last reference to it, and the buffer becomes resizable again."
      },
      "syntax": "raise BufferError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#BufferError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise BufferError",
        "except BufferError as e:",
        "    print(type(e).__name__)   # → BufferError"
      ],
      "related": [
        "memoryview",
        "bytearray",
        "collections.abc.Buffer"
      ],
      "related_errors": []
    },
    {
      "id": "byteswarning",
      "title": "BytesWarning",
      "kind": "exception",
      "summary": {
        "ru": "Предупреждение о сомнительных операциях с bytes/bytearray.",
        "en": "A warning about dubious bytes/bytearray operations."
      },
      "body": {
        "ru": "По умолчанию оно молчит: сравнение bytes со str просто даёт False, а str(b'abc') тихо возвращает текст с буквой b внутри — никто вас не остановит. Предупреждение включается только флагом интерпретатора -b, а -bb превращает его в настоящую ошибку; именно так и ищут места, где перепутаны байты и текст.",
        "en": "By default it stays silent: comparing bytes with str simply evaluates to False, and str(b'abc') quietly yields text with a stray b in it — nothing stops you. The warning is emitted only when the interpreter runs with -b, and -bb turns it into a hard error; that flag pair is how you hunt down places where bytes and text got mixed up."
      },
      "syntax": "raise BytesWarning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#BytesWarning",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise BytesWarning",
        "except BytesWarning as e:",
        "    print(type(e).__name__)   # → BytesWarning"
      ],
      "related": [
        "warning",
        "bytes",
        "bytearray"
      ],
      "related_errors": []
    },
    {
      "id": "childprocesserror",
      "title": "ChildProcessError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка операции над дочерним процессом (напр. wait без детей; подкласс OSError).",
        "en": "An operation on a child process failed (a subclass of OSError)."
      },
      "body": {
        "ru": "Приходит из системного errno ECHILD: os.wait() или os.waitpid() позвали, когда дочерних процессов не осталось или статус уже забрали раньше. В коде на subprocess вы её почти не встретите — Popen сам отслеживает завершение ребёнка, так что если ошибка всё-таки вылезла, обычно одного и того же потомка ждут дважды или из разных мест программы.",
        "en": "It surfaces from the ECHILD errno: os.wait() or os.waitpid() was called when no child process was left, or its status had already been collected. Code built on subprocess rarely sees it, because Popen tracks the child's exit status itself — so when it does appear, something is usually waiting on the same child twice or from two different places."
      },
      "syntax": "raise ChildProcessError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ChildProcessError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise ChildProcessError",
        "except ChildProcessError as e:",
        "    print(type(e).__name__)   # → ChildProcessError"
      ],
      "related": [
        "oserror",
        "os.waitpid",
        "subprocess-Popen"
      ],
      "related_errors": []
    },
    {
      "id": "connectionabortederror",
      "title": "ConnectionAbortedError",
      "kind": "exception",
      "summary": {
        "ru": "Соединение прервано (aborted) хостом (подкласс ConnectionError).",
        "en": "The connection was aborted by the host (a subclass of ConnectionError)."
      },
      "body": {
        "ru": "Соответствует errno ECONNABORTED: соединение развалилось ещё на этапе установки, и чаще всего оно вылетает из accept() на стороне сервера — на Windows заметно чаще, чем на Linux. Правильная реакция не «упасть», а поймать ошибку прямо внутри accept-цикла, выбросить неудавшееся соединение и продолжить принимать следующие.",
        "en": "This maps to the ECONNABORTED errno: the connection fell apart while it was still being established, and it typically pops out of a server's accept() call — noticeably more often on Windows than on Linux. The right response is not to let the server die but to catch it inside the accept loop, drop that one half-open connection and keep serving the rest."
      },
      "syntax": "raise ConnectionAbortedError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ConnectionAbortedError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "соединение прервано",
        "обрыв соединения"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise ConnectionAbortedError",
        "except ConnectionAbortedError as e:",
        "    print(type(e).__name__)   # → ConnectionAbortedError"
      ],
      "related": [
        "connectionerror",
        "connectionreseterror",
        "connectionrefusederror"
      ],
      "related_errors": []
    },
    {
      "id": "connectionerror",
      "title": "ConnectionError",
      "kind": "exception",
      "summary": {
        "ru": "Базовый класс сетевых ошибок соединения (подкласс OSError).",
        "en": "Base class for connection-related errors (a subclass of OSError)."
      },
      "body": {
        "ru": "Ловите его, когда подробности не важны: он накрывает BrokenPipeError, ConnectionAbortedError, ConnectionRefusedError и ConnectionResetError сразу. Но не всякая сетевая беда — его потомок: таймаут это TimeoutError, а неразрешившееся имя хоста — socket.gaierror, оба наследуются от OSError в обход ConnectionError. Отдельная ловушка для тех, кто работает с requests: requests.exceptions.ConnectionError — совсем другой класс, встроенным except ConnectionError он не поймается.",
        "en": "Catch this one when the specific cause does not matter: it covers BrokenPipeError, ConnectionAbortedError, ConnectionRefusedError and ConnectionResetError in a single clause. Not every networking failure is underneath it, though — a timeout is TimeoutError and an unresolvable host name is socket.gaierror, both descending from OSError while bypassing ConnectionError. One more trap for requests users: requests.exceptions.ConnectionError is a different class entirely and the builtin except ConnectionError will not catch it."
      },
      "syntax": "raise ConnectionError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ConnectionError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка соединения",
        "не удалось подключиться",
        "нет связи с сервером"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise ConnectionError",
        "except ConnectionError as e:",
        "    print(type(e).__name__)   # → ConnectionError"
      ],
      "related": [
        "oserror",
        "connectionrefusederror",
        "connectionreseterror",
        "connectionabortederror"
      ],
      "related_errors": []
    },
    {
      "id": "connectionrefusederror",
      "title": "ConnectionRefusedError",
      "kind": "exception",
      "summary": {
        "ru": "Удалённая сторона отклонила попытку соединения (подкласс ConnectionError).",
        "en": "The remote end refused the connection (a subclass of ConnectionError)."
      },
      "body": {
        "ru": "errno ECONNREFUSED: пакет дошёл до хоста, но на этом порту никто не слушает — сервис не запущен, упал или порт перепутан. От таймаута отличается определённостью и скоростью: там пакеты молча теряются и вы получите TimeoutError, а тут вам сразу явно отказали. Повторять попытки осмысленно лишь тогда, когда вы намеренно ждёте старта сервиса, — сама по себе retry-петля отказ не лечит.",
        "en": "This is the ECONNREFUSED errno: the packet reached the host, but nothing is listening on that port — the service is not running, has crashed, or you typed the wrong port. It differs from a timeout by being fast and definite: a dropped-packet situation gives you TimeoutError instead, whereas here you got an explicit refusal. Retrying only makes sense if you are deliberately waiting for a service to come up; a retry loop by itself cures nothing."
      },
      "syntax": "raise ConnectionRefusedError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ConnectionRefusedError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "соединение отклонено",
        "сервер отказал в подключении"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise ConnectionRefusedError",
        "except ConnectionRefusedError as e:",
        "    print(type(e).__name__)   # → ConnectionRefusedError"
      ],
      "related": [
        "connectionerror",
        "connectionreseterror",
        "timeouterror"
      ],
      "related_errors": []
    },
    {
      "id": "connectionreseterror",
      "title": "ConnectionResetError",
      "kind": "exception",
      "summary": {
        "ru": "Соединение сброшено удалённой стороной (подкласс ConnectionError).",
        "en": "The connection was reset by the peer (a subclass of ConnectionError)."
      },
      "body": {
        "ru": "errno ECONNRESET: удалённая сторона оборвала уже установленное соединение (послала RST) — процесс упал, перезапустился или прокси закрыл простаивающий keep-alive. Типичная ошибка — считать, что запрос вообще не дошёл: часть данных вы могли и отправить, и прочитать, поэтому перед повтором думайте об идемпотентности операции. При попытке писать в такой сокет вместо неё нередко прилетает BrokenPipeError.",
        "en": "This is the ECONNRESET errno: the peer tore down an already-established connection with an RST — it crashed, restarted, or a proxy dropped an idle keep-alive. The common mistake is assuming the request never went through: bytes may already have been sent and received, so think about whether the operation is idempotent before retrying. Writing into such a socket often raises BrokenPipeError instead."
      },
      "syntax": "raise ConnectionResetError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ConnectionResetError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "соединение сброшено",
        "сервер разорвал соединение"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise ConnectionResetError",
        "except ConnectionResetError as e:",
        "    print(type(e).__name__)   # → ConnectionResetError"
      ],
      "related": [
        "connectionerror",
        "connectionabortederror",
        "brokenpipeerror"
      ],
      "related_errors": []
    },
    {
      "id": "copy.error",
      "title": "copy.Error",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка операции копирования в модуле copy.",
        "en": "Common base class for all non-exit exceptions"
      },
      "body": {
        "ru": "Одной этой ловушки мало: большинство некопируемых объектов (сокеты, блокировки, открытые файлы) падают с TypeError из pickle-механизма, а copy.Error достаётся лишь тем, у кого нет ни __copy__/__deepcopy__, ни __reduce_ex__. Поэтому при копировании чужих объектов ловите copy.Error и TypeError вместе — либо вообще не копируйте то, что держит внешний ресурс.",
        "en": "Catching this alone is not enough: most uncopyable objects (sockets, locks, open files) fail with TypeError from the pickle machinery, and copy.Error is left for objects that define neither __copy__/__deepcopy__ nor __reduce_ex__. So when copying objects you did not write, catch copy.Error and TypeError together — or simply do not copy anything that holds an external resource."
      },
      "syntax": "raise copy.Error",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/copy.html#copy.Error",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка копирования объекта",
        "сбой при копировании"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import copy",
        "try:",
        "    raise copy.Error",
        "except copy.Error as e:",
        "    print(type(e).__name__)   # → Error"
      ],
      "related": [
        "copy.copy",
        "copy.deepcopy",
        "ловушки-копирования"
      ],
      "related_errors": []
    },
    {
      "id": "dataclasses.frozeninstanceerror",
      "title": "dataclasses.FrozenInstanceError",
      "kind": "exception",
      "summary": {
        "ru": "Попытка изменить поле frozen-dataclass (@dataclass(frozen=True)).",
        "en": "Attribute not found"
      },
      "body": {
        "ru": "Это подкласс AttributeError, поэтому широкий except AttributeError проглотит его незаметно — ловите именно FrozenInstanceError, если хотите отличить попытку записи в frozen-объект от опечатки в имени атрибута. И помните, что frozen=True запрещает только присваивание и удаление атрибутов самого объекта: список или словарь, лежащий в поле, по-прежнему меняется на месте, так что неизменяемость тут поверхностная. Чтобы получить объект с другим значением поля, делают изменённую копию через dataclasses.replace().",
        "en": "It subclasses AttributeError, so a broad except AttributeError swallows it silently — catch FrozenInstanceError by name if you want to tell a write to a frozen object apart from a misspelled attribute. Also, frozen=True only blocks setting and deleting attributes on the instance itself: a list or dict stored in a field can still be mutated in place, so the immutability is shallow. To get a different value in a field, build a modified copy with dataclasses.replace()."
      },
      "syntax": "raise dataclasses.FrozenInstanceError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.FrozenInstanceError",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "нельзя изменить поле неизменяемого датакласса",
        "присваивание в замороженном датаклассе",
        "неизменяемый датакласс"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import dataclasses",
        "try:",
        "    raise dataclasses.FrozenInstanceError",
        "except dataclasses.FrozenInstanceError as e:",
        "    print(type(e).__name__)   # → FrozenInstanceError"
      ],
      "related": [
        "dataclass-frozen-true",
        "dataclasses.replace",
        "attributeerror"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.clamped",
      "title": "decimal.Clamped",
      "kind": "exception",
      "summary": {
        "ru": "Экспонента результата decimal изменена, чтобы уложиться в допустимый диапазон.",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "Clamped — сигнал, а не обычная ошибка: в стандартном контексте ловушка для него выключена, вычисление продолжается молча, и факт срабатывания виден только по флагу в контексте. Чтобы он действительно поднялся как исключение, ловушку включают в контексте вручную. Само число при этом не портится — подгоняются экспонента и число нулей в записи, поэтому сигнал важен, когда вам принципиально точное представление (например, количество знаков после запятой), а не значение.",
        "en": "Clamped is a signal, not an everyday error: its trap is off in the default context, so the computation goes on quietly and the only trace is a flag on the context. You have to enable the trap yourself to turn it into a real exception. The numeric value is not corrupted — only the exponent and the number of trailing zeros are adjusted to fit the representation limits, so this matters when you care about the exact scale of the result rather than its value."
      },
      "syntax": "raise decimal.Clamped",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.Clamped",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.Clamped",
        "except decimal.Clamped as e:",
        "    print(type(e).__name__)   # → Clamped"
      ],
      "related": [
        "decimal.decimalexception",
        "decimal.subnormal",
        "decimal.rounded"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.conversionsyntax",
      "title": "decimal.ConversionSyntax",
      "kind": "exception",
      "summary": {
        "ru": "Некорректная строка при создании Decimal из строки.",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "Ловить этот класс напрямую бесполезно: на некорректной строке decimal поднимает InvalidOperation, а ConversionSyntax — лишь имя уточняющего условия, а не то, что реально прилетит в except. Пишите except decimal.InvalidOperation. А если ловушку InvalidOperation в контексте выключить, исключения не будет вовсе — вместо него вернётся Decimal NaN, и ошибку придётся замечать по флагу контекста или проверкой is_nan().",
        "en": "Catching this class directly gets you nowhere: a malformed string makes decimal raise InvalidOperation, and ConversionSyntax is just the name of the underlying condition, not the object that reaches your except clause. Write except decimal.InvalidOperation instead. And if the InvalidOperation trap is disabled in the context, nothing is raised at all — you get a Decimal NaN back, and the failure has to be spotted via the context flags or an is_nan() check."
      },
      "syntax": "raise decimal.ConversionSyntax",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#signals",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "некорректная строка для десятичного числа",
        "ошибка создания десятичного числа из строки"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.ConversionSyntax",
        "except decimal.ConversionSyntax as e:",
        "    print(type(e).__name__)   # → ConversionSyntax"
      ],
      "related": [
        "decimal.invalidoperation",
        "decimal.decimal",
        "decimal.decimalexception"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.decimalexception",
      "title": "decimal.DecimalException",
      "kind": "exception",
      "summary": {
        "ru": "Базовый класс всех сигналов-исключений модуля decimal (подкласс ArithmeticError).",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "Общий предок всех сигналов decimal и одновременно подкласс ArithmeticError, так что except ArithmeticError перехватит и его — удобно, но слишком широко, если рядом возможны обычные деления и переполнения. Главное, чего не видно из имени: большинство сигналов по умолчанию вообще не поднимаются. В стандартном контексте включены ловушки только для InvalidOperation, DivisionByZero и Overflow, остальные условия просто ставят флаг в контексте, и обнаружить их можно лишь по этому флагу.",
        "en": "It is the common ancestor of every decimal signal and at the same time a subclass of ArithmeticError, so except ArithmeticError catches it too — convenient, but too wide if plain divisions or overflows can happen nearby. The non-obvious part: most signals are not raised at all by default. The standard context traps only InvalidOperation, DivisionByZero and Overflow; the rest merely set a flag on the context, and that flag is the only way to notice them."
      },
      "syntax": "raise decimal.DecimalException",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.DecimalException",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "базовое исключение десятичной арифметики",
        "ошибки точных десятичных вычислений"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.DecimalException",
        "except decimal.DecimalException as e:",
        "    print(type(e).__name__)   # → DecimalException"
      ],
      "related": [
        "arithmeticerror",
        "decimal.invalidoperation",
        "decimal.divisionbyzero",
        "decimal.getcontext"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.divisionbyzero",
      "title": "decimal.DivisionByZero",
      "kind": "exception",
      "summary": {
        "ru": "Деление ненулевого Decimal на ноль (аналог ZeroDivisionError для decimal).",
        "en": "Second argument to a division or modulo operation was zero"
      },
      "body": {
        "ru": "Класс наследуется и от DecimalException, и от встроенного ZeroDivisionError, поэтому привычный except ZeroDivisionError сработает и на Decimal — отдельная ветка нужна, только если вы различаете источники ошибки. Деление нуля на ноль сюда не относится: это неопределённость, и она сигнализируется как InvalidOperation. И если выключить ловушку в контексте, исключения не будет — операция вернёт бесконечность соответствующего знака.",
        "en": "The class inherits from both DecimalException and the built-in ZeroDivisionError, so an ordinary except ZeroDivisionError already covers Decimal division too; a separate branch is only worth it when you need to tell the two sources apart. Zero divided by zero does not land here — that is undefined and signals InvalidOperation instead. And with the trap disabled in the context nothing is raised: the operation simply yields a signed infinity."
      },
      "syntax": "raise decimal.DivisionByZero",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.DivisionByZero",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "деление на ноль десятичного числа",
        "деление на ноль в десятичной арифметике"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.DivisionByZero",
        "except decimal.DivisionByZero as e:",
        "    print(type(e).__name__)   # → DivisionByZero"
      ],
      "related": [
        "zerodivisionerror",
        "decimal.divisionundefined",
        "decimal.decimalexception"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.divisionimpossible",
      "title": "decimal.DivisionImpossible",
      "kind": "exception",
      "summary": {
        "ru": "Целочисленное деление decimal не может дать корректный результат.",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "Речь не про обычное деление, а про целочисленное: сигнал возникает, когда целая часть результата // или divmod требует больше цифр, чем precision текущего контекста. Это подкласс InvalidOperation, а его ловушка в контексте по умолчанию включена, поэтому исключение действительно вылетает; со снятой ловушкой вместо него вернётся NaN. Чинится увеличением precision, а не перехватом.",
        "en": "This is about integer division rather than the plain / operator: it fires when the integer part produced by // or divmod would need more digits than the context precision allows. It subclasses InvalidOperation, whose trap is enabled in the default context, so you genuinely get an exception; untrap it and the operation returns NaN instead. The cure is a larger precision, not a try/except."
      },
      "syntax": "raise decimal.DivisionImpossible",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#signals",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.DivisionImpossible",
        "except decimal.DivisionImpossible as e:",
        "    print(type(e).__name__)   # → DivisionImpossible"
      ],
      "related": [
        "decimal.invalidoperation",
        "decimal.divisionundefined",
        "decimal.divisionbyzero"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.divisionundefined",
      "title": "decimal.DivisionUndefined",
      "kind": "exception",
      "summary": {
        "ru": "Деление 0/0 в decimal (результат не определён).",
        "en": "Second argument to a division or modulo operation was zero"
      },
      "body": {
        "ru": "Срабатывает именно на 0/0 — деление ненулевого числа на ноль даёт другой сигнал, DivisionByZero. Класс наследует сразу и InvalidOperation, и ZeroDivisionError, так что обычный except ZeroDivisionError его тоже поймает. Ловушка InvalidOperation включена по умолчанию; если её снять, операция молча вернёт NaN.",
        "en": "It is specifically the 0/0 case: dividing a non-zero value by zero raises DivisionByZero instead. The class inherits from both InvalidOperation and ZeroDivisionError, so an ordinary except ZeroDivisionError catches it as well. The InvalidOperation trap is on by default; remove it and the operation quietly yields NaN."
      },
      "syntax": "raise decimal.DivisionUndefined",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#signals",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.DivisionUndefined",
        "except decimal.DivisionUndefined as e:",
        "    print(type(e).__name__)   # → DivisionUndefined"
      ],
      "related": [
        "decimal.divisionbyzero",
        "decimal.divisionimpossible",
        "decimal.invalidoperation"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.floatoperation",
      "title": "decimal.FloatOperation",
      "kind": "exception",
      "summary": {
        "ru": "Смешивание Decimal и float при включённой ловушке FloatOperation.",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "По умолчанию ловушка выключена: смешивать Decimal и float в конструкторе и в сравнениях можно, факт смешения лишь молча взводит флаг в контексте. Включают её осознанно — чтобы отловить случайно затесавшийся float в денежных расчётах. Даже с включённой ловушкой тихими остаются сравнения на равенство и явные преобразования вроде Decimal.from_float(), а сам класс наследуется ещё и от TypeError.",
        "en": "The trap is off by default: mixing Decimal and float in the constructor or in comparisons is permitted and merely records a flag in the context. You enable it deliberately, to catch a stray float that sneaked into money arithmetic. Even when trapped, equality comparisons and explicit conversions such as Decimal.from_float() stay silent, and the class also inherits from TypeError."
      },
      "syntax": "raise decimal.FloatOperation",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.FloatOperation",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.FloatOperation",
        "except decimal.FloatOperation as e:",
        "    print(type(e).__name__)   # → FloatOperation"
      ],
      "related": [
        "decimal.decimal",
        "decimal.getcontext",
        "float"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.inexact",
      "title": "decimal.Inexact",
      "kind": "exception",
      "summary": {
        "ru": "Округление decimal отбросило ненулевые разряды (результат неточен).",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "Ловушка по умолчанию выключена — сигнал просто взводит флаг в context.flags, а округлённый результат возвращается как ни в чём не бывало. Её включают, когда важно узнать, что цифры реально потерялись: например, что сумма не разделилась нацело. Не путать с Rounded: тот взводится при любом отбрасывании разрядов, даже нулевых, а Inexact — только когда отброшено что-то значащее.",
        "en": "The trap is disabled by default: the signal just sets a flag in context.flags while the rounded result is returned as usual. You turn it on when you need to know that digits were actually lost, say that an amount did not divide evenly. Do not confuse it with Rounded, which fires whenever digits are dropped at all, even zeros; Inexact fires only when the discarded digits were non-zero."
      },
      "syntax": "raise decimal.Inexact",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.Inexact",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "потеря точности при округлении",
        "неточный результат вычисления"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.Inexact",
        "except decimal.Inexact as e:",
        "    print(type(e).__name__)   # → Inexact"
      ],
      "related": [
        "decimal.rounded",
        "decimal.decimalexception",
        "decimal.getcontext"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.invalidcontext",
      "title": "decimal.InvalidContext",
      "kind": "exception",
      "summary": {
        "ru": "Недопустимая настройка контекста decimal.",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "Говорит не о плохих данных, а об испорченном контексте: неизвестный или неподдерживаемый режим округления либо precision за пределами возможностей реализации. Подкласс InvalidOperation, ловушка которого включена по умолчанию; без ловушки операция вернёт NaN. Причину искать там, где Context собирали руками, а не в самой арифметике.",
        "en": "It points at a malformed context rather than bad data: an unknown or unsupported rounding mode, or a precision beyond what the implementation can handle. It subclasses InvalidOperation, whose trap is on by default; untrapped, the operation returns NaN. Look for the bug where the Context was built by hand, not in the arithmetic itself."
      },
      "syntax": "raise decimal.InvalidContext",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#signals",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.InvalidContext",
        "except decimal.InvalidContext as e:",
        "    print(type(e).__name__)   # → InvalidContext"
      ],
      "related": [
        "decimal.getcontext",
        "decimal.setcontext",
        "decimal.invalidoperation"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.invalidoperation",
      "title": "decimal.InvalidOperation",
      "kind": "exception",
      "summary": {
        "ru": "Недопустимая операция decimal (напр. Decimal('nan') в сравнении, деление 0/0).",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "Частая ловушка на разборе ввода: Decimal(\"abc\") кидает именно InvalidOperation, а не ValueError, как int(\"abc\"), поэтому привычный except ValueError вокруг чтения числа её не поймает (общий except ArithmeticError — поймает). Ловушка на этот сигнал включена в контексте по умолчанию; если снять её через getcontext().traps, операция молча вернёт NaN, и дальше этот NaN протечёт через все вычисления, не выдав себя ничем, кроме липкого флага в getcontext().flags.",
        "en": "Watch out when parsing input: Decimal(\"abc\") raises InvalidOperation, not the ValueError you get from int(\"abc\"), so a habitual except ValueError around user input will miss it (except ArithmeticError does catch it). The trap for this signal is on in the default context; turn it off via getcontext().traps and the operation quietly yields NaN instead, which then spreads through every later computation with nothing to show for it but a sticky entry in getcontext().flags."
      },
      "syntax": "raise decimal.InvalidOperation",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.InvalidOperation",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "недопустимая операция с десятичным числом",
        "некорректное значение десятичного числа"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.InvalidOperation",
        "except decimal.InvalidOperation as e:",
        "    print(type(e).__name__)   # → InvalidOperation"
      ],
      "related": [
        "decimal.decimalexception",
        "decimal.conversionsyntax",
        "decimal.divisionundefined"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.overflow",
      "title": "decimal.Overflow",
      "kind": "exception",
      "summary": {
        "ru": "Экспонента результата decimal превысила максимум контекста.",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "Это не встроенный OverflowError — перехватывать нужно именно decimal.Overflow (или общий ArithmeticError). Порог задаёт Emax текущего контекста, а он по умолчанию равен 999999, так что на практике сигнал видят только после сужения контекста вручную. Ловушка включена по умолчанию; если её снять, результатом станет либо Infinity, либо наибольшее конечное число — что именно, решает режим округления.",
        "en": "This is not the built-in OverflowError: catch decimal.Overflow itself (or the shared ArithmeticError base). The threshold is the current context's Emax, which defaults to 999999, so you normally only meet this signal after deliberately narrowing the context. Its trap is enabled by default; with the trap off the result is either Infinity or the largest finite number, depending on the rounding mode in effect."
      },
      "syntax": "raise decimal.Overflow",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.Overflow",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "переполнение десятичного числа",
        "слишком большое десятичное число"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.Overflow",
        "except decimal.Overflow as e:",
        "    print(type(e).__name__)   # → Overflow"
      ],
      "related": [
        "decimal.underflow",
        "decimal.decimalexception",
        "overflowerror",
        "decimal.inexact"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.rounded",
      "title": "decimal.Rounded",
      "kind": "exception",
      "summary": {
        "ru": "Результат decimal был округлён (даже если точность не потеряна).",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "Это сигнал, а не обычная ошибка: ловушка на него по умолчанию выключена, и узнать о срабатывании можно через getcontext().flags[decimal.Rounded] — флаг липкий, сам не сбрасывается, чистить его надо руками (clear_flags). Не путать с Inexact: Rounded срабатывает при любом отбрасывании цифр, даже если отброшены нули и значение не изменилось, а Inexact — только когда действительно потеряна ненулевая часть.",
        "en": "Treat this as a signal rather than an error: its trap is off by default, and you detect it by reading getcontext().flags[decimal.Rounded] — the flag is sticky and stays set until you call clear_flags. Do not confuse it with Inexact: Rounded fires whenever digits are discarded at all, even discarded zeros that leave the value unchanged, while Inexact fires only when something non-zero was actually lost."
      },
      "syntax": "raise decimal.Rounded",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.Rounded",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "округление десятичного числа",
        "результат округлён"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.Rounded",
        "except decimal.Rounded as e:",
        "    print(type(e).__name__)   # → Rounded"
      ],
      "related": [
        "decimal.inexact",
        "decimal.decimalexception",
        "decimal.getcontext"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.subnormal",
      "title": "decimal.Subnormal",
      "kind": "exception",
      "summary": {
        "ru": "Результат decimal субнормален (экспонента ниже Emin).",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "Ловушка на этот сигнал по умолчанию выключена, а при дефолтном Emin, равном -999999, он практически недостижим — вы его увидите, только если сузите контекст вручную. Сам по себе Subnormal не значит, что результат испорчен: он лишь предупреждает, что число ушло ниже Emin и представлено меньшим числом значащих цифр. Реальная потеря — это уже Underflow, то есть субнормальный и одновременно неточный результат.",
        "en": "The trap for this signal is off by default, and with the default Emin of -999999 it is practically unreachable — you meet it only after narrowing the context yourself. Subnormal on its own does not mean the result is wrong; it warns that the value dropped below Emin and is now carried with fewer significant digits. Actual loss of value is Underflow: a result that is subnormal and inexact at the same time."
      },
      "syntax": "raise decimal.Subnormal",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.Subnormal",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.Subnormal",
        "except decimal.Subnormal as e:",
        "    print(type(e).__name__)   # → Subnormal"
      ],
      "related": [
        "decimal.underflow",
        "decimal.clamped",
        "decimal.decimalexception"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.underflow",
      "title": "decimal.Underflow",
      "kind": "exception",
      "summary": {
        "ru": "Результат decimal слишком мал (субнормализован и округлён к нулю).",
        "en": "Base class for arithmetic errors"
      },
      "body": {
        "ru": "Underflow наследуется сразу от Subnormal, Inexact и Rounded, поэтому широкий except decimal.Subnormal перехватит и его — более конкретные классы ставьте раньше в цепочке except. Ловушка по умолчанию выключена: без неё результат просто становится нулём нужного знака, и такое молчаливое обнуление легко принять за ошибку в собственной формуле, хотя это переполнение вниз по Emin.",
        "en": "Underflow inherits from Subnormal, Inexact and Rounded all at once, so a broad except decimal.Subnormal will swallow it too — put the more specific classes earlier in the except chain. Its trap is off by default, so without it the result simply collapses to a signed zero, and that silent zero is easy to misread as a bug in your own formula rather than a value pushed past Emin."
      },
      "syntax": "raise decimal.Underflow",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.Underflow",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "потеря значимости",
        "слишком маленькое десятичное число"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import decimal",
        "try:",
        "    raise decimal.Underflow",
        "except decimal.Underflow as e:",
        "    print(type(e).__name__)   # → Underflow"
      ],
      "related": [
        "decimal.overflow",
        "decimal.subnormal",
        "decimal.decimalexception",
        "decimal.inexact"
      ],
      "related_errors": []
    },
    {
      "id": "deprecationwarning",
      "title": "DeprecationWarning",
      "kind": "exception",
      "summary": {
        "ru": "Использование устаревшей возможности, предназначенной для разработчиков.",
        "en": "Use of a deprecated feature, aimed at developers."
      },
      "body": {
        "ru": "Это подкласс Warning, а не ошибки: его почти никогда не поднимают через raise и не ловят — его выдаёт warnings.warn() внутри библиотеки, и программа продолжает работать. По умолчанию Python показывает DeprecationWarning только для кода, запущенного в __main__, поэтому предупреждение из глубины сторонней библиотеки вы просто не увидите, пока не запустите с -W default или python -X dev. Реакция правильная одна: не глушить, а переписать вызов на актуальный аналог, пока он ещё работает.",
        "en": "It is a Warning subclass, not an error: you almost never raise or catch it — a library emits it through warnings.warn() and execution continues. By default CPython shows DeprecationWarning only for code triggered in __main__, so warnings coming from deep inside a third-party package stay invisible until you run with -W default or python -X dev. The right response is not to silence it but to migrate the call while the old API still works."
      },
      "syntax": "raise DeprecationWarning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#DeprecationWarning",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "устаревшая возможность",
        "предупреждение об устаревании"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise DeprecationWarning",
        "except DeprecationWarning as e:",
        "    print(type(e).__name__)   # → DeprecationWarning"
      ],
      "related": [
        "pendingdeprecationwarning",
        "futurewarning",
        "warning"
      ],
      "related_errors": []
    },
    {
      "id": "else-в-try-except",
      "title": "else в try-except",
      "kind": "exception",
      "summary": {
        "ru": "Блок else выполняется, только если в try не возникло исключения. Отделяет «счастливый путь» от обработки ошибок.",
        "en": "The else block runs only if no exception was raised inside try. It separates the happy path from error handling."
      },
      "body": {
        "ru": "Смысл else не в красоте, а в сужении зоны риска: строки, переехавшие из try в else, этим except уже не перехватываются, иначе ValueError из «продолжения работы» тихо уйдёт в ветку, написанную про разбор числа. else без хотя бы одного except — синтаксическая ошибка, а порядок такой: try, затем else и только потом finally. Если из try вышли через return, break или continue, else не выполнится вовсе.",
        "en": "The point of else isn't tidiness but narrowing the risk zone: lines moved out of try are no longer covered by that except, so a ValueError raised while carrying on can't be silently swallowed by a handler written for parsing. An else clause needs at least one except (else on its own is a syntax error), and the order is try, then else, then finally. Leaving try via return, break or continue skips else entirely."
      },
      "syntax": "try:\n    ...\nexcept Error:\n    ...\nelse:\n    # только при успехе",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#else-clause",
      "version": "",
      "section": "Исключения",
      "subcat": "обработка",
      "color_group": "exc",
      "aliases": [
        "если исключения не было",
        "код при успешном выполнении try"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    result = int('42')",
        "except ValueError:",
        "    print('error')",
        "else:",
        "    print(f'success: {result}')  # → success: 42",
        "def safe_open(path):",
        "    try:",
        "        f = open(path)",
        "    except FileNotFoundError:",
        "        return None",
        "    else:",
        "        data = f.read()",
        "        f.close()",
        "        return data",
        "# else + finally",
        "try:",
        "    x = 10 / 2",
        "except ZeroDivisionError:",
        "    print('zero division')",
        "else:",
        "    print(f'result: {x}')  # → result: 5.0",
        "finally:",
        "    print('done')  # → done",
        "    import json",
        "def parse(s):",
        "    try:",
        "        data = json.loads(s)",
        "    except json.JSONDecodeError:",
        "        print('invalid JSON')",
        "    else:",
        "        return data",
        "print(parse('{\"a\":1}'))  # → {'a':1}",
        "# else vs перемещение кода после try",
        "# В else — только код, который НЕ должен вызывать то же исключение",
        "try:",
        "    n = int(input('Enter number: ') if False else '5')",
        "except ValueError:",
        "    print('not a number')",
        "else:",
        "    print(n * 2)  # → 10"
      ],
      "related": [
        "try-except",
        "finally",
        "else-в-циклах"
      ],
      "related_errors": []
    },
    {
      "id": "encodings.CodecRegistryError",
      "title": "encodings.CodecRegistryError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка реестра кодеков: поднимается при сбое поиска/регистрации кодека в системе encodings (подкласс LookupError и SystemError).",
        "en": "A codec registry error, raised on failures looking up or registering a codec."
      },
      "body": {
        "ru": "Это не про «неправильную кодировку»: неизвестное имя кодировки даёт обычный LookupError, а битые байты — UnicodeDecodeError. CodecRegistryError означает поломку самого механизма кодеков — модуль из пакета encodings зарегистрировал что-то, не годное как CodecInfo. В учебном коде почти не встречается; из-за наследования сразу от LookupError и SystemError его перехватит и except LookupError.",
        "en": "This is not about a wrong text encoding: an unknown encoding name gives a plain LookupError, and broken bytes give UnicodeDecodeError. CodecRegistryError means the codec machinery itself is broken — a module in the encodings package registered something unusable as CodecInfo. You will practically never meet it in course work, and since it inherits from both LookupError and SystemError, an except LookupError handler already catches it."
      },
      "syntax": "raise encodings.CodecRegistryError(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/codecs.html#encodings.CodecRegistryError",
      "version": "",
      "section": "Исключения",
      "subcat": "исключения модулей",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "encodings"
      ],
      "examples": [
        "import encodings",
        "print(issubclass(encodings.CodecRegistryError, Exception))   # → True",
        "print(issubclass(encodings.CodecRegistryError, LookupError))   # → True",
        "print(issubclass(encodings.CodecRegistryError, SystemError))   # → True",
        "print(encodings.CodecRegistryError('bad search function'))   # → bad search function",
        "print('abc'.encode('nosuchcodec'))   # → LookupError"
      ],
      "related": [
        "lookuperror",
        "systemerror",
        "unicodeerror"
      ],
      "related_errors": []
    },
    {
      "id": "encodingwarning",
      "title": "EncodingWarning",
      "kind": "exception",
      "summary": {
        "ru": "Открытие текстового файла без явной кодировки (полагаясь на локаль).",
        "en": "Opening a text file without an explicit encoding (relying on the locale)."
      },
      "body": {
        "ru": "Появилось в Python 3.10 (PEP 597) и по умолчанию молчит: чтобы увидеть его, нужен запуск с -X warn_default_encoding или переменная PYTHONWARNDEFAULTENCODING=1. Смысл предупреждения не в самом факте, а в скрытой переносимости: open(path) без encoding берёт кодировку локали, поэтому файл, прочитанный на Linux как UTF-8, на Windows может уехать в cp1251 и упасть с UnicodeDecodeError. Лечится не перехватом, а явным encoding='utf-8' в каждом открытии текстового файла.",
        "en": "Added in Python 3.10 (PEP 597) and silent by default: you only see it when running with -X warn_default_encoding or with PYTHONWARNDEFAULTENCODING=1 set. The real issue it flags is portability — open(path) without encoding falls back to the locale encoding, so a file read as UTF-8 on Linux may be decoded as cp1251 on Windows and blow up with UnicodeDecodeError. The fix is never to catch it, but to pass encoding='utf-8' explicitly wherever you open text."
      },
      "syntax": "raise EncodingWarning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#EncodingWarning",
      "version": "3.10",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "кодировка не указана",
        "предупреждение о кодировке"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise EncodingWarning",
        "except EncodingWarning as e:",
        "    print(type(e).__name__)   # → EncodingWarning"
      ],
      "related": [
        "open",
        "io.text_encoding",
        "warning"
      ],
      "related_errors": []
    },
    {
      "id": "eoferror",
      "title": "EOFError",
      "kind": "exception",
      "summary": {
        "ru": "input() достиг конца ввода (EOF), не получив данных.",
        "en": "input() hit end-of-file (EOF) without reading any data."
      },
      "body": {
        "ru": "На Stepik это штатный сигнал «строки кончились», когда ввод подаётся файлом или пайпом, а не с клавиатуры: цикл вроде while True: s = input() рано или поздно упирается в конец потока. Важно, что EOFError поднимает именно input(); низкоуровневое чтение ведёт себя иначе — sys.stdin.readline() и file.read() на конце просто возвращают пустую строку, без исключения. Поэтому либо ловите EOFError и выходите из цикла, либо читайте через for line in sys.stdin, где конец обрабатывается сам.",
        "en": "On judge systems this is the normal \"input ran out\" signal when stdin comes from a file or a pipe rather than a keyboard: a while True: s = input() loop eventually hits the end of the stream. Note that only input() raises it — lower-level reads behave differently, since sys.stdin.readline() and file.read() simply return an empty string at EOF. So either catch EOFError to break the loop, or iterate with for line in sys.stdin, where the end of input is handled for you."
      },
      "syntax": "raise EOFError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#EOFError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "конец ввода",
        "ввод закончился",
        "закончились строки ввода"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise EOFError",
        "except EOFError as e:",
        "    print(type(e).__name__)   # → EOFError"
      ],
      "related": [
        "input",
        "stdin-stdout",
        "keyboardinterrupt"
      ],
      "related_errors": []
    },
    {
      "id": "exception",
      "title": "Exception",
      "kind": "exception",
      "summary": {
        "ru": "Базовый класс всех обычных (прикладных) исключений. `except Exception` — типовой перехват, не задевающий SystemExit/KeyboardInterrupt.",
        "en": "The base class of all ordinary (non-system-exit) exceptions; `except Exception` is the usual broad catch."
      },
      "body": {
        "ru": "except Exception ловит и настоящую ошибку данных, и вашу опечатку: NameError, AttributeError, TypeError — тоже его потомки, поэтому широкий перехват превращает баг в тихо проглоченное сообщение. Пишите конкретный класс (ValueError, KeyError, ZeroDivisionError), а Exception оставляйте на самый верхний уровень программы, где вы логируете и завершаетесь. От голого except: он отличается тем, что не трогает SystemExit, KeyboardInterrupt и GeneratorExit — они наследуются от BaseException, и Ctrl+C по-прежнему прерывает программу.",
        "en": "except Exception swallows genuine data errors and your own typos alike: NameError, AttributeError and TypeError are all subclasses, so a broad catch turns a bug into a quietly printed message. Name the concrete class instead (ValueError, KeyError, ZeroDivisionError) and reserve Exception for the outermost layer where you log and exit. Unlike a bare except:, it leaves SystemExit, KeyboardInterrupt and GeneratorExit alone — they descend from BaseException, so Ctrl+C still stops the program."
      },
      "syntax": "raise Exception",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#Exception",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "базовый класс всех ошибок",
        "поймать любую ошибку"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise Exception",
        "except Exception as e:",
        "    print(type(e).__name__)   # → Exception"
      ],
      "related": [
        "baseexception",
        "иерархия-исключений",
        "try-except",
        "пользовательские-исключения"
      ],
      "related_errors": []
    },
    {
      "id": "exceptiongroup",
      "title": "ExceptionGroup",
      "kind": "exception",
      "summary": {
        "ru": "Группа нескольких исключений сразу (Python 3.11+); обрабатывается except*.",
        "en": "A group of several exceptions at once (3.11+); handled with except*."
      },
      "body": {
        "ru": "Членство в группе не работает «прозрачно»: except ValueError не поймает группу, внутри которой лежит ValueError, а except ExceptionGroup не сработает на одиночный ValueError, брошенный обычным raise. Ради этого и придумали except* — он разбирает группу по типам, выполняет ветку для подходящего подмножества, а остальные исключения пробрасывает дальше; при обычном except вы получаете объект целиком и разбираете список e.exceptions руками. Конструктор требует непустой список, а все элементы — наследники Exception; для BaseException-потомков есть BaseExceptionGroup. Чаще всего группу вы встретите не в своём raise, а на выходе из asyncio.TaskGroup.",
        "en": "Group membership is not transparent: except ValueError will not catch a group that contains a ValueError, and except ExceptionGroup will not catch a plain ValueError raised on its own. That is what except* is for — it splits the group by type, runs the branch for the matching subset and re-raises the rest; with an ordinary except you get the whole object and must walk e.exceptions yourself. The constructor demands a non-empty sequence whose items all derive from Exception; use BaseExceptionGroup for anything below that. In practice you meet groups on the way out of asyncio.TaskGroup far more often than in your own raise."
      },
      "syntax": "raise ExceptionGroup",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ExceptionGroup",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "группа исключений",
        "несколько ошибок сразу"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise ExceptionGroup('несколько ошибок', [ValueError('a'), KeyError('b')])",
        "except ExceptionGroup as e:",
        "    print(type(e).__name__)   # → ExceptionGroup"
      ],
      "related": [
        "baseexceptiongroup",
        "exception",
        "try-except"
      ],
      "related_errors": []
    },
    {
      "id": "fileexistserror",
      "title": "FileExistsError",
      "kind": "exception",
      "summary": {
        "ru": "Попытка создать файл/каталог, который уже существует (подкласс OSError).",
        "en": "Trying to create a file or directory that already exists (a subclass of OSError)."
      },
      "body": {
        "ru": "Прилетает от os.mkdir(), Path.mkdir() и от open() в режиме 'x' — то есть там, где вы просите создать объект и он обязан быть новым. Обычная реакция студента — сначала проверить существование через os.path.exists(), но между проверкой и созданием файл может появиться; надёжнее либо ловить это исключение, либо сразу передать exist_ok=True в mkdir()/makedirs().",
        "en": "You get this from os.mkdir(), Path.mkdir(), or open() in 'x' mode — places where you demand that the target be brand new. The instinct to guard with os.path.exists() first is fragile: the file can appear between the check and the call, so either catch the exception or pass exist_ok=True to mkdir()/makedirs()."
      },
      "syntax": "raise FileExistsError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#FileExistsError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "файл уже существует",
        "каталог уже существует"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise FileExistsError",
        "except FileExistsError as e:",
        "    print(type(e).__name__)   # → FileExistsError"
      ],
      "related": [
        "filenotfounderror",
        "oserror",
        "path.mkdir"
      ],
      "related_errors": []
    },
    {
      "id": "filenotfounderror",
      "title": "FileNotFoundError",
      "kind": "exception",
      "summary": {
        "ru": "Вызывается при попытке открыть или работать с файлом, которого не существует. Подкласс OSError.",
        "en": "Raised on an attempt to open or use a file that does not exist. A subclass of OSError."
      },
      "body": {
        "ru": "Чаще всего виноват не отсутствующий файл, а относительный путь: он считается от текущей рабочей директории, а не от папки со скриптом, поэтому из другой директории тот же код падает. При записи ошибка означает другое — самого файла может не быть, но обязана существовать папка, в которую вы пишете. Ловите этот класс, а не голый OSError, иначе в ту же ветку попадут отказ в доступе и десяток других причин.",
        "en": "Usually the culprit is not a missing file but a relative path: it resolves against the current working directory, not the script's folder, so the same code breaks when run from elsewhere. In write mode the meaning shifts — the file itself may be absent, but the directory you write into must already exist. Catch this specific class rather than bare OSError, or permission denials and unrelated failures land in the same branch."
      },
      "syntax": "raise FileNotFoundError('файл не найден')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#FileNotFoundError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "файл не найден",
        "нет такого файла",
        "не удалось открыть файл"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "open('no_file.txt')         # FileNotFoundError",
        "try:",
        "    with open('data.txt') as f:",
        "        text = f.read()",
        "except FileNotFoundError:",
        "    print('файл не найден')"
      ],
      "related": [
        "oserror",
        "permissionerror",
        "fileexistserror",
        "open"
      ],
      "related_errors": []
    },
    {
      "id": "finally",
      "title": "finally",
      "kind": "exception",
      "summary": {
        "ru": "Блок finally выполняется всегда — независимо от того, было ли исключение. Используется для освобождения ресурсов.",
        "en": "The finally block always runs — whether or not an exception was raised. Used to release resources."
      },
      "body": {
        "ru": "Главная ловушка — return, break или continue внутри самого finally: они гасят летящее исключение и подменяют результат try, из-за чего ошибка исчезает бесследно; в Python 3.14 такой код уже даёт SyntaxWarning. finally срабатывает и при выходе через return: значение сначала вычисляется, потом идёт finally, и только затем функция действительно возвращает. Для файлов, сокетов и блокировок обычно лучше with — менеджер контекста делает то же самое короче и без шанса забыть закрытие.",
        "en": "The main trap is a return, break or continue inside finally itself: it swallows the exception in flight and overrides the value from try, so a failure disappears without a trace — Python 3.14 flags this with a SyntaxWarning. finally also runs when you leave via return: the value is computed first, then finally runs, and only then does the function actually return. For files, sockets and locks prefer with — a context manager does the same job with less room to forget the cleanup."
      },
      "syntax": "try:\n    ...\nfinally:\n    cleanup()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#finally",
      "version": "",
      "section": "Исключения",
      "subcat": "обработка",
      "color_group": "exc",
      "aliases": [
        "выполнится в любом случае",
        "гарантированное освобождение ресурсов",
        "закрыть файл даже при ошибке"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "def read_file(path):",
        "f = None",
        "try:",
        "f = open(path)",
        "return f.read()",
        "except FileNotFoundError:",
        "return None",
        "finally:",
        "if f:",
        "f.close()",
        "print('File handle closed')  # → всегда",
        "def divide(a, b):",
        "try:",
        "return a / b",
        "finally:",
        "print('division attempted')",
        "print(divide(10, 2))  # → division attempted / 5.0",
        "# finally при return",
        "def func():",
        "try:",
        "return 'try'",
        "finally:",
        "return 'finally'  # перекрывает return из try!",
        "print(func())  # → finally",
        "# finally при raise",
        "def func2():",
        "try:",
        "raise ValueError('oops')",
        "finally:",
        "print('cleanup')  # → cleanup (до распространения исключения)",
        "try:",
        "func2()",
        "except ValueError:",
        "pass",
        "# Классический паттерн с ресурсом",
        "import sqlite3",
        "conn = None",
        "try:",
        "conn = sqlite3.connect(':memory:')",
        "except Exception as e:",
        "print(e)",
        "finally:",
        "if conn:",
        "conn.close()",
        "print('Connection closed')  # → Connection closed"
      ],
      "related": [
        "try-except",
        "else-в-try-except",
        "__enter__-__exit__"
      ],
      "related_errors": []
    },
    {
      "id": "floatingpointerror",
      "title": "FloatingPointError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка операции с плавающей точкой. В CPython по умолчанию не возникает (нужна спец. настройка), но входит в иерархию.",
        "en": "A floating-point operation failed; not raised by default in CPython but part of the hierarchy."
      },
      "body": {
        "ru": "В современном CPython это исключение практически мёртвое: модуль fpectl, который его включал, удалён в 3.7, и обычная арифметика его не порождает. Реальные беды с плавающей точкой приходят под другими именами — деление на ноль даёт ZeroDivisionError, слишком большой результат OverflowError, math.sqrt(-1) — ValueError, а 0.0/0.0 через numpy или float('nan') вообще молча даёт nan. Так что except FloatingPointError в учебном коде просто никогда не сработает; проверяйте результат через math.isnan() и math.isinf().",
        "en": "In modern CPython this exception is essentially dead: the fpectl module that enabled it was removed in 3.7, and ordinary arithmetic never raises it. Real floating-point trouble arrives under other names — ZeroDivisionError for division by zero, OverflowError for a result too large, ValueError from math.sqrt(-1), while a NaN simply propagates silently. An except FloatingPointError branch in student code will never fire; test results with math.isnan() and math.isinf() instead."
      },
      "syntax": "raise FloatingPointError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#FloatingPointError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка вещественной арифметики",
        "сбой операции с плавающей точкой"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise FloatingPointError",
        "except FloatingPointError as e:",
        "    print(type(e).__name__)   # → FloatingPointError"
      ],
      "related": [
        "arithmeticerror",
        "zerodivisionerror",
        "overflowerror"
      ],
      "related_errors": []
    },
    {
      "id": "futurewarning",
      "title": "FutureWarning",
      "kind": "exception",
      "summary": {
        "ru": "Изменение поведения в будущем, предназначенное для конечных пользователей.",
        "en": "A coming behavior change aimed at end users."
      },
      "body": {
        "ru": "Это предупреждение, а не ошибка: его не поднимают через raise, а выдают через warnings.warn(..., FutureWarning), после чего программа спокойно продолжает работать. Отличие от DeprecationWarning — в адресате: FutureWarning виден по умолчанию и обращён к тем, кто просто пользуется библиотекой, а DeprecationWarning по умолчанию показывается только в __main__ и адресован разработчикам. Если хотите, чтобы такое предупреждение роняло прогон (например, в тестах), запускайте с -W error::FutureWarning.",
        "en": "This is a warning, not an error: you emit it with warnings.warn(..., FutureWarning) rather than raise, and execution continues afterwards. The split from DeprecationWarning is about audience — FutureWarning is shown by default and speaks to people merely using a library, while DeprecationWarning is hidden outside __main__ by default and speaks to developers. To make such a warning fail a run, say in tests, start Python with -W error::FutureWarning."
      },
      "syntax": "raise FutureWarning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#FutureWarning",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "предупреждение о будущих изменениях",
        "поведение изменится в новых версиях"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise FutureWarning",
        "except FutureWarning as e:",
        "    print(type(e).__name__)   # → FutureWarning"
      ],
      "related": [
        "deprecationwarning",
        "pendingdeprecationwarning",
        "warning"
      ],
      "related_errors": []
    },
    {
      "id": "generatorexit",
      "title": "GeneratorExit",
      "kind": "exception",
      "summary": {
        "ru": "Поднимается внутри генератора при его закрытии (.close()). Наследует BaseException.",
        "en": "Raised inside a generator when it is closed (.close()). Inherits BaseException."
      },
      "body": {
        "ru": "Оно наследуется от BaseException, а не от Exception, и это сделано намеренно: обычный except Exception внутри генератора его не перехватит и не помешает закрытию. Возникает в точке, где генератор остановился на yield, когда вызывают .close() или когда объект собирает сборщик мусора. Ловить его вручную почти никогда не нужно — для освобождения ресурсов хватает try/finally; а если после перехвата генератор снова сделает yield, интерпретатор поднимет RuntimeError о проигнорированном GeneratorExit.",
        "en": "It inherits from BaseException rather than Exception on purpose: a plain except Exception inside the generator will not swallow it and cannot block the shutdown. It surfaces at the yield where the generator is suspended, triggered by .close() or by garbage collection of the generator object. You rarely need to catch it — try/finally is enough for cleanup — and if the generator yields again after catching it, the interpreter raises RuntimeError about an ignored GeneratorExit."
      },
      "syntax": "raise GeneratorExit",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#GeneratorExit",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "закрытие генератора",
        "прерывание генератора"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise GeneratorExit",
        "except GeneratorExit as e:",
        "    print(type(e).__name__)   # → GeneratorExit"
      ],
      "related": [
        "generator-function-yield",
        "stopiteration",
        "baseexception"
      ],
      "related_errors": []
    },
    {
      "id": "importerror",
      "title": "ImportError",
      "kind": "exception",
      "summary": {
        "ru": "Вызывается, если импорт модуля не удался — модуль найден, но при загрузке произошла ошибка.",
        "en": "Raised when a module import fails — the module was found, but something went wrong while loading it."
      },
      "body": {
        "ru": "Главная путаница — с ModuleNotFoundError: он подкласс ImportError, поэтому except ImportError ловит сразу и «модуля вообще нет», и «модуль нашёлся, но при загрузке что-то упало». Отсюда грабли: try/except ImportError вокруг импорта необязательной зависимости молча проглотит настоящую поломку внутри самого модуля — его собственный битый импорт или опечатку в коде. У пойманного объекта есть атрибуты name и path, по ним видно, о каком именно модуле и файле речь.",
        "en": "The usual confusion is with ModuleNotFoundError: it is a subclass of ImportError, so except ImportError catches both \"no such module\" and \"module found, but it blew up while loading\". That is the trap — wrapping an optional dependency in try/except ImportError silently swallows a real failure inside that module, such as its own broken import or a typo in its code. The caught object carries name and path attributes telling you which module and file actually failed."
      },
      "syntax": "raise ImportError('сообщение')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ImportError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка импорта",
        "не удалось импортировать модуль",
        "сбой при загрузке модуля"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "from os import nonexistent  # ImportError",
        "try:",
        "    import some_module",
        "except ImportError:",
        "    print('модуль недоступен')"
      ],
      "related": [
        "modulenotfounderror",
        "sys.path",
        "sys.modules"
      ],
      "related_errors": []
    },
    {
      "id": "importwarning",
      "title": "ImportWarning",
      "kind": "exception",
      "summary": {
        "ru": "Возможная ошибка при импорте (по умолчанию скрыта).",
        "en": "A possible mistake in module import (hidden by default)."
      },
      "body": {
        "ru": "Это предупреждение, а не сбой: выполнение не прерывается, и по умолчанию сообщение вообще не показывается — стандартные фильтры warnings его отбрасывают. Увидеть его можно запуском python -W default или python -X dev. В прикладном коде его почти никогда не поднимают руками: ImportWarning выдаёт сама машинерия импорта, и говорит он о странностях с путями и пакетами, а не об ошибке в твоей строке import.",
        "en": "This is a warning, not a failure: execution continues, and by default you never see the message at all, because the standard warnings filters drop ImportWarning. Run python -W default or python -X dev to make it visible. You almost never raise it yourself — it comes from the import machinery and points at odd package or path setups rather than at a mistake in your own import statement."
      },
      "syntax": "raise ImportWarning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ImportWarning",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "предупреждение при импорте модуля",
        "скрытое предупреждение об импорте"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise ImportWarning",
        "except ImportWarning as e:",
        "    print(type(e).__name__)   # → ImportWarning"
      ],
      "related": [
        "importerror",
        "modulenotfounderror",
        "warning"
      ],
      "related_errors": []
    },
    {
      "id": "indentationerror",
      "title": "IndentationError",
      "kind": "exception",
      "summary": {
        "ru": "Некорректные отступы (подкласс SyntaxError).",
        "en": "Incorrect indentation (a subclass of SyntaxError)."
      },
      "body": {
        "ru": "Раз это подкласс SyntaxError, ошибка возникает ещё до запуска — на этапе компиляции файла. Поэтому обернуть собственный кривой отступ в try/except невозможно: перехват сработает только там, где код компилируется по ходу выполнения (exec, compile, импорт чужого модуля). Отдельный частый случай — TabError, подкласс IndentationError про смешанные табы и пробелы; лечится настройкой редактора на 4 пробела.",
        "en": "Because it subclasses SyntaxError, this is raised while the file is being compiled, before a single line runs. So you cannot wrap your own badly indented code in try/except — the handler only fires where code is compiled at runtime (exec, compile, importing another module). A common special case is TabError, a subclass of IndentationError for mixed tabs and spaces; setting your editor to four spaces makes it go away."
      },
      "syntax": "raise IndentationError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#IndentationError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка отступа",
        "неправильные отступы"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    exec(' x = 1')",
        "except IndentationError as e:",
        "    print(type(e).__name__)   # → IndentationError"
      ],
      "related": [
        "syntaxerror",
        "taberror"
      ],
      "related_errors": []
    },
    {
      "id": "indexerror",
      "title": "IndexError",
      "kind": "exception",
      "summary": {
        "ru": "Вызывается при обращении к элементу последовательности по несуществующему индексу.",
        "en": "Raised when a sequence is indexed with an index that does not exist."
      },
      "body": {
        "ru": "Отрицательные индексы считаются с конца, но границу имеют такую же: у списка из трёх элементов lst[-4] — тот же IndexError. А срезы, в отличие от индексации, не падают никогда: lst[5:10] на коротком списке просто вернёт пустой список — это частый источник тихих багов вместо честной ошибки. У словаря аналогичный промах — это KeyError; общий предок обоих LookupError, если нужно поймать разом.",
        "en": "Negative indices count from the end but hit the same wall: on a three-element list, lst[-4] raises IndexError just like lst[5]. Slicing, unlike indexing, never raises — lst[5:10] on a short list quietly returns an empty list, which hides bugs that an exception would have exposed. The dictionary counterpart is KeyError; both inherit from LookupError if you want to catch them together."
      },
      "syntax": "raise IndexError('сообщение')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#IndexError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "выход за границы списка",
        "индекс вне диапазона",
        "нет такого индекса"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "lst = [1, 2, 3]",
        "lst[5]               # IndexError",
        "lst[-1]              # 3 — ok",
        "try:",
        "    print(lst[10])",
        "except IndexError:",
        "    print('нет такого индекса')"
      ],
      "related": [
        "lookuperror",
        "keyerror",
        "индексирование-списка"
      ],
      "related_errors": []
    },
    {
      "id": "interruptederror",
      "title": "InterruptedError",
      "kind": "exception",
      "summary": {
        "ru": "Системный вызов был прерван сигналом (подкласс OSError). В современном Python обычно повторяется автоматически.",
        "en": "A system call was interrupted by a signal (a subclass of OSError)."
      },
      "body": {
        "ru": "После PEP 475 (Python 3.5) прерванные сигналом системные вызовы Python повторяет сам, поэтому вручную писать цикл «поймал — повторил вызов» больше не нужно, и в обычном коде это исключение практически не всплывает. Не путай с Ctrl+C: там прилетит KeyboardInterrupt, а не InterruptedError. Как подкласс OSError оно спокойно ловится общим except OSError вместе с остальными ошибками ввода-вывода.",
        "en": "Since PEP 475 (Python 3.5) the interpreter retries system calls interrupted by a signal on its own, so the old catch-and-retry loop is obsolete and you will rarely see this exception in ordinary code. Do not confuse it with Ctrl+C, which produces KeyboardInterrupt instead. Being a subclass of OSError, it is picked up by a plain except OSError along with other I/O failures."
      },
      "syntax": "raise InterruptedError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#InterruptedError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise InterruptedError",
        "except InterruptedError as e:",
        "    print(type(e).__name__)   # → InterruptedError"
      ],
      "related": [
        "oserror",
        "keyboardinterrupt"
      ],
      "related_errors": []
    },
    {
      "id": "io.unsupportedoperation",
      "title": "io.UnsupportedOperation",
      "kind": "exception",
      "summary": {
        "ru": "Операция не поддержана потоком (напр. запись в файл, открытый для чтения; подкласс OSError и ValueError).",
        "en": "Base class for I/O related errors"
      },
      "body": {
        "ru": "Возникает не из-за плохих данных, а из-за режима открытия: запись в файл, открытый на 'r', чтение из 'w', seek() по непозиционируемому потоку вроде stdin или пайпа. Вместо ловли исключения обычно правильнее спросить сам поток: writable(), readable(), seekable(). Учтите двойное наследование от OSError и ValueError — блок except ValueError перехватит его тоже, и это часто оказывается сюрпризом.",
        "en": "This is not about bad data but about the mode a stream was opened in: writing to a file opened for reading, reading from a write-only file, or seeking on a non-seekable stream such as stdin or a pipe. Rather than catching it, ask the stream first via writable(), readable() or seekable(). Note that it inherits from both OSError and ValueError, so an except ValueError block will swallow it, which surprises people."
      },
      "syntax": "raise io.UnsupportedOperation",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.UnsupportedOperation",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "файл открыт только для чтения",
        "нельзя писать в файл, открытый для чтения",
        "неподдерживаемая операция с файлом"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import io",
        "try:",
        "    raise io.UnsupportedOperation",
        "except io.UnsupportedOperation as e:",
        "    print(type(e).__name__)   # → UnsupportedOperation"
      ],
      "related": [
        "oserror",
        "valueerror",
        "open"
      ],
      "related_errors": []
    },
    {
      "id": "isadirectoryerror",
      "title": "IsADirectoryError",
      "kind": "exception",
      "summary": {
        "ru": "Файловая операция ожидала файл, но получила каталог (подкласс OSError).",
        "en": "A file operation expected a file but got a directory (a subclass of OSError)."
      },
      "body": {
        "ru": "Обычный источник — open() или os.remove(), которым вместо файла передали путь к каталогу: для каталогов нужны os.listdir(), os.rmdir() или shutil.rmtree(). Ошибка платформозависима: на Windows та же попытка чаще приходит как PermissionError, поэтому переносимый код ловит родительский OSError или заранее проверяет Path.is_file().",
        "en": "It usually comes from open() or os.remove() handed a directory path instead of a file: directories need os.listdir(), os.rmdir() or shutil.rmtree(). The class is platform-dependent — on Windows the same attempt typically surfaces as PermissionError, so portable code catches the parent OSError or checks Path.is_file() up front."
      },
      "syntax": "raise IsADirectoryError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#IsADirectoryError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "это папка, а не файл",
        "попытка открыть каталог как файл"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise IsADirectoryError",
        "except IsADirectoryError as e:",
        "    print(type(e).__name__)   # → IsADirectoryError"
      ],
      "related": [
        "notadirectoryerror",
        "oserror",
        "filenotfounderror"
      ],
      "related_errors": []
    },
    {
      "id": "json.decoder.jsondecodeerror",
      "title": "json.JSONDecodeError",
      "kind": "exception",
      "summary": {
        "ru": "Некорректный JSON при разборе: json.loads('{bad'). Несёт позицию ошибки (pos/lineno/colno).",
        "en": "Subclass of ValueError with the following additional properties:"
      },
      "body": {
        "ru": "Чаще всего разбираемая строка вообще не JSON: пустой ответ сервера, HTML-страница ошибки или питоновский repr словаря с одинарными кавычками — JSON требует двойные и запрещает висячую запятую. В обработчике полезны атрибуты pos, lineno, colno и doc: они показывают точное место обрыва, а не просто факт неудачи. Это подкласс ValueError, так что except ValueError ловит его заодно; ловить нужно только этот класс, если хотите отличить битый JSON от прочих ошибок.",
        "en": "Usually the text being parsed is not JSON at all: an empty response body, an HTML error page, or a Python dict repr with single quotes, while JSON demands double quotes and forbids trailing commas. In the handler, the pos, lineno, colno and doc attributes pinpoint where parsing broke instead of merely saying that it did. It is a ValueError subclass, so a broad except ValueError catches it too; catch this class specifically when you need to tell malformed JSON apart from other failures."
      },
      "syntax": "raise json.JSONDecodeError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/json.html#json.JSONDecodeError",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка разбора json",
        "некорректный json",
        "не удалось прочитать json"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import json",
        "try:",
        "    json.loads('{bad')",
        "except json.JSONDecodeError as e:",
        "    print(type(e).__name__)   # → JSONDecodeError"
      ],
      "related": [
        "json.loads",
        "json.load",
        "valueerror",
        "json.JSONDecoder"
      ],
      "related_errors": []
    },
    {
      "id": "keyboardinterrupt",
      "title": "KeyboardInterrupt",
      "kind": "exception",
      "summary": {
        "ru": "Пользователь прервал программу (Ctrl+C). Наследует BaseException, поэтому `except Exception` его не ловит.",
        "en": "The user interrupted execution (Ctrl+C). Inherits BaseException, so `except Exception` won't catch it."
      },
      "body": {
        "ru": "Главная ловушка — голый except: или except BaseException внутри цикла: он проглатывает Ctrl+C, и программа перестаёт останавливаться. Если перехват всё-таки нужен (закрыть файл, сохранить прогресс), сделай очистку и пробрось исключение дальше через raise. Прерывание приходит в главный поток и может возникнуть между любыми двумя инструкциями, так что состояние на момент перехвата бывает半 недоделанным.",
        "en": "The classic trap is a bare except: or except BaseException inside a loop — it swallows Ctrl+C and the program stops being interruptible. If you do need to catch it (close a file, save progress), clean up and re-raise. The interrupt lands in the main thread and can arrive between any two instructions, so whatever you were doing may be half-finished."
      },
      "syntax": "raise KeyboardInterrupt",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#KeyboardInterrupt",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "прерывание с клавиатуры",
        "остановка программы пользователем",
        "прервал выполнение вручную"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise KeyboardInterrupt",
        "except KeyboardInterrupt as e:",
        "    print(type(e).__name__)   # → KeyboardInterrupt"
      ],
      "related": [
        "baseexception",
        "systemexit",
        "exception"
      ],
      "related_errors": []
    },
    {
      "id": "keyerror",
      "title": "KeyError",
      "kind": "exception",
      "summary": {
        "ru": "Вызывается при обращении к несуществующему ключу словаря. Используй .get() чтобы избежать.",
        "en": "Raised when a dictionary key that does not exist is accessed. Use .get() to avoid it."
      },
      "body": {
        "ru": "Единственный аргумент исключения — сам ключ, поэтому print(e) покажет 'b' в кавычках и больше ничего: понятное сообщение придётся писать самому. И .get() не всегда правильный ответ — если ключ обязан присутствовать, значение по умолчанию лишь замаскирует опечатку и уронит программу позже и не в том месте; кстати, тот же KeyError бросает set.remove().",
        "en": "The exception carries only the key itself, so print(e) shows 'b' in quotes and nothing more — a human-readable message is on you. And .get() is not always the fix: when the key is supposed to be there, a silent default just hides a typo and blows up later somewhere else. The same KeyError is also raised by set.remove()."
      },
      "syntax": "raise KeyError(key)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#KeyError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "нет такого ключа",
        "ключ отсутствует в словаре",
        "обращение к несуществующему ключу"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "d = {'a': 1}",
        "d['b']               # KeyError: 'b'",
        "d.get('b', 0)        # 0 — безопасно",
        "try:",
        "    v = d['x']",
        "except KeyError:",
        "    v = None"
      ],
      "related": [
        "dict.get",
        "доступ-d-key",
        "lookuperror",
        "indexerror"
      ],
      "related_errors": []
    },
    {
      "id": "locale.error",
      "title": "locale.Error",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка модуля locale (напр. неизвестная локаль в setlocale).",
        "en": "Common base class for all non-exit exceptions"
      },
      "body": {
        "ru": "Главная причина — имя локали, которого нет в системе: строка вроде 'ru_RU.UTF-8' работает на Linux и macOS, а на Windows нужна 'Russian_Russia.1251', так что зашитое в код имя ломает переносимость. Разумная реакция — не падать, а поймать ошибку и остаться на локали по умолчанию (пустая строка '' берёт системную). Помните, что setlocale меняет состояние всего процесса и не потокобезопасен, поэтому дёргать его посреди работы программы — плохая идея.",
        "en": "The usual cause is a locale name the operating system does not know: 'ru_RU.UTF-8' works on Linux and macOS, while Windows expects something like 'Russian_Russia.1251', so a hardcoded name breaks portability. The sane reaction is to catch it and fall back to the default locale rather than crash (an empty string picks up the system setting). Keep in mind that setlocale mutates process-wide state and is not thread-safe, so calling it in the middle of a running program is asking for trouble."
      },
      "syntax": "raise locale.Error",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/locale.html#locale.Error",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка установки локали",
        "неизвестная локаль"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import locale",
        "try:",
        "    raise locale.Error",
        "except locale.Error as e:",
        "    print(type(e).__name__)   # → Error"
      ],
      "related": [],
      "related_errors": []
    },
    {
      "id": "lookuperror",
      "title": "LookupError",
      "kind": "exception",
      "summary": {
        "ru": "Базовый класс ошибок поиска по ключу/индексу: IndexError и KeyError.",
        "en": "Base class for lookup errors: IndexError and KeyError."
      },
      "body": {
        "ru": "Ловить его напрямую стоит только там, где код одинаково работает и с последовательностями, и со словарями; в остальных случаях конкретные IndexError или KeyError точнее и не прячут третий, неожиданный случай. Неочевидное: под LookupError попадает и неизвестное имя кодировки — 'abc'.encode('nosuch') падает именно им, а не ValueError.",
        "en": "Catch it directly only where the code works over sequences and mappings alike; elsewhere the specific IndexError or KeyError is sharper and won't hide a third, unexpected case. Less obvious: an unknown codec name also lands here — 'abc'.encode('nosuch') raises LookupError, not ValueError."
      },
      "syntax": "raise LookupError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#LookupError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "базовый класс ошибок поиска",
        "ошибка поиска по ключу или индексу"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise LookupError",
        "except LookupError as e:",
        "    print(type(e).__name__)   # → LookupError"
      ],
      "related": [
        "indexerror",
        "keyerror",
        "иерархия-исключений"
      ],
      "related_errors": []
    },
    {
      "id": "memoryerror",
      "title": "MemoryError",
      "kind": "exception",
      "summary": {
        "ru": "Операции не хватило памяти.",
        "en": "An operation ran out of memory."
      },
      "body": {
        "ru": "Обычно это не «в системе кончилась память», а одна слишком жадная операция: список на миллиард элементов, конкатенация гигантских строк, чтение большого файла целиком. Лечится переходом на генераторы и построчную обработку, а не блоком except. Рассчитывать на перехват вообще не стоит: на Linux с overcommit ядро чаще просто убивает процесс OOM-killer'ом, и до исключения дело не доходит.",
        "en": "It rarely means the machine is out of RAM — usually one greedy operation is to blame: a billion-element list, giant string concatenation, reading a huge file in one gulp. The fix is generators and streaming, not an except block. Don't count on catching it either: on Linux with overcommit the kernel tends to kill the process via the OOM killer before Python ever raises anything."
      },
      "syntax": "raise MemoryError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#MemoryError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "не хватает памяти",
        "закончилась память"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise MemoryError",
        "except MemoryError as e:",
        "    print(type(e).__name__)   # → MemoryError"
      ],
      "related": [
        "sys.getsizeof",
        "recursionerror"
      ],
      "related_errors": []
    },
    {
      "id": "modulenotfounderror",
      "title": "ModuleNotFoundError",
      "kind": "exception",
      "summary": {
        "ru": "Подкласс ImportError. Вызывается, когда модуль вообще не найден в sys.path. Python 3.6+.",
        "en": "A subclass of ImportError. Raised when the module is not found in sys.path at all. Python 3.6+."
      },
      "body": {
        "ru": "Чаще всего проблема не в том, что библиотеки нет, а в том, что она установлена не в тот интерпретатор, которым вы запускаете код: другой venv, другой pip, запуск из PyCharm против запуска из терминала. Отдельный except ModuleNotFoundError нужен редко — он подкласс ImportError, поэтому except ImportError его уже перехватывает. Разделять их имеет смысл, когда важно отличить «пакета нет вовсе» от «пакет есть, но внутри него нет нужного имени» — второе даёт чистый ImportError.",
        "en": "Usually the library is not missing at all — it is just installed for a different interpreter than the one running your code: another venv, another pip, IDE versus terminal. You rarely need to catch ModuleNotFoundError by name, since it is a subclass of ImportError and except ImportError already covers it. Separate them only when it matters whether the package is absent entirely or present but missing the name you asked for — the latter is a plain ImportError."
      },
      "syntax": "raise ModuleNotFoundError('No module named ...')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ModuleNotFoundError",
      "version": "3.6",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "модуль не найден",
        "нет такого модуля",
        "библиотека не установлена"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import nonexistent_lib      # ModuleNotFoundError",
        "try:",
        "    import pandas",
        "except ModuleNotFoundError:",
        "    print('установи: pip install pandas')"
      ],
      "related": [
        "importerror",
        "sys.path",
        "sys.modules"
      ],
      "related_errors": []
    },
    {
      "id": "nameerror",
      "title": "NameError",
      "kind": "exception",
      "summary": {
        "ru": "Вызывается при использовании имени переменной или функции, которая не определена в текущей области видимости.",
        "en": "Raised when a variable or function name that is not defined in the current scope is used."
      },
      "body": {
        "ru": "Обычно за ним стоит опечатка либо обращение к переменной раньше её присваивания. Отдельно стоит знать подкласс UnboundLocalError: если имя где-то присваивается внутри функции, оно считается локальным во всём теле, и чтение до присваивания не подхватит глобальную переменную, а упадёт. Начиная с Python 3.10 интерпретатор подсказывает похожие имена («Did you mean ...»), что почти всегда мгновенно вскрывает опечатку.",
        "en": "Most of the time this is a typo or a read that happens before the assignment. Worth knowing its subclass UnboundLocalError: if a name is assigned anywhere inside a function, it is local throughout the whole body, so reading it earlier fails instead of falling back to the global. Since Python 3.10 the interpreter suggests close matches (\"Did you mean ...\"), which usually exposes a typo immediately."
      },
      "syntax": "raise NameError('сообщение')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#NameError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "переменная не определена",
        "имя не определено",
        "опечатка в имени переменной"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "print(x)             # NameError: name 'x' is not defined",
        "def f():",
        "    return undefined_var  # NameError"
      ],
      "related": [
        "unboundlocalerror",
        "attributeerror",
        "локальные-и-глобальные-переменные"
      ],
      "related_errors": []
    },
    {
      "id": "notadirectoryerror",
      "title": "NotADirectoryError",
      "kind": "exception",
      "summary": {
        "ru": "Операция ожидала каталог, но получила не-каталог (подкласс OSError).",
        "en": "An operation expected a directory but got a non-directory (a subclass of OSError)."
      },
      "body": {
        "ru": "Возникает не на пустом месте, а когда путь, обязанный быть каталогом, на деле оказывается файлом: os.listdir('data.txt'), os.scandir по файлу, открытие 'data.txt/inner.txt', где промежуточный компонент — обычный файл. Разумная реакция — проверить Path.is_dir() до операции, а в except ловить более общий OSError: на разных ОС одна и та же ситуация может прилететь другим подклассом (например, FileNotFoundError).",
        "en": "It shows up when a path that has to be a directory turns out to be a file: os.listdir('data.txt'), os.scandir on a file, or opening 'data.txt/inner.txt' where an intermediate component is a regular file. Check Path.is_dir() before the operation, and prefer catching the broader OSError, because the same situation can surface as a different subclass on another OS (FileNotFoundError, for instance)."
      },
      "syntax": "raise NotADirectoryError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#NotADirectoryError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ожидался каталог, а получен файл",
        "это не папка"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise NotADirectoryError",
        "except NotADirectoryError as e:",
        "    print(type(e).__name__)   # → NotADirectoryError"
      ],
      "related": [
        "isadirectoryerror",
        "oserror",
        "filenotfounderror",
        "os.path.isdir"
      ],
      "related_errors": []
    },
    {
      "id": "notimplementederror",
      "title": "NotImplementedError",
      "kind": "exception",
      "summary": {
        "ru": "Вызывается в абстрактных методах, которые обязаны быть переопределены в подклассах. Показывает, что метод намеренно не реализован.",
        "en": "Raised in abstract methods that subclasses are required to override. It shows that the method is deliberately left unimplemented."
      },
      "body": {
        "ru": "Не путайте с NotImplemented — это отдельный синглтон, который методы вроде __eq__ и __add__ возвращают (а не бросают), чтобы Python попробовал зеркальную операцию у второго операнда. И это не полноценный способ объявить абстракцию: заглушка сработает только в момент вызова метода, тогда как abc.ABC с @abstractmethod не даст даже создать экземпляр недописанного подкласса. Формально NotImplementedError — подкласс RuntimeError.",
        "en": "Do not confuse it with NotImplemented: that is a separate singleton which methods like __eq__ and __add__ return (never raise) so Python can try the reflected operation on the other operand. It is also a weak way to declare an abstraction — the stub only fires when the method is actually called, while abc.ABC with @abstractmethod refuses to instantiate an incomplete subclass at all. In the hierarchy NotImplementedError sits under RuntimeError."
      },
      "syntax": "raise NotImplementedError('переопредели метод')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#NotImplementedError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "метод не реализован",
        "нужно переопределить метод",
        "заглушка метода"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "class Shape:",
        "    def area(self):",
        "        raise NotImplementedError('area() must be implemented')",
        "class Circle(Shape):",
        "    def area(self):",
        "        return 3.14 * self.r ** 2"
      ],
      "related": [
        "abc.abstractmethod",
        "абстрактные-классы",
        "runtimeerror"
      ],
      "related_errors": []
    },
    {
      "id": "oserror",
      "title": "OSError",
      "kind": "exception",
      "summary": {
        "ru": "Базовый класс для ошибок операционной системы: работа с файлами, сетью, процессами. FileNotFoundError и PermissionError — его подклассы.",
        "en": "The base class for operating system errors: files, network, processes. FileNotFoundError and PermissionError are its subclasses."
      },
      "body": {
        "ru": "Ловить сам OSError стоит скорее как последнюю сетку: он объединяет десятки разных ситуаций, и в except почти всегда точнее назвать конкретный подкласс — FileNotFoundError, PermissionError, IsADirectoryError. Если разбирать нужно именно код ошибки, у объекта есть атрибуты errno, strerror и filename; сравнивайте errno с константами модуля errno, а не с текстом сообщения — он зависит от ОС и локали. Старые имена IOError и EnvironmentError начиная с Python 3.3 — просто синонимы OSError, отдельных классов за ними нет.",
        "en": "Catch OSError itself only as a safety net: it covers dozens of unrelated failures, and an except clause is almost always sharper when it names the concrete subclass — FileNotFoundError, PermissionError, IsADirectoryError. When you really need to branch on the cause, use the errno, strerror and filename attributes, and compare errno against constants from the errno module rather than parsing the message, which varies by OS and locale. The old names IOError and EnvironmentError have been plain aliases of OSError since Python 3.3, not separate classes."
      },
      "syntax": "raise OSError(errno, strerror)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#OSError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка операционной системы",
        "системная ошибка ввода-вывода"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import os",
        "os.remove('no_file')  # FileNotFoundError (подкласс OSError)",
        "try:",
        "    os.mkdir('/root/test')",
        "except OSError as e:",
        "    print(e.strerror)"
      ],
      "related": [
        "filenotfounderror",
        "permissionerror",
        "иерархия-исключений",
        "exception"
      ],
      "related_errors": []
    },
    {
      "id": "overflowerror",
      "title": "OverflowError",
      "kind": "exception",
      "summary": {
        "ru": "Результат арифметической операции слишком велик для типа (обычно float): math.exp(1000).",
        "en": "The result of an arithmetic operation is too large to represent (typically for float)."
      },
      "body": {
        "ru": "Целые в Python неограниченной точности, поэтому обычная int-арифметика такого исключения не даёт — оно про float и си-шные типы под капотом. Причём переполнение самого float молчаливое: 1e308 * 10 просто вернёт inf, зато функции math и попытка перевести гигантский int во float честно бросают OverflowError. Оборачивать в try имеет смысл конкретное вычисление, а не всю программу.",
        "en": "Python integers are unbounded, so plain int arithmetic never overflows; this exception belongs to floats and to the C-level types underneath. Note the asymmetry: float arithmetic overflows silently to inf (1e308 * 10 raises nothing), while math functions and float() on a huge int do raise OverflowError. Wrap the specific computation, not the whole program."
      },
      "syntax": "raise OverflowError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#OverflowError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "переполнение числа",
        "слишком большое число",
        "результат не помещается в тип"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise OverflowError",
        "except OverflowError as e:",
        "    print(type(e).__name__)   # → OverflowError"
      ],
      "related": [
        "arithmeticerror",
        "floatingpointerror",
        "zerodivisionerror"
      ],
      "related_errors": []
    },
    {
      "id": "pendingdeprecationwarning",
      "title": "PendingDeprecationWarning",
      "kind": "exception",
      "summary": {
        "ru": "Возможность устареет в будущем (мягче DeprecationWarning).",
        "en": "A feature that will be deprecated in the future."
      },
      "body": {
        "ru": "Это предупреждение, а не ошибка: программа не падает, и по умолчанию сообщение вообще не печатается — увидеть его можно, запустив интерпретатор с -W always или настроив фильтр в warnings. На практике встречается редко: для уже устаревшего API берут DeprecationWarning, а PendingDeprecationWarning остаётся для «пока работает, но когда-нибудь объявим устаревшим».",
        "en": "This is a warning, not an error: nothing crashes, and by default the message is not even printed — run Python with -W always or set a filter via warnings to see it. It is rarely used today; already-deprecated APIs emit DeprecationWarning, and PendingDeprecationWarning is reserved for \"still fine, but we may deprecate it later\"."
      },
      "syntax": "raise PendingDeprecationWarning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#PendingDeprecationWarning",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "предупреждение о скором устаревании",
        "устареет в будущих версиях"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise PendingDeprecationWarning",
        "except PendingDeprecationWarning as e:",
        "    print(type(e).__name__)   # → PendingDeprecationWarning"
      ],
      "related": [
        "deprecationwarning",
        "futurewarning",
        "warning"
      ],
      "related_errors": []
    },
    {
      "id": "permissionerror",
      "title": "PermissionError",
      "kind": "exception",
      "summary": {
        "ru": "Вызывается при попытке выполнить операцию без необходимых прав доступа к файлу или ресурсу.",
        "en": "Raised on an attempt to perform an operation without the access rights the file or resource requires."
      },
      "body": {
        "ru": "Подкласс OSError, который прилетает в момент самой операции (open, os.remove, запись), а не заранее — поэтому проверять права через os.access до открытия и бесполезно, и опасно: между проверкой и открытием всё может измениться, надёжнее обернуть в try сам вызов. На Windows тот же PermissionError встречается в неожиданных местах: при open() каталога и при попытке удалить или переименовать файл, который держит открытым другая программа.",
        "en": "A subclass of OSError, raised by the operation itself (open, os.remove, a write) and never in advance — which makes an os.access pre-check both useless and racy; wrap the real call in try instead. On Windows it also shows up in surprising places: opening a directory with open(), or deleting or renaming a file that another program still holds open."
      },
      "syntax": "raise PermissionError('нет доступа')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#PermissionError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "нет доступа к файлу",
        "отказано в доступе",
        "нет прав на запись"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "with open('/etc/shadow') as f:",
        "pass",
        "except PermissionError:",
        "print('нет прав на чтение')"
      ],
      "related": [
        "oserror",
        "filenotfounderror",
        "os.access"
      ],
      "related_errors": []
    },
    {
      "id": "processlookuperror",
      "title": "ProcessLookupError",
      "kind": "exception",
      "summary": {
        "ru": "Указанный процесс не существует (подкласс OSError).",
        "en": "The given process does not exist (a subclass of OSError)."
      },
      "body": {
        "ru": "Соответствует POSIX-ошибке ESRCH и прилетает из os.kill или os.waitpid, когда процесса с таким pid уже нет — как правило, он успел завершиться между вашей проверкой и сигналом. Именно из-за этой гонки правильный приём — послать сигнал и спокойно поймать ProcessLookupError, а не «сначала убедиться, что процесс жив». И помните про переиспользование pid: отсутствие ошибки не доказывает, что вы попали именно в нужный процесс.",
        "en": "Maps to the POSIX ESRCH error and comes out of os.kill or os.waitpid when no process with that pid exists any more — usually because it exited between your check and your signal. Because of that race, the right pattern is to send the signal and catch ProcessLookupError rather than to test \"is it alive?\" first. Keep pid reuse in mind too: no error does not prove you hit the process you meant."
      },
      "syntax": "raise ProcessLookupError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ProcessLookupError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "процесс не найден",
        "нет такого процесса"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise ProcessLookupError",
        "except ProcessLookupError as e:",
        "    print(type(e).__name__)   # → ProcessLookupError"
      ],
      "related": [
        "os.kill",
        "oserror",
        "childprocesserror"
      ],
      "related_errors": []
    },
    {
      "id": "pythonfinalizationerror",
      "title": "PythonFinalizationError",
      "kind": "exception",
      "summary": {
        "ru": "Операция запрещена во время финализации интерпретатора (напр. запуск нового потока при завершении, 3.13+).",
        "en": "An operation is blocked during interpreter finalization (3.13+)."
      },
      "body": {
        "ru": "Появился в 3.13 и наследует RuntimeError; возникает, когда во время выключения интерпретатора код пытается сделать запрещённое — запустить новый поток, форкнуться, создать подпроцесс. Обычно виноват код, доделывающий работу слишком поздно: __del__, обработчик atexit или недожитый daemon-поток. Лечится не try/except, а тем, чтобы завершить и присоединить фоновые задачи до выхода; на версиях до 3.13 та же ситуация выглядит как обычный RuntimeError.",
        "en": "New in 3.13 and a subclass of RuntimeError; it fires when code attempts something forbidden while the interpreter is shutting down — starting a thread, forking, spawning a subprocess. The usual culprit is work scheduled too late: a __del__, an atexit hook, or a daemon thread still trying to finish. The cure is finishing and joining background work before exit rather than catching the error; before 3.13 the same situation surfaces as a plain RuntimeError."
      },
      "syntax": "raise PythonFinalizationError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#PythonFinalizationError",
      "version": "3.13",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка при завершении интерпретатора",
        "нельзя запустить поток при выходе из программы"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise PythonFinalizationError",
        "except PythonFinalizationError as e:",
        "    print(type(e).__name__)   # → PythonFinalizationError"
      ],
      "related": [
        "runtimeerror",
        "systemexit",
        "threading-thread"
      ],
      "related_errors": []
    },
    {
      "id": "raise",
      "title": "raise",
      "kind": "exception",
      "summary": {
        "ru": "Генерирует исключение вручную. raise from — цепочка исключений. re-raise — raise без аргументов.",
        "en": "Raises an exception by hand. raise ... from chains exceptions together. A bare raise re-raises the current one."
      },
      "body": {
        "ru": "Голый raise допустим только внутри обработчика (иначе RuntimeError: No active exception to re-raise) и сохраняет исходный traceback — привычка писать raise e вместо него лишь добавляет лишний кадр стека. Внутри except Python и так сцепляет исключения сам («During handling of the above exception, another exception occurred»); from нужен, чтобы назвать причину явно, а from None — чтобы спрятать предыдущее исключение из вывода. Бросать можно и класс, и экземпляр: raise ValueError и raise ValueError() равнозначны, класс инстанцируется автоматически.",
        "en": "A bare raise is legal only inside an exception handler (otherwise RuntimeError: No active exception to re-raise) and it preserves the original traceback — writing raise e instead just adds an extra stack frame. Inside except, Python already chains exceptions on its own (\"During handling of the above exception, another exception occurred\"); from states the cause explicitly, and from None hides the earlier exception from the report. You may raise either a class or an instance: raise ValueError and raise ValueError() are equivalent, the class gets instantiated for you."
      },
      "syntax": "raise ValueError('msg')\nraise RuntimeError('msg') from original",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/simple_stmts.html#raise",
      "version": "",
      "section": "Исключения",
      "subcat": "генерация",
      "color_group": "exc",
      "aliases": [
        "выбросить исключение",
        "сгенерировать ошибку вручную",
        "проброс исключения"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "def check_age(age):",
        "if age < 0:",
        "raise ValueError(f'Age cannot be negative: {age}')",
        "return age",
        "try:",
        "check_age(-1)",
        "except ValueError as e:",
        "print(e)  # → Age cannot be negative: -1",
        "# raise from",
        "try:",
        "int('abc')",
        "except ValueError as e:",
        "raise RuntimeError('conversion failed') from e",
        "# Re-raise",
        "def safe():",
        "try:",
        "return 1/0",
        "except ZeroDivisionError:",
        "print('caught, re-raising')",
        "raise  # передаёт исходное исключение",
        "try:",
        "safe()",
        "except ZeroDivisionError:",
        "print('handled')  # → caught / handled",
        "# raise с кастомным классом",
        "class ValidationError(ValueError):",
        "pass",
        "def validate(x):",
        "if x < 0:",
        "raise ValidationError(f'Invalid value: {x}')",
        "try:",
        "validate(-5)",
        "except ValidationError as e:",
        "print(e)  # → Invalid value: -5",
        "# raise без аргументов в except",
        "try:",
        "try:",
        "1/0",
        "except ZeroDivisionError:",
        "raise  # пробрасываем",
        "except ZeroDivisionError:",
        "print('outer caught')  # → outer caught",
        "# Цепочка __cause__ и __context__",
        "try:",
        "raise ValueError('first') from TypeError('cause')",
        "except ValueError as e:",
        "print(type(e.__cause__).__name__)  # → TypeError"
      ],
      "related": [
        "try-except",
        "пользовательские-исключения",
        "exception",
        "assert"
      ],
      "related_errors": []
    },
    {
      "id": "re.patternerror",
      "title": "re.PatternError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка компиляции регулярного выражения: re.compile('['). Синоним — re.error.",
        "en": "Exception raised for invalid regular expressions"
      },
      "body": {
        "ru": "Ошибка возникает на этапе компиляции шаблона, а не при сопоставлении, поэтому проявляется при первом же re.compile/re.search — типично из-за незакрытой скобки, повисшего квантификатора или пользовательского текста, вставленного в шаблон без re.escape(). Атрибуты msg, pattern и pos покажут, на каком именно символе шаблона всё сломалось. Имя re.PatternError появилось только в Python 3.13; в более ранних версиях есть лишь re.error, который остаётся рабочим синонимом и сейчас.",
        "en": "It fires while the pattern is being compiled, not while matching, so it shows up on the very first re.compile or re.search — typically from an unclosed bracket, a dangling quantifier, or user text spliced into a pattern without re.escape(). The msg, pattern and pos attributes tell you exactly which character of the pattern broke. The name re.PatternError only exists from Python 3.13 onward; earlier versions have re.error, which is still a valid alias today."
      },
      "syntax": "raise re.PatternError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.PatternError",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка в регулярном выражении",
        "неверный шаблон регулярного выражения",
        "регулярка не компилируется"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import re",
        "try:",
        "    re.compile('[')",
        "except re.PatternError as e:",
        "    print(type(e).__name__)   # → PatternError"
      ],
      "related": [
        "re.compile",
        "спецсимволы-паттернов",
        "re.escape",
        "re.Pattern"
      ],
      "related_errors": []
    },
    {
      "id": "recursionerror",
      "title": "RecursionError",
      "kind": "exception",
      "summary": {
        "ru": "Вызывается при превышении максимальной глубины рекурсии (по умолчанию 1000). Подкласс RuntimeError.",
        "en": "Raised when the maximum recursion depth (1000 by default) is exceeded. A subclass of RuntimeError."
      },
      "body": {
        "ru": "Почти всегда причина не в маленьком лимите, а в пропущенном или недостижимом базовом случае; поднимать sys.setrecursionlimit() безопасно только если рекурсия точно конечная — иначе процесс упадёт уже по-настоящему, без шанса перехватить. Лимит один на весь стек интерпретатора, поэтому кадры библиотечных вызовов расходуют его наравне с вашими. И раз это подкласс RuntimeError, except RuntimeError поймает его заодно — иногда неожиданно.",
        "en": "Nine times out of ten the cause is a missing or unreachable base case, not a limit that is too low; raising sys.setrecursionlimit() is safe only when the recursion is genuinely finite, otherwise the process crashes for real, past the point where except can help. The limit covers the whole interpreter stack, so frames from library calls eat into the same budget as yours. Being a RuntimeError subclass, it is also swallowed by a broad except RuntimeError."
      },
      "syntax": "raise RecursionError('...')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#RecursionError",
      "version": "3.5",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "превышена глубина рекурсии",
        "бесконечная рекурсия",
        "слишком глубокая рекурсия"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "def infinite():",
        "    return infinite()",
        "try:",
        "    infinite()",
        "except RecursionError:",
        "    print('слишком глубокая рекурсия')",
        "    import sys",
        "    sys.setrecursionlimit(2000)  # изменить лимит"
      ],
      "related": [
        "рекурсия",
        "sys.setrecursionlimit",
        "runtimeerror"
      ],
      "related_errors": []
    },
    {
      "id": "referenceerror",
      "title": "ReferenceError",
      "kind": "exception",
      "summary": {
        "ru": "Обращение к weakref-прокси на уже удалённый объект.",
        "en": "Accessing a weak-reference proxy to an object that has been garbage-collected."
      },
      "body": {
        "ru": "Порождает его только weakref.proxy — обращение к прокси после того, как исходный объект уже собран сборщиком мусора. Обычный weakref.ref ведёт себя иначе: вызов ref() просто вернёт None, поэтому его результат проверяют на None, а не оборачивают в try. И это не аналог ReferenceError из JavaScript: за обращение к несуществующему имени в Python отвечает NameError.",
        "en": "Only weakref.proxy produces it: touching the proxy after its referent has been garbage-collected. A plain weakref.ref behaves differently — calling it simply returns None, so you check the result instead of wrapping the access in try. It is also unrelated to JavaScript's ReferenceError; an undefined name in Python raises NameError."
      },
      "syntax": "raise ReferenceError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ReferenceError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "обращение к удалённому объекту",
        "слабая ссылка на уничтоженный объект"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise ReferenceError",
        "except ReferenceError as e:",
        "    print(type(e).__name__)   # → ReferenceError"
      ],
      "related": [],
      "related_errors": []
    },
    {
      "id": "resourcewarning",
      "title": "ResourceWarning",
      "kind": "exception",
      "summary": {
        "ru": "Не освобождённый ресурс (напр. незакрытый файл/сокет; по умолчанию скрыт).",
        "en": "A resource was not released (e.g. an unclosed file; hidden by default)."
      },
      "body": {
        "ru": "Стандартные фильтры предупреждений его глушат, так что незакрытый файл вы, скорее всего, просто не увидите — запускайте python -X dev или -W default, когда подозреваете утечку дескрипторов. Сообщение приходит в момент, когда сборщик мусора добирается до объекта, поэтому указанное место часто не совпадает с тем, где файл открывали. Лечится это не перехватом, а with.",
        "en": "The default warning filters silence it, so an unclosed file usually leaves no trace — run with python -X dev or -W default when you suspect leaking descriptors. It fires whenever the garbage collector finally reaches the object, so the reported location rarely matches where the file was opened. The fix is a with block, never an except."
      },
      "syntax": "raise ResourceWarning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ResourceWarning",
      "version": "3.2",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "незакрытый файл",
        "утечка ресурсов"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise ResourceWarning",
        "except ResourceWarning as e:",
        "    print(type(e).__name__)   # → ResourceWarning"
      ],
      "related": [
        "open",
        "__enter__-__exit__",
        "warning"
      ],
      "related_errors": []
    },
    {
      "id": "runtimeerror",
      "title": "RuntimeError",
      "kind": "exception",
      "summary": {
        "ru": "Общая ошибка времени выполнения, не подходящая под другие категории.",
        "en": "A generic runtime error that doesn't fit other categories."
      },
      "body": {
        "ru": "Ловить его как «что-то сломалось» — плохая идея: RecursionError и NotImplementedError его подклассы, и except RuntimeError проглотит их заодно. Прилетает он и из вполне конкретных ситуаций: dictionary changed size during iteration (словарь изменили прямо в цикле по нему) или generator raised StopIteration. Поднимать свой RuntimeError стоит только когда ни один специализированный класс не подходит.",
        "en": "Using it as a catch-all is a trap: RecursionError and NotImplementedError are subclasses, so except RuntimeError swallows them too. It also arrives from very concrete situations — dictionary changed size during iteration when you mutate a dict inside a loop over it, or generator raised StopIteration. Raise your own only when no more specific exception class fits."
      },
      "syntax": "raise RuntimeError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#RuntimeError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка времени выполнения",
        "ошибка выполнения"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise RuntimeError",
        "except RuntimeError as e:",
        "    print(type(e).__name__)   # → RuntimeError"
      ],
      "related": [
        "recursionerror",
        "notimplementederror",
        "exception",
        "иерархия-исключений"
      ],
      "related_errors": []
    },
    {
      "id": "runtimewarning",
      "title": "RuntimeWarning",
      "kind": "exception",
      "summary": {
        "ru": "Сомнительное поведение во время выполнения.",
        "en": "Dubious runtime behavior."
      },
      "body": {
        "ru": "В отличие от ResourceWarning показывается по умолчанию, но лишь один раз для каждого места в коде, поэтому внутри цикла легко решить, что проблема исчезла. Самый частый случай в учебном коде — coroutine ... was never awaited: корутину вызвали, но не дождались через await или asyncio.run, и её тело просто не выполнилось. Перехватывать предупреждение бессмысленно, чинить надо причину.",
        "en": "Unlike ResourceWarning it is shown by default, but only once per source location, so inside a loop it can look as if the problem went away. The classic case in student code is coroutine ... was never awaited: the coroutine was called but never driven by await or asyncio.run, so its body never ran. Catching the warning achieves nothing; fix what triggered it."
      },
      "syntax": "raise RuntimeWarning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#RuntimeWarning",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "предупреждение времени выполнения",
        "предупреждение о сомнительном поведении"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise RuntimeWarning",
        "except RuntimeWarning as e:",
        "    print(type(e).__name__)   # → RuntimeWarning"
      ],
      "related": [
        "warning",
        "userwarning",
        "deprecationwarning"
      ],
      "related_errors": []
    },
    {
      "id": "shutil.error",
      "title": "shutil.Error",
      "kind": "exception",
      "summary": {
        "ru": "Базовая ошибка операций shutil (напр. copytree собрала несколько ошибок).",
        "en": "Base class for I/O related errors"
      },
      "body": {
        "ru": "Это про многофайловые операции: copytree не останавливается на первом сбое, а копирует что может и в конце бросает одну Error, у которой args[0] — список кортежей (источник, назначение, причина). Значит, разбирать надо именно этот список, а не одно сообщение, иначе потеряете часть отказов. Класс наследует OSError, поэтому общий except OSError его тоже перехватит.",
        "en": "This is about multi-file operations: copytree does not stop at the first failure but copies what it can and finally raises a single Error whose args[0] is a list of (source, destination, reason) tuples. So inspect that list instead of a single message, otherwise you silently lose some of the failures. The class derives from OSError, so a broad except OSError will catch it as well."
      },
      "syntax": "raise shutil.Error",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/shutil.html#shutil.Error",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка копирования файлов",
        "ошибка при копировании каталога"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import shutil",
        "try:",
        "    raise shutil.Error",
        "except shutil.Error as e:",
        "    print(type(e).__name__)   # → Error"
      ],
      "related": [
        "shutil.samefileerror",
        "shutil.specialfileerror",
        "shutil.readerror",
        "oserror"
      ],
      "related_errors": []
    },
    {
      "id": "shutil.execerror",
      "title": "shutil.ExecError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка выполнения внешней команды в shutil.",
        "en": "Raised when a command could not be executed"
      },
      "body": {
        "ru": "Реликт тех времён, когда make_archive паковал архивы внешними tar и zip: в актуальном CPython ни одна функция shutil его не возбуждает. В Python 3.14 само обращение к shutil.ExecError даёт DeprecationWarning и возвращает обычный RuntimeError (до 3.13 это был отдельный подкласс OSError), а в 3.16 имя уберут совсем. Писать под него except в новом коде смысла нет.",
        "en": "A leftover from the days when make_archive shelled out to external tar and zip commands: no function in today's shutil raises it. In Python 3.14 merely touching shutil.ExecError emits a DeprecationWarning and hands back plain RuntimeError (through 3.13 it was its own OSError subclass), and the name is scheduled for removal in 3.16. There is nothing worth catching here in new code."
      },
      "syntax": "raise shutil.ExecError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/shutil.html#shutil.Error",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import shutil",
        "try:",
        "    raise shutil.ExecError",
        "except shutil.ExecError as e:",
        "    print(type(e).__name__)   # → ExecError"
      ],
      "related": [
        "shutil.error",
        "shutil.readerror",
        "shutil.registryerror"
      ],
      "related_errors": []
    },
    {
      "id": "shutil.readerror",
      "title": "shutil.ReadError",
      "kind": "exception",
      "summary": {
        "ru": "Архив не распознан/повреждён при unpack_archive.",
        "en": "Raised when an archive cannot be read"
      },
      "body": {
        "ru": "unpack_archive выбирает распаковщик по расширению имени файла, а не по содержимому: валидный zip, переименованный в data.bin, упадёт с ReadError и текстом про неизвестный формат. Если расширение нестандартное, укажите format явно ('zip', 'gztar' и т.д.) — тогда угадывание пропускается. Второй источник той же ошибки — расширение подошло, но внутри не архив: её поднимает уже сам zip- или tar-распаковщик.",
        "en": "unpack_archive picks an unpacker by the file name extension, never by content, so a perfectly valid zip renamed to data.bin fails with ReadError about an unknown format. When the extension is non-standard, pass format explicitly ('zip', 'gztar' and so on) to skip the guessing step. The same error also comes from the zip and tar unpackers themselves when the extension matched but the bytes are not an archive."
      },
      "syntax": "raise shutil.ReadError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/shutil.html#shutil.Error",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "не удалось распаковать архив",
        "повреждённый архив"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import shutil",
        "try:",
        "    raise shutil.ReadError",
        "except shutil.ReadError as e:",
        "    print(type(e).__name__)   # → ReadError"
      ],
      "related": [
        "shutil.registryerror",
        "shutil.error",
        "shutil.execerror"
      ],
      "related_errors": []
    },
    {
      "id": "shutil.registryerror",
      "title": "shutil.RegistryError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка реестра форматов архивов/распаковщиков shutil.",
        "en": "Raised when a registry operation with the archiving\nand unpacking registries fails"
      },
      "body": {
        "ru": "На практике единственный источник — register_unpack_format с расширением, которое уже занято другим распаковщиком (.zip, .tar, .tar.gz и прочие заняты из коробки). В отличие от остальных ошибок shutil он наследуется прямо от Exception, а не от OSError, поэтому except OSError его не поймает. Опечатка в имени формата у make_archive или unpack_archive даёт не его, а ValueError.",
        "en": "In practice the only source is register_unpack_format with an extension another unpacker has already claimed (.zip, .tar, .tar.gz and friends are taken out of the box). Unlike the rest of shutil's errors it inherits straight from Exception rather than OSError, so an except OSError clause will let it through. A misspelled format name passed to make_archive or unpack_archive raises ValueError instead."
      },
      "syntax": "raise shutil.RegistryError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/shutil.html#shutil.Error",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import shutil",
        "try:",
        "    raise shutil.RegistryError",
        "except shutil.RegistryError as e:",
        "    print(type(e).__name__)   # → RegistryError"
      ],
      "related": [
        "shutil.readerror",
        "shutil.error",
        "shutil.execerror"
      ],
      "related_errors": []
    },
    {
      "id": "shutil.samefileerror",
      "title": "shutil.SameFileError",
      "kind": "exception",
      "summary": {
        "ru": "Источник и приёмник copy() — один и тот же файл (подкласс shutil.Error).",
        "en": "Raised when source and destination are the same file"
      },
      "body": {
        "ru": "Совпадение проверяется по устройству и inode, а не по строке пути: симлинк, жёсткая ссылка или './data.txt' против абсолютного пути — это один и тот же файл. Ошибка спасает данные: без неё copyfile открыл бы приёмник на запись и обнулил исходник. Частый способ на неё наткнуться — copy(файл, каталог), когда файл уже лежит в этом каталоге: приёмник достраивается до каталог/имя и совпадает с источником.",
        "en": "Sameness is decided by device and inode, not by comparing path strings, so a symlink, a hard link or './data.txt' versus its absolute form all count as one file. The check is there to protect your data: without it copyfile would open the destination for writing and truncate the original to zero. A common way to hit it is copy(file, directory) when the file already lives in that directory — the destination expands to directory/name and lands on the source."
      },
      "syntax": "raise shutil.SameFileError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/shutil.html#shutil.SameFileError",
      "version": "3.4",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "копирование файла в самого себя",
        "исходный и целевой файл совпадают"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import shutil",
        "try:",
        "    raise shutil.SameFileError",
        "except shutil.SameFileError as e:",
        "    print(type(e).__name__)   # → SameFileError"
      ],
      "related": [
        "shutil.error",
        "os.path.samefile",
        "shutil.specialfileerror"
      ],
      "related_errors": []
    },
    {
      "id": "shutil.specialfileerror",
      "title": "shutil.SpecialFileError",
      "kind": "exception",
      "summary": {
        "ru": "Операция над специальным файлом (сокет/устройство), который нельзя скопировать как обычный.",
        "en": "Raised when trying to do a kind of operation (e.g"
      },
      "body": {
        "ru": "copyfile отсеивает только именованные каналы (FIFO) — сокеты и файлы-устройства не проверяются, так что копирование чего-нибудь вроде /dev/zero просто будет читать бесконечно. Класс наследуется от OSError, а не от shutil.Error, поэтому except shutil.Error его пропустит. На Windows встретить его практически негде: FIFO там не объекты файловой системы.",
        "en": "copyfile only screens for named pipes (FIFOs); sockets and device files are not checked, so copying something like /dev/zero will happily read forever. The class derives from OSError rather than shutil.Error, which means an except shutil.Error clause misses it. On Windows you will essentially never see it, since FIFOs are not filesystem objects there."
      },
      "syntax": "raise shutil.SpecialFileError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/shutil.html#shutil.SpecialFileError",
      "version": "2.7",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import shutil",
        "try:",
        "    raise shutil.SpecialFileError",
        "except shutil.SpecialFileError as e:",
        "    print(type(e).__name__)   # → SpecialFileError"
      ],
      "related": [
        "shutil.error",
        "shutil.samefileerror",
        "os.stat"
      ],
      "related_errors": []
    },
    {
      "id": "signal.itimererror",
      "title": "signal.ItimerError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка setitimer/getitimer (подкласс OSError).",
        "en": "Base class for I/O related errors"
      },
      "body": {
        "ru": "Появляется только вокруг signal.setitimer() и getitimer() — например, при отрицательном интервале или неизвестном типе таймера. Обе функции существуют лишь на Unix: на Windows их просто нет, и обращение даст AttributeError, а не это исключение. Наследуется от OSError, поэтому широкий except OSError его тоже перехватит.",
        "en": "It surfaces only around signal.setitimer() and getitimer() — a negative interval or an unknown timer type, say. Both functions are Unix-only; on Windows the attribute is missing entirely, so you get AttributeError instead of this error. Since it subclasses OSError, a broader except OSError catches it as well."
      },
      "syntax": "raise signal.ItimerError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/signal.html#signal.ItimerError",
      "version": "3.3",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc",
        "platform:posix"
      ],
      "examples": [
        "import signal",
        "try:",
        "    raise signal.ItimerError",
        "except signal.ItimerError as e:",
        "    print(type(e).__name__)   # → ItimerError"
      ],
      "related": [
        "oserror",
        "interruptederror",
        "keyboardinterrupt"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.statisticserror",
      "title": "statistics.StatisticsError",
      "kind": "exception",
      "summary": {
        "ru": "Некорректные данные для статистической функции: statistics.mean([]).",
        "en": "Inappropriate argument value (of correct type)"
      },
      "body": {
        "ru": "Почти всегда за ним стоит пустая или слишком короткая выборка: mean и median требуют хотя бы одно значение, variance и stdev — минимум два. Проверить длину данных до вызова обычно честнее, чем ловить исключение постфактум. Класс наследует ValueError, так что привычный except ValueError сработает — но заодно проглотит и другие ошибки значений.",
        "en": "Behind it there is nearly always empty or too short data: mean and median need at least one value, variance and stdev need at least two. Checking the length before the call is usually cleaner than catching the error afterwards. It subclasses ValueError, so a plain except ValueError will catch it — along with every other value error you did not mean to hide."
      },
      "syntax": "raise statistics.StatisticsError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.StatisticsError",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "среднее по пустому списку",
        "недостаточно данных для статистики"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import statistics",
        "try:",
        "    raise statistics.StatisticsError",
        "except statistics.StatisticsError as e:",
        "    print(type(e).__name__)   # → StatisticsError"
      ],
      "related": [
        "statistics.mean",
        "statistics.stdev",
        "valueerror"
      ],
      "related_errors": []
    },
    {
      "id": "stopasynciteration",
      "title": "StopAsyncIteration",
      "kind": "exception",
      "summary": {
        "ru": "Сигнал исчерпания асинхронного итератора (аналог StopIteration для async for).",
        "en": "Signals that an async iterator is exhausted (async analog of StopIteration)."
      },
      "body": {
        "ru": "Руками это исключение почти не пишут: его поднимает метод __anext__ асинхронного итератора, а async for ловит его молча и просто завершает цикл. Внутри async-генератора выходить через raise StopAsyncIteration нельзя — интерпретатор подменит его на RuntimeError, для завершения достаточно обычного return. Синхронный for и next() про этот сигнал не знают: он работает только в async-контексте.",
        "en": "You almost never raise this yourself — an async iterator's __anext__ raises it, and async for swallows it and ends the loop. Inside an async generator, raising StopAsyncIteration to finish is wrong: the interpreter replaces it with RuntimeError, so use a plain return. A synchronous for or next() will not recognise the signal at all; it only means anything in async code."
      },
      "syntax": "raise StopAsyncIteration",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#StopAsyncIteration",
      "version": "3.5",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "конец асинхронного итератора",
        "исчерпание асинхронного генератора"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise StopAsyncIteration",
        "except StopAsyncIteration as e:",
        "    print(type(e).__name__)   # → StopAsyncIteration"
      ],
      "related": [
        "stopiteration",
        "async-for-async-with",
        "anext"
      ],
      "related_errors": []
    },
    {
      "id": "stopiteration",
      "title": "StopIteration",
      "kind": "exception",
      "summary": {
        "ru": "Сигнализирует об исчерпании итератора. Автоматически перехватывается циклом for и генераторами — вручную поднимать нужно редко.",
        "en": "Signals that an iterator is exhausted. for loops and generators catch it automatically — you rarely need to raise it by hand."
      },
      "body": {
        "ru": "Главная ловушка — PEP 479: если StopIteration вылетит наружу из тела генератора (скажем, из необработанного внутреннего next()), Python подменит его на RuntimeError, а не тихо оборвёт генератор — завершать генератор нужно обычным return. Вторая: StopIteration наследуется от Exception, поэтому широкий except Exception проглотит сигнал конца итерации вместе с настоящими ошибками. Когда исчерпание итератора — штатная ситуация, вместо try/except берите next(it, default).",
        "en": "The big trap is PEP 479: if StopIteration escapes the body of a generator (for example from an unguarded inner next() call), Python turns it into RuntimeError instead of quietly ending the generator — finish a generator with a plain return. It also inherits from Exception, so a broad except Exception silently eats the end-of-iteration signal along with real errors. When running out of items is expected, prefer next(it, default) over try/except."
      },
      "syntax": "raise StopIteration",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#StopIteration",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "конец итератора",
        "итератор закончился",
        "исчерпан генератор"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "it = iter([1, 2])",
        "next(it)  # 1",
        "next(it)  # 2",
        "next(it)  # StopIteration",
        "# В генераторе raise StopIteration = return",
        "def gen():",
        "    yield 1",
        "    return  # автоматически поднимает StopIteration"
      ],
      "related": [
        "next",
        "итератор-__iter__-__next__",
        "generator-function-yield",
        "stopasynciteration"
      ],
      "related_errors": []
    },
    {
      "id": "struct.error",
      "title": "struct.error",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка упаковки/распаковки struct (несоответствие формата и данных).",
        "en": "Common base class for all non-exit exceptions"
      },
      "body": {
        "ru": "Две основные причины: при unpack длина буфера не совпадает со struct.calcsize(формат) — для чтения только начала есть unpack_from — либо значение не влезает в код формата, как 300 в 'B'. Учтите и то, что по умолчанию размеры считаются нативными, с выравниванием; предсказуемую раскладку дают префиксы '<', '>' или '='. Наследуется напрямую от Exception, а не от ValueError, поэтому привычный except ValueError его пропустит.",
        "en": "Two causes dominate: unpack got a buffer whose length differs from struct.calcsize(fmt) — use unpack_from to read just a prefix — or a value that does not fit its format code, like 300 into 'B'. Note also that the default layout is native with padding; the '<', '>' and '=' prefixes give you fixed, predictable sizes. It inherits straight from Exception, not from ValueError, so a habitual except ValueError misses it."
      },
      "syntax": "raise struct.error",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/struct.html#struct.error",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка упаковки двоичных данных",
        "формат не совпадает с данными",
        "ошибка разбора байтов по формату"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import struct",
        "try:",
        "    raise struct.error",
        "except struct.error as e:",
        "    print(type(e).__name__)   # → error"
      ],
      "related": [
        "int.to_bytes",
        "int.from_bytes"
      ],
      "related_errors": []
    },
    {
      "id": "syntaxerror",
      "title": "SyntaxError",
      "kind": "exception",
      "summary": {
        "ru": "Синтаксически некорректный код (обнаруживается при компиляции): eval('1 +').",
        "en": "Syntactically invalid code, detected at compile time."
      },
      "body": {
        "ru": "Обернуть собственный сломанный код в try/except невозможно: файл разбирается целиком до запуска, и ошибка возникает раньше, чем выполнится первая строка. Перехват осмыслен только для кода, компилируемого на лету — eval(), exec(), compile(), импорт стороннего модуля. У объекта есть msg, filename, lineno, offset и text, по ним показывают точное место; IndentationError и TabError — его подклассы.",
        "en": "You cannot wrap your own broken source in try/except: the whole file is parsed before anything runs, so the error fires before the first statement executes. Catching it makes sense only for code compiled at runtime — eval(), exec(), compile(), or importing third-party code. The exception carries msg, filename, lineno, offset and text for pinpointing the spot, and IndentationError and TabError are its subclasses."
      },
      "syntax": "raise SyntaxError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#SyntaxError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "синтаксическая ошибка",
        "неверный синтаксис",
        "пропущена скобка или двоеточие"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    eval('1 +')",
        "except SyntaxError as e:",
        "    print(type(e).__name__)   # → SyntaxError"
      ],
      "related": [
        "indentationerror",
        "taberror",
        "syntaxwarning"
      ],
      "related_errors": []
    },
    {
      "id": "syntaxwarning",
      "title": "SyntaxWarning",
      "kind": "exception",
      "summary": {
        "ru": "Сомнительный синтаксис (напр. подозрительное сравнение).",
        "en": "Dubious syntax."
      },
      "body": {
        "ru": "Это предупреждение, а не ошибка: компилятор печатает его один раз и продолжает работу, так что до except дело обычно не доходит. Чаще всего студент ловит его на сравнении is с литералом (нужно ==) и на нераспознанной escape-последовательности в обычной строке — с Python 3.12 такая строка даёт именно SyntaxWarning, лечится r-префиксом. Чтобы такие места падали, а не проскакивали, запускайте с ключом -W error::SyntaxWarning.",
        "en": "It is a warning, not an error: the compiler prints it once and keeps going, so an except block normally never sees it. The two classic triggers are comparing to a literal with is instead of ==, and an unrecognised escape sequence in a plain string — since Python 3.12 that emits SyntaxWarning, and an r-prefix fixes it. Run with -W error::SyntaxWarning if you want these spots to fail loudly instead of scrolling past."
      },
      "syntax": "raise SyntaxWarning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#SyntaxWarning",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "подозрительный синтаксис",
        "предупреждение о подозрительном сравнении"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise SyntaxWarning",
        "except SyntaxWarning as e:",
        "    print(type(e).__name__)   # → SyntaxWarning"
      ],
      "related": [
        "syntaxerror",
        "warning",
        "runtimewarning"
      ],
      "related_errors": []
    },
    {
      "id": "systemerror",
      "title": "SystemError",
      "kind": "exception",
      "summary": {
        "ru": "Внутренняя ошибка интерпретатора (не фатальная). Обычно повод сообщить о баге.",
        "en": "An internal interpreter error (non-fatal); usually worth reporting as a bug."
      },
      "body": {
        "ru": "Ловить и «чинить» его в своём коде смысла нет: он означает, что сам интерпретатор или C-расширение обнаружили у себя противоречивое состояние. Правильная реакция — свести к минимальному воспроизводимому примеру и завести баг с версией Python и трассировкой. Не путать с SystemExit: тот наследуется от BaseException и штатно завершает программу, а SystemError — обычное Exception и попадёт в широкий except Exception.",
        "en": "There is nothing to fix on your side when it appears: it means the interpreter itself, or a C extension, found its own state inconsistent. The right response is to reduce it to a minimal reproducer and file a bug with the Python version and traceback. Do not confuse it with SystemExit, which derives from BaseException and ends the program normally — SystemError is an ordinary Exception and will be caught by a broad except Exception."
      },
      "syntax": "raise SystemError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#SystemError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "внутренняя ошибка интерпретатора",
        "баг интерпретатора"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise SystemError",
        "except SystemError as e:",
        "    print(type(e).__name__)   # → SystemError"
      ],
      "related": [
        "runtimeerror",
        "exception"
      ],
      "related_errors": []
    },
    {
      "id": "systemexit",
      "title": "SystemExit",
      "kind": "exception",
      "summary": {
        "ru": "Поднимается sys.exit() для завершения программы. Наследует BaseException.",
        "en": "Raised by sys.exit() to terminate the program. Inherits BaseException."
      },
      "body": {
        "ru": "Наследует BaseException, а не Exception — именно поэтому обычный except Exception его не ловит и sys.exit() спокойно доходит до интерпретатора. Зато голый except: или except BaseException его проглотит, и программа не завершится: это самая частая причина жалобы «мой sys.exit() не срабатывает». Аргумент sys.exit() оседает в атрибуте code: число становится кодом возврата, строка печатается в stderr, а код возврата будет 1.",
        "en": "It inherits from BaseException rather than Exception, which is exactly why a normal except Exception lets it pass and sys.exit() actually reaches the interpreter. A bare except: or except BaseException, on the other hand, swallows it and the program keeps running — the usual reason people complain that sys.exit() does nothing. Whatever you pass to sys.exit() ends up in the code attribute: an integer becomes the exit status, a string is printed to stderr and the status becomes 1."
      },
      "syntax": "raise SystemExit",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#SystemExit",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "выход из программы",
        "завершить выполнение скрипта"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise SystemExit",
        "except SystemExit as e:",
        "    print(type(e).__name__)   # → SystemExit"
      ],
      "related": [
        "sys.exit",
        "baseexception",
        "keyboardinterrupt",
        "os._exit"
      ],
      "related_errors": []
    },
    {
      "id": "taberror",
      "title": "TabError",
      "kind": "exception",
      "summary": {
        "ru": "Непоследовательное смешение табов и пробелов в отступах (подкласс IndentationError).",
        "en": "Inconsistent mixing of tabs and spaces in indentation (a subclass of IndentationError)."
      },
      "body": {
        "ru": "Это ошибка разбора, а не выполнения: интерпретатор спотыкается ещё до того, как выполнит первую строку файла, поэтому обернуть проблемное место в try/except внутри того же файла невозможно. Чинится не кодом, а редактором: включите показ невидимых символов и держите во всём файле один стиль отступа — четыре пробела на уровень, как советует PEP 8.",
        "en": "This is a parse-time error, not a runtime one: the interpreter chokes before a single line of the file executes, so wrapping the offending spot in try/except inside that same file cannot help. The fix lives in your editor, not in your code: turn on visible whitespace and keep one indentation style throughout the file — four spaces per level, as PEP 8 recommends."
      },
      "syntax": "raise TabError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#TabError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "смешаны табы и пробелы",
        "ошибка табуляции в отступах"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise TabError",
        "except TabError as e:",
        "    print(type(e).__name__)   # → TabError"
      ],
      "related": [
        "indentationerror",
        "syntaxerror"
      ],
      "related_errors": []
    },
    {
      "id": "threading.brokenbarriererror",
      "title": "threading.BrokenBarrierError",
      "kind": "exception",
      "summary": {
        "ru": "Barrier сломан (сброшен/таймаут), пока поток ждал на нём.",
        "en": "Unspecified run-time error"
      },
      "body": {
        "ru": "Главная деталь: если хотя бы один поток вышел из wait() по таймауту, барьер ломается для всех — исключение получат и остальные участники, а не только опоздавший. Тот же эффект даёт явный abort(), которым намеренно разблокируют застрявшие потоки. Вернуть барьер в строй можно только через reset(), причём в момент, когда на нём никто не ждёт.",
        "en": "The key detail: if a single thread leaves wait() on timeout, the barrier breaks for everyone — the other parties get this exception too, not just the late one. An explicit abort() does the same thing on purpose, to unblock threads stuck waiting. The only way back to a usable barrier is reset(), called at a moment when nobody is waiting on it."
      },
      "syntax": "raise threading.BrokenBarrierError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/threading.html#threading.BrokenBarrierError",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import threading",
        "try:",
        "    raise threading.BrokenBarrierError",
        "except threading.BrokenBarrierError as e:",
        "    print(type(e).__name__)   # → BrokenBarrierError"
      ],
      "related": [
        "threading-event",
        "threading-semaphore",
        "threading-thread"
      ],
      "related_errors": []
    },
    {
      "id": "timeouterror",
      "title": "TimeoutError",
      "kind": "exception",
      "summary": {
        "ru": "Истёк тайм-аут системной функции (подкласс OSError).",
        "en": "A system function timed out (a subclass of OSError)."
      },
      "body": {
        "ru": "Возникает только там, где тайм-аут кто-то явно задал — у сокета через settimeout(), у сетевого клиента через параметр timeout; собственный медленный цикл сам по себе TimeoutError не поднимет никогда. Начиная с Python 3.10 socket.timeout, а с 3.11 и asyncio.TimeoutError — просто другие имена этого же встроенного класса, так что одного except TimeoutError хватает на все три случая. У subprocess исключение своё, TimeoutExpired, и под этот except оно не попадёт.",
        "en": "It only shows up where a timeout was actually set — socket.settimeout(), a timeout= argument to a network client; your own slow loop will never raise it on its own. Since Python 3.10 socket.timeout, and since 3.11 asyncio.TimeoutError as well, are just other names for this same built-in class, so a single except TimeoutError covers all three. Note that subprocess has its own TimeoutExpired, which this except will not catch."
      },
      "syntax": "raise TimeoutError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#TimeoutError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "превышено время ожидания",
        "истёк тайм-аут"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise TimeoutError",
        "except TimeoutError as e:",
        "    print(type(e).__name__)   # → TimeoutError"
      ],
      "related": [
        "oserror",
        "connectionerror",
        "blockingioerror"
      ],
      "related_errors": []
    },
    {
      "id": "tokenize.tokenerror",
      "title": "tokenize.TokenError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка токенизации Python-исходника (напр. незакрытая скобка/строка).",
        "en": "Common base class for all non-exit exceptions"
      },
      "body": {
        "ru": "Это сигнал «исходник оборвался на полуслове»: не закрыта скобка или тройная кавычка, и токенизатор дошёл до конца ввода внутри конструкции. Про синтаксис вообще речи нет — tokenize не разбирает грамматику, так что бессмысленный, но корректно разбитый на токены код пройдёт без жалоб. В интерактивных инструментах эта ошибка обычно значит не «отвергнуть ввод», а «попросить ещё одну строку».",
        "en": "It means the source ran out mid-construct: an unclosed bracket or triple-quoted string, with the tokenizer hitting end of input inside it. It is not a syntax check — tokenize never parses grammar, so nonsense that still splits into valid tokens passes without complaint. In interactive tools this error usually means \"ask for another line\", not \"reject the input\"."
      },
      "syntax": "raise tokenize.TokenError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/tokenize.html#tokenize.TokenError",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import tokenize",
        "try:",
        "    raise tokenize.TokenError",
        "except tokenize.TokenError as e:",
        "    print(type(e).__name__)   # → TokenError"
      ],
      "related": [
        "syntaxerror",
        "_incompleteinputerror",
        "compile"
      ],
      "related_errors": []
    },
    {
      "id": "try-except",
      "title": "try / except",
      "kind": "exception",
      "summary": {
        "ru": "Блок try содержит код, который может вызвать исключение. except перехватывает его. else — если исключения не было. finally — всегда.",
        "en": "The try block holds code that may raise an exception. except catches it. else runs if there was none. finally runs always."
      },
      "body": {
        "ru": "Ловите конкретный класс: except Exception, а тем более голый except:, заодно проглатывает опечатки вроде NameError и превращает баг в тихо работающую программу; голый except: захватывает ещё и BaseException, то есть KeyboardInterrupt с SystemExit. Порядок веток важен — срабатывает первая подходящая, поэтому except Exception перед except ValueError делает вторую ветку недостижимой. И держите тело try минимальным: чем больше строк под защитой, тем выше шанс перехватить не ту ошибку.",
        "en": "Catch the specific class: except Exception — and a bare except: even more so — also swallows typos like NameError and turns a bug into a program that quietly does the wrong thing, while a bare except: additionally grabs BaseException, KeyboardInterrupt and SystemExit included. Branch order matters, since the first matching handler wins: except Exception placed before except ValueError makes the later branch unreachable. Keep the try body as small as possible — the more lines it covers, the easier it is to catch an error you never meant to handle."
      },
      "syntax": "try:\n    ...\nexcept ErrorType:\n    ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#the-try-statement",
      "version": "",
      "section": "Исключения",
      "subcat": "обработка",
      "color_group": "exc",
      "aliases": [
        "обработка ошибок",
        "поймать исключение",
        "перехватить ошибку"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    x = 1 / 0",
        "except ZeroDivisionError:",
        "    print('division by zero')  # → division by zero",
        "    try:",
        "        int('abc')",
        "    except ValueError as e:",
        "        print(e)  # → invalid literal for int()...",
        "# Несколько except",
        "try:",
        "    lst = [1,2,3]",
        "    print(lst[10])",
        "except IndexError:",
        "    print('index error')  # → index error",
        "except Exception as e:",
        "    print('other:', e)",
        "# else — если исключений не было",
        "try:",
        "    result = 10 / 2",
        "except ZeroDivisionError:",
        "    print('error')",
        "else:",
        "    print(result)  # → 5.0",
        "# finally — всегда выполняется",
        "try:",
        "    f = open('/nonexistent')",
        "except FileNotFoundError:",
        "    print('not found')",
        "finally:",
        "    print('always')  # → not found / always",
        "# Перехват нескольких типов в одном except",
        "try:",
        "    x = int(None)",
        "except (TypeError, ValueError) as e:",
        "    print(f'Caught: {e}')  # → Caught: ...",
        "# Базовый Exception — ловит всё (кроме SystemExit и KeyboardInterrupt)",
        "try:",
        "    raise RuntimeError('oops')",
        "except Exception as e:",
        "    print(type(e).__name__, e)  # → RuntimeError oops"
      ],
      "related": [
        "raise",
        "finally",
        "else-в-try-except",
        "иерархия-исключений"
      ],
      "related_errors": []
    },
    {
      "id": "typeerror",
      "title": "TypeError",
      "kind": "exception",
      "summary": {
        "ru": "Вызывается при операции или функции с объектом неподходящего типа. Частый случай: сложение str и int.",
        "en": "Raised by an operation or a function applied to an object of an unsuitable type. A common case: adding a str to an int."
      },
      "body": {
        "ru": "Держите в голове границу с ValueError: TypeError — тип не тот (len(42)), ValueError — тип верный, а значение не годится (int('abc')). Самый частый источник у студентов — незаметно просочившийся None: функция забыла return, или переменной присвоили результат list.sort()/append(), а потом обратились к ней по индексу. Неверное число аргументов при вызове функции — тоже TypeError, а не какое-то отдельное исключение.",
        "en": "Keep the line between this and ValueError clear: TypeError means the type is wrong (len(42)), ValueError means the type is right but the value is not usable (int('abc')). The most common source in student code is a None that sneaked in: a function that forgot to return, or a variable holding the result of list.sort()/append() that is later indexed. Calling a function with the wrong number of arguments raises TypeError too, not some separate exception."
      },
      "syntax": "raise TypeError('сообщение')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#TypeError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка типа",
        "нельзя сложить строку и число",
        "неподходящий тип аргумента"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "'2' + 2              # TypeError",
        "len(42)              # TypeError",
        "def add(a, b):",
        "    return a + b",
        "add(1, '2')          # TypeError"
      ],
      "related": [
        "valueerror",
        "attributeerror",
        "преобразование-типов"
      ],
      "related_errors": []
    },
    {
      "id": "unboundlocalerror",
      "title": "UnboundLocalError",
      "kind": "exception",
      "summary": {
        "ru": "Чтение локальной переменной до присваивания (подкласс NameError); частая ловушка с присваиванием в функции.",
        "en": "Reading a local variable before it was assigned (a subclass of NameError)."
      },
      "body": {
        "ru": "Python решает вопрос «локальная или нет» сразу по всему телу функции: если имя где-нибудь внутри присваивается, оно локально везде — и чтение до этой строки падает, даже когда снаружи есть глобальная переменная с тем же именем. Особенно коварны count += 1 и total = total + 1: справа читается ещё не созданная локальная переменная. Если внешнюю переменную действительно нужно менять — объявите global (или nonlocal для вложенной функции), но обычно чище передать значение аргументом и вернуть результат.",
        "en": "Python decides local-or-not for the whole function body at once: if a name is assigned anywhere inside, it is local everywhere, so reading it before that line fails even when a global of the same name exists. count += 1 and total = total + 1 are the classic traps — the right-hand side reads a local that has not been created yet. If you really must modify the outer variable, declare global (or nonlocal inside a nested function), though passing it in as an argument and returning the result is usually cleaner."
      },
      "syntax": "raise UnboundLocalError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#UnboundLocalError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "локальная переменная без значения",
        "обращение к переменной до присваивания"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise UnboundLocalError",
        "except UnboundLocalError as e:",
        "    print(type(e).__name__)   # → UnboundLocalError"
      ],
      "related": [
        "nameerror",
        "локальные-и-глобальные-переменные",
        "global-nonlocal"
      ],
      "related_errors": []
    },
    {
      "id": "unicodedecodeerror",
      "title": "UnicodeDecodeError",
      "kind": "exception",
      "summary": {
        "ru": "Не удалось декодировать байты в строку: b'\\xff'.decode('utf-8').",
        "en": "Failed to decode bytes into a string."
      },
      "body": {
        "ru": "Почти всегда виноваты не данные, а неверно угаданная кодировка: open() без encoding= берёт кодировку системы, поэтому файл в UTF-8 спокойно читается на Linux и падает на Windows с cp1251. Ловить это исключение обычно бессмысленно — правильнее явно передать encoding='utf-8', а если битые байты действительно ожидаемы, добавить errors='replace' или 'ignore'. У объекта есть поля encoding, object, start, end и reason: они показывают точную позицию байта, на котором декодер сдался.",
        "en": "The bytes are rarely the real problem — the guessed encoding usually is: open() without encoding= falls back to the platform default, so a UTF-8 file that reads fine on Linux blows up on a Windows box using cp1251. Catching the exception seldom helps; pass encoding='utf-8' explicitly, or errors='replace'/'ignore' when broken bytes are genuinely expected. The instance carries encoding, object, start, end and reason, which pinpoint the exact byte where the decoder gave up."
      },
      "syntax": "raise UnicodeDecodeError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#UnicodeDecodeError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка декодирования",
        "неверная кодировка при чтении файла"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    b'\\xff'.decode('utf-8')",
        "except UnicodeDecodeError as e:",
        "    print(type(e).__name__)   # → UnicodeDecodeError"
      ],
      "related": [
        "unicodeencodeerror",
        "unicodeerror",
        "bytes.decode",
        "file-errors"
      ],
      "related_errors": []
    },
    {
      "id": "unicodeencodeerror",
      "title": "UnicodeEncodeError",
      "kind": "exception",
      "summary": {
        "ru": "Не удалось закодировать строку в байты: '☃'.encode('ascii').",
        "en": "Failed to encode a string into bytes."
      },
      "body": {
        "ru": "Обратная сторона декодирования: символ в строке есть, но целевая кодировка его не выражает — ascii не знает ни снеговика, ни кириллицы. У студентов это чаще всего не явный encode() в коде, а обычный print() в консоль со старой кодовой страницей: сама строка цела, ломается только вывод. Если данные надо сохранить восстановимыми, errors='backslashreplace' или 'xmlcharrefreplace' лучше 'ignore' — последний молча выкидывает символы навсегда.",
        "en": "This is the mirror image of decoding: the character exists in your string, but the target encoding has no way to express it — ascii knows neither a snowman nor Cyrillic. For students it usually isn't an explicit encode() call but a plain print() into a console stuck on a legacy code page: the string itself is fine, only the output path breaks. When the data must stay recoverable, prefer errors='backslashreplace' or 'xmlcharrefreplace' over 'ignore', which silently throws characters away for good."
      },
      "syntax": "raise UnicodeEncodeError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#UnicodeEncodeError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка кодирования строки",
        "неверная кодировка при записи файла",
        "кириллица не выводится в консоль"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    '\\u2603'.encode('ascii')",
        "except UnicodeEncodeError as e:",
        "    print(type(e).__name__)   # → UnicodeEncodeError"
      ],
      "related": [
        "unicodedecodeerror",
        "unicodeerror",
        "str.encode",
        "file-errors"
      ],
      "related_errors": []
    },
    {
      "id": "unicodeerror",
      "title": "UnicodeError",
      "kind": "exception",
      "summary": {
        "ru": "Базовый класс ошибок кодирования/декодирования Unicode (подкласс ValueError).",
        "en": "Base class for Unicode encoding/decoding errors (a subclass of ValueError)."
      },
      "body": {
        "ru": "Общий предок UnicodeDecodeError и UnicodeEncodeError, поэтому один except UnicodeError ловит обе стороны сразу — но не говорит, где именно сломалось; если реакция должна отличаться, ловите конкретный класс. Он же наследник ValueError, так что стоящий выше except ValueError перехватит его первым, и это легко проглядеть. Изредка UnicodeError возбуждается и сам по себе — например, кодек idna так жалуется на пустую или слишком длинную метку домена.",
        "en": "It is the common ancestor of UnicodeDecodeError and UnicodeEncodeError, so a single except UnicodeError covers both directions — at the cost of not telling you which one failed; catch the specific class when the handling differs. It also inherits from ValueError, so an earlier except ValueError will swallow it, which is easy to miss when reading a chain of handlers. Occasionally UnicodeError is raised on its own — the idna codec, for instance, uses it to complain about an empty or over-long domain label."
      },
      "syntax": "raise UnicodeError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#UnicodeError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка юникода",
        "общая ошибка кодировки"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise UnicodeError",
        "except UnicodeError as e:",
        "    print(type(e).__name__)   # → UnicodeError"
      ],
      "related": [
        "unicodeencodeerror",
        "unicodedecodeerror",
        "unicodetranslateerror",
        "valueerror"
      ],
      "related_errors": []
    },
    {
      "id": "unicodetranslateerror",
      "title": "UnicodeTranslateError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка при переводе Unicode-символов (редкая, при str.translate с codec-обработчиком).",
        "en": "An error while translating Unicode characters (rare)."
      },
      "body": {
        "ru": "В обычном коде вы его практически не встретите: объект создаётся внутренней машинерией перевода, чтобы обработчики ошибок, зарегистрированные через codecs.register_error, могли отличить перевод от кодирования и декодирования. В отличие от соседей, конструктор принимает четыре аргумента без encoding — только object, start, end и reason. Отдельный except на него в учебной задаче ставить незачем.",
        "en": "You will almost never meet it in ordinary code: the object exists so that error handlers registered through codecs.register_error can tell the translation case apart from encoding and decoding. Unlike its siblings, the constructor takes four arguments and no encoding — just object, start, end and reason. There is no reason to write a dedicated except clause for it in coursework."
      },
      "syntax": "raise UnicodeTranslateError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#UnicodeTranslateError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise UnicodeTranslateError('x', 0, 1, 'reason')",
        "except UnicodeTranslateError as e:",
        "    print(type(e).__name__)   # → UnicodeTranslateError"
      ],
      "related": [
        "unicodeerror",
        "str.translate",
        "unicodeencodeerror"
      ],
      "related_errors": []
    },
    {
      "id": "unicodewarning",
      "title": "UnicodeWarning",
      "kind": "exception",
      "summary": {
        "ru": "Предупреждение, связанное с Unicode.",
        "en": "A Unicode-related warning."
      },
      "body": {
        "ru": "Это предупреждение, а не ошибка: по умолчанию текст один раз печатается в stderr, программа спокойно идёт дальше, и никакой except его не поймает. Чтобы оно стало настоящим исключением, нужен фильтр — warnings.simplefilter('error', UnicodeWarning) или запуск с -W error. Наследуется оно прямо от Warning, а не от UnicodeError, так что except UnicodeError мимо него проходит.",
        "en": "This is a warning, not an error: by default the message is printed to stderr once and execution simply continues, so no except clause will ever see it. Turn it into a real exception with a filter — warnings.simplefilter('error', UnicodeWarning) or running with -W error. It descends directly from Warning rather than from UnicodeError, so except UnicodeError will not catch it."
      },
      "syntax": "raise UnicodeWarning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#UnicodeWarning",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "предупреждение о юникоде",
        "предупреждение при сравнении байтов и строки"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise UnicodeWarning",
        "except UnicodeWarning as e:",
        "    print(type(e).__name__)   # → UnicodeWarning"
      ],
      "related": [
        "warning",
        "unicodeerror",
        "encodingwarning"
      ],
      "related_errors": []
    },
    {
      "id": "userwarning",
      "title": "UserWarning",
      "kind": "exception",
      "summary": {
        "ru": "Категория предупреждений по умолчанию для warnings.warn().",
        "en": "The default category for warnings.warn()."
      },
      "body": {
        "ru": "Частая ловушка: warnings.warn('...') ничего не бросает — текст уходит в stderr, а выполнение спокойно идёт дальше, поэтому except UserWarning вокруг такого вызова ничего не поймает. Чтобы предупреждения действительно превращались в исключения, нужен фильтр: warnings.simplefilter('error') или запуск python -W error. И по умолчанию предупреждение из одной и той же строки печатается только при первом срабатывании, так что в цикле его легко проглядеть.",
        "en": "A common trap: warnings.warn('...') raises nothing — the text goes to stderr and execution continues, so an except UserWarning around the call catches nothing. To make warnings behave like real exceptions you need a filter: warnings.simplefilter('error') or running python -W error. Also, by default a warning from a given source line is printed only the first time it fires, so inside a loop it is easy to miss."
      },
      "syntax": "raise UserWarning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#UserWarning",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "как выдать предупреждение",
        "своё предупреждение"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise UserWarning",
        "except UserWarning as e:",
        "    print(type(e).__name__)   # → UserWarning"
      ],
      "related": [
        "warning",
        "deprecationwarning",
        "runtimewarning"
      ],
      "related_errors": []
    },
    {
      "id": "valueerror",
      "title": "ValueError",
      "kind": "exception",
      "summary": {
        "ru": "Вызывается, когда функция получает аргумент правильного типа, но недопустимого значения. Например, int('abc') или math.sqrt(-1).",
        "en": "Raised when a function gets an argument of the right type but with an unacceptable value. For example, int('abc') or math.sqrt(-1)."
      },
      "body": {
        "ru": "Граница с TypeError простая: тип подошёл, а значение — нет. int('abc') это ValueError, а int([]) уже TypeError. int('12.5') тоже падает: int() не разбирает дробную часть, нужно сначала float(). Оборачивайте в except ValueError именно узкое место (int(input())), а не весь блок через except Exception — иначе спрячете собственные опечатки в коде.",
        "en": "The line against TypeError is simple: the type fits, the value does not — int('abc') is a ValueError, while int([]) is a TypeError. int('12.5') fails too, because int() does not parse a fractional part; go through float() first. Put except ValueError around the narrow spot (int(input())) instead of wrapping a whole block in except Exception, or you will bury your own typos."
      },
      "syntax": "raise ValueError('сообщение')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ValueError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "недопустимое значение аргумента",
        "ошибка преобразования строки в число",
        "неверное значение"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "int('abc')           # ValueError",
        "float('inf')         # ok — inf допустим",
        "list.index(99)       # ValueError если 99 нет",
        "try:",
        "    x = int('нет')",
        "except ValueError as e:",
        "    print('Ошибка:', e)"
      ],
      "related": [
        "typeerror",
        "exception",
        "try-except",
        "raise"
      ],
      "related_errors": []
    },
    {
      "id": "warning",
      "title": "Warning",
      "kind": "exception",
      "summary": {
        "ru": "Базовый класс всех предупреждений (обычно показываются, а не поднимаются).",
        "en": "Base class for all warning categories (usually shown, not raised)."
      },
      "body": {
        "ru": "Warning наследуется от Exception, поэтому фильтр с category=Warning в warnings.filterwarnings ловит все категории сразу, а except Exception проглотит предупреждение, превращённое в ошибку. Напрямую этот класс почти не используют: для сообщения пользователю кода берут UserWarning, для устаревшего API — DeprecationWarning (она по умолчанию скрыта везде, кроме кода, запущенного как __main__).",
        "en": "Warning inherits from Exception, so category=Warning in warnings.filterwarnings matches every warning category at once, and a broad except Exception will swallow a warning that a filter turned into an error. The class itself is rarely used directly: pick UserWarning for messages aimed at the caller and DeprecationWarning for obsolete API — the latter is hidden by default everywhere except code running as __main__."
      },
      "syntax": "raise Warning",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#Warning",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "базовый класс предупреждений",
        "виды предупреждений"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    raise Warning",
        "except Warning as e:",
        "    print(type(e).__name__)   # → Warning"
      ],
      "related": [
        "userwarning",
        "deprecationwarning",
        "exception",
        "иерархия-исключений"
      ],
      "related_errors": []
    },
    {
      "id": "zerodivisionerror",
      "title": "ZeroDivisionError",
      "kind": "exception",
      "summary": {
        "ru": "Деление (или взятие остатка) на ноль: 1/0, 5 % 0.",
        "en": "Division or modulo by zero, e.g. 1/0 or 5 % 0."
      },
      "body": {
        "ru": "В отличие от IEEE-754 и numpy, где деление на ноль даёт inf, встроенные типы Python всегда бросают исключение — 1.0 / 0.0 падает так же, как 1 / 0. Сюда же относятся 5 % 0, divmod(5, 0) и 0 ** -1. Обычно чище проверить делитель заранее, чем оборачивать в try большой кусок кода: широкий except легко спрячет ноль, пришедший из другого места.",
        "en": "Unlike IEEE-754 or numpy, where dividing by zero yields inf, Python's built-in numeric types always raise — 1.0 / 0.0 fails exactly like 1 / 0. The same goes for 5 % 0, divmod(5, 0) and 0 ** -1. Checking the divisor up front is usually cleaner than wrapping a large block in try, since a wide except can hide a zero that leaked in from somewhere else."
      },
      "syntax": "raise ZeroDivisionError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#ZeroDivisionError",
      "version": "",
      "section": "Исключения",
      "subcat": "встроенные исключения",
      "color_group": "exc",
      "aliases": [
        "деление на ноль",
        "нельзя делить на ноль"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    1 / 0",
        "except ZeroDivisionError as e:",
        "    print(type(e).__name__)   # → ZeroDivisionError"
      ],
      "related": [
        "arithmeticerror",
        "decimal.divisionbyzero",
        "overflowerror"
      ],
      "related_errors": []
    },
    {
      "id": "zipimport.zipimporterror",
      "title": "zipimport.ZipImportError",
      "kind": "exception",
      "summary": {
        "ru": "Не удалось импортировать модуль из ZIP-архива (подкласс ImportError).",
        "en": "Import can't find module, or can't find name in module"
      },
      "body": {
        "ru": "Наследуется от ImportError, так что обычный except ImportError его уже перехватывает — отдельная ветка нужна, только когда важно отличить проблему с самим архивом от просто отсутствующего модуля. Порождает его модуль zipimport: конструктор zipimporter(path) бросает эту ошибку, если путь не указывает на читаемый ZIP-архив. Вручную такое исключение почти никто не поднимает — оно всплывает само, когда в sys.path оказывается .zip.",
        "en": "It subclasses ImportError, so a plain except ImportError already catches it; a dedicated branch pays off only when you need to tell a broken archive apart from a module that simply isn't there. It comes from the zipimport machinery: zipimporter(path) raises it when the path doesn't point to a readable ZIP archive. You almost never raise it yourself — it shows up on its own once a .zip entry lands on sys.path."
      },
      "syntax": "raise zipimport.ZipImportError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/zipimport.html#zipimport.ZipImportError",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import zipimport",
        "try:",
        "    raise zipimport.ZipImportError",
        "except zipimport.ZipImportError as e:",
        "    print(type(e).__name__)   # → ZipImportError"
      ],
      "related": [
        "importerror",
        "modulenotfounderror",
        "sys.path"
      ],
      "related_errors": []
    },
    {
      "id": "zlib.error",
      "title": "zlib.error",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка сжатия/распаковки zlib (напр. повреждённые данные).",
        "en": "Common base class for all non-exit exceptions"
      },
      "body": {
        "ru": "Наследуется прямо от Exception, а не от ValueError или OSError, поэтому привычные except ValueError и except OSError его не поймают. Самая частая причина — скормить zlib.decompress данные не того формата: у gzip другой заголовок (нужен wbits=16+zlib.MAX_WBITS или модуль gzip), а обрезанный поток падает как неполный, а не возвращает то, что успело распаковаться. Если данные приходят кусками, распаковывайте через zlib.decompressobj.",
        "en": "It derives straight from Exception, not from ValueError or OSError, so the usual except ValueError or except OSError won't catch it. The classic cause is feeding zlib.decompress the wrong format: gzip data has a different header (pass wbits=16+zlib.MAX_WBITS, or use the gzip module), and a truncated stream fails as incomplete instead of returning whatever decoded so far. When data arrives in chunks, decompress it through zlib.decompressobj."
      },
      "syntax": "raise zlib.error",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/zlib.html#zlib.error",
      "version": "",
      "section": "Исключения",
      "subcat": "модульные исключения",
      "color_group": "exc",
      "aliases": [
        "ошибка сжатия данных",
        "не удалось распаковать данные"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "import zlib",
        "try:",
        "    raise zlib.error",
        "except zlib.error as e:",
        "    print(type(e).__name__)   # → error"
      ],
      "related": [
        "shutil.readerror",
        "struct.error"
      ],
      "related_errors": []
    },
    {
      "id": "иерархия-исключений",
      "title": "Иерархия исключений",
      "kind": "exception",
      "summary": {
        "ru": "Встроенные исключения образуют иерархию. BaseException → Exception → стандартные исключения.",
        "en": "The built-in exceptions form a hierarchy. BaseException → Exception → the standard exceptions."
      },
      "body": {
        "ru": "Ловите конкретный класс, а не Exception: широкий обработчик молча проглатывает опечатки в вашем же коде. SystemExit, KeyboardInterrupt и GeneratorExit наследуются прямо от BaseException намеренно — под except Exception они не попадают, зато голый except: их перехватит и сломает выход по Ctrl+C. Блоки except проверяются сверху вниз, поэтому потомка (IndexError) пишут раньше предка (LookupError), иначе он не сработает никогда.",
        "en": "Catch the specific class rather than Exception: a broad handler quietly swallows typos in your own code. SystemExit, KeyboardInterrupt and GeneratorExit inherit straight from BaseException on purpose, so except Exception leaves them alone, while a bare except: grabs them and breaks Ctrl+C. except clauses are tested top to bottom, so a subclass like IndexError must come before its ancestor LookupError or it will never run."
      },
      "syntax": "BaseException\n  SystemExit, KeyboardInterrupt\n  Exception\n    ValueError, TypeError, IndexError...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/exceptions.html#BaseException",
      "version": "",
      "section": "Исключения",
      "subcat": "иерархия",
      "color_group": "exc",
      "aliases": [
        "дерево классов исключений",
        "какие бывают исключения",
        "какое исключение ловить"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "try:",
        "    int('abc')",
        "except ValueError as e:",
        "    print(f'ValueError: {e}')  # → invalid literal...",
        "    try:",
        "        x = [1,2][5]",
        "    except IndexError as e:",
        "        print(f'IndexError: {e}')  # → list index out of range",
        "    try:",
        "        d = {}",
        "        d['missing']",
        "    except KeyError as e:",
        "        print(f'KeyError: {e}')  # → 'missing'",
        "    try:",
        "        'str' + 1",
        "    except TypeError as e:",
        "        print(f'TypeError: {e}')  # → can only concatenate...",
        "    try:",
        "        1 / 0",
        "    except ZeroDivisionError as e:",
        "        print(f'ZeroDivisionError: {e}')  # → division by zero",
        "    try:",
        "        open('/nonexistent_file')",
        "    except FileNotFoundError as e:",
        "        print(f'FileNotFoundError: {e}')",
        "    try:",
        "        None.attr",
        "    except AttributeError as e:",
        "        print(f'AttributeError: {e}')  # → 'NoneType' object has no attribute..."
      ],
      "related": [
        "baseexception",
        "exception",
        "try-except",
        "пользовательские-исключения"
      ],
      "related_errors": []
    },
    {
      "id": "пользовательские-исключения",
      "title": "Пользовательские исключения",
      "kind": "exception",
      "summary": {
        "ru": "Создавай классы исключений, наследуясь от Exception. Можно добавлять атрибуты и иерархию.",
        "en": "Write your own exception classes by inheriting from Exception. You can give them attributes and a hierarchy of their own."
      },
      "body": {
        "ru": "Наследуйся от Exception, а не от BaseException: от BaseException идут KeyboardInterrupt и SystemExit, и случайно попасть в их семейство — значит сломать Ctrl+C и штатный выход. Полезно завести один базовый класс на модуль или приложение, а от него уже частные ошибки: тогда вызывающий код одним except ловит всё семейство, но при желании может отреагировать и на конкретный подкласс. Аргументы конструктора сами попадают в args и в текст str(e), поэтому ради одного сообщения свой __init__ писать не нужно.",
        "en": "Inherit from Exception, never from BaseException — the latter is home to KeyboardInterrupt and SystemExit, and joining that family means broad handlers start swallowing Ctrl+C and clean shutdown. It pays to define one base error per module or application and derive the specific ones from it: callers can then catch the whole family with a single except, or narrow down to one subclass when they care. Whatever you pass to the constructor lands in args and in str(e) automatically, so a custom __init__ is unnecessary just to carry a message."
      },
      "syntax": "class MyError(Exception):\n    pass",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/errors.html#user-defined-exceptions",
      "version": "",
      "section": "Исключения",
      "subcat": "пользовательские",
      "color_group": "exc",
      "aliases": [
        "свой класс исключения",
        "создать собственную ошибку"
      ],
      "keywords": [],
      "tags": [
        "exc"
      ],
      "examples": [
        "class AppError(Exception):",
        "pass",
        "class DatabaseError(AppError):",
        "pass",
        "class NetworkError(AppError):",
        "pass",
        "try:",
        "raise DatabaseError('Connection failed')",
        "except AppError as e:",
        "print(type(e).__name__, e)  # → DatabaseError Connection failed",
        "class ValidationError(ValueError):",
        "def __init__(self, field, message):",
        "self.field = field",
        "super().__init__(f'{field}: {message}')",
        "try:",
        "raise ValidationError('email', 'invalid format')",
        "except ValidationError as e:",
        "print(e.field, e)  # → email  email: invalid format",
        "class HttpError(Exception):",
        "def __init__(self, status_code, message):",
        "self.status_code = status_code",
        "super().__init__(f'HTTP {status_code}: {message}')",
        "class NotFoundError(HttpError):",
        "def __init__(self, url):",
        "super().__init__(404, f'{url} not found')",
        "try:",
        "raise NotFoundError('/api/users')",
        "except HttpError as e:",
        "print(e.status_code, e)  # → 404 HTTP 404: /api/users not found",
        "# Иерархия ошибок",
        "class PaymentError(Exception): pass",
        "class InsufficientFundsError(PaymentError):",
        "def __init__(self, amount, balance):",
        "self.shortage = amount - balance",
        "super().__init__(f'Need {self.shortage} more')",
        "try:",
        "raise InsufficientFundsError(100, 60)",
        "except InsufficientFundsError as e:",
        "print(e, e.shortage)  # → Need 40 more  40",
        "# raise ... from",
        "class ServiceError(Exception): pass",
        "try:",
        "try:",
        "int('abc')",
        "except ValueError as e:",
        "raise ServiceError('Parse failed') from e",
        "except ServiceError as e:",
        "print(e)                    # → Parse failed",
        "print(type(e.__cause__))   # → <class 'ValueError'>",
        "# repr дефолтный",
        "class SimpleError(Exception):",
        "pass",
        "e = SimpleError('something went wrong')",
        "print(str(e))   # → something went wrong",
        "print(repr(e))  # → SimpleError('something went wrong')"
      ],
      "related": [
        "exception",
        "иерархия-исключений",
        "raise",
        "try-except"
      ],
      "related_errors": []
    },
    {
      "id": "generator-function-yield",
      "title": "Generator function — yield",
      "kind": "term",
      "summary": {
        "ru": "Функция с yield — генераторная функция. Возвращает генератор. Выполнение приостанавливается при yield.",
        "en": "A function with yield is a generator function. It returns a generator. Execution is suspended at each yield."
      },
      "body": {
        "ru": "Вызов генераторной функции не выполняет ни одной строки её тела — только создаёт объект-генератор; код стартует при первом next() или на первой итерации цикла. Генератор одноразовый: после того как он исчерпан, повторный for по той же переменной молча даст ноль итераций — это ловушка, когда результат хотят обойти дважды или сначала посчитать len(). Смысл конструкции — считать элементы по одному и не держать в памяти весь список, поэтому она уместна для больших или бесконечных последовательностей.",
        "en": "Calling a generator function runs none of its body — it only builds a generator object; the code starts at the first next() or the first loop iteration. Generators are single-pass: once exhausted, a second for over the same variable silently yields nothing, which bites people who want to iterate twice or take len() first. The whole point is producing items one at a time instead of materialising a list, which is what makes huge or infinite sequences workable."
      },
      "syntax": "def gen():\n    yield value",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-generator",
      "version": "",
      "section": "Итераторы и генераторы",
      "subcat": "генераторы",
      "color_group": "iter",
      "aliases": [
        "генераторная функция",
        "возвращать значения по одному",
        "ленивая выдача значений"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "def count_up(n):",
        "i = 0",
        "while i < n:",
        "yield i",
        "i += 1",
        "for x in count_up(4):",
        "print(x, end=' ')  # → 0 1 2 3",
        "# Бесконечный генератор",
        "def naturals():",
        "n = 1",
        "while True:",
        "yield n",
        "n += 1",
        "from itertools import islice",
        "print(list(islice(naturals(), 5)))  # → [1,2,3,4,5]",
        "# Ленивые вычисления",
        "def read_large_file(path):",
        "# Открывает файл ленивно",
        "with open(path) as f:",
        "for line in f:",
        "yield line.strip()",
        "# Сохраняет память!",
        "# Состояние между вызовами",
        "def running_total(lst):",
        "total = 0",
        "for x in lst:",
        "total += x",
        "yield total",
        "print(list(running_total([1,2,3,4])))  # → [1,3,6,10]",
        "# Фибоначчи-генератор",
        "def fib_gen():",
        "a, b = 0, 1",
        "while True:",
        "yield a",
        "a, b = b, a+b",
        "g = fib_gen()",
        "print([next(g) for _ in range(8)])  # → [0,1,1,2,3,5,8,13]",
        "# send() — отправить значение в генератор",
        "def accumulator():",
        "total = 0",
        "while True:",
        "value = yield total",
        "if value is not None:",
        "total += value",
        "acc = accumulator()",
        "next(acc)  # инициализация",
        "print(acc.send(10))  # → 10",
        "print(acc.send(5))   # → 15",
        "# Генератор можно обходить только раз",
        "g2 = count_up(3)",
        "print(list(g2))  # → [0,1,2]",
        "print(list(g2))  # → [] исчерпан"
      ],
      "related": [
        "yield-from",
        "генераторное-выражение",
        "iter-next",
        "stopiteration"
      ],
      "related_errors": []
    },
    {
      "id": "iter-next",
      "title": "iter() / next()",
      "kind": "function",
      "summary": {
        "ru": "iter() создаёт итератор из итерируемого. next() возвращает следующий элемент. Default-значение — вместо StopIteration.",
        "en": "iter() builds an iterator out of an iterable. next() returns the following item. The default value is returned instead of raising StopIteration."
      },
      "body": {
        "ru": "Цикл for делает ровно это под капотом: берёт iter() у объекта и дёргает next(), пока не поймает StopIteration. Отсюда неочевидное следствие — итератор помнит позицию, поэтому после частичного обхода следующий цикл продолжит с того места, где вы остановились, а не с начала. Голый next() без второго аргумента бросает StopIteration, и если это происходит внутри генератора, интерпретатор превращает исключение в RuntimeError (PEP 479) — надёжнее передавать значение по умолчанию.",
        "en": "A for loop is exactly this under the hood: it calls iter() on the object and keeps calling next() until StopIteration shows up. The non-obvious consequence is that an iterator remembers its position, so after a partial walk the next loop resumes where you stopped rather than from the beginning. A bare next() without a default raises StopIteration, and when that happens inside a generator the interpreter converts it into RuntimeError (PEP 479), so passing a default is the safer habit."
      },
      "syntax": "it = iter(obj)\nnext(it, default)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-generator",
      "version": "",
      "section": "Итераторы и генераторы",
      "subcat": "протокол итератора",
      "color_group": "iter",
      "aliases": [
        "создать итератор из коллекции",
        "получить следующий элемент",
        "ручной перебор без цикла"
      ],
      "keywords": [
        "iter",
        "next"
      ],
      "tags": [
        "iter"
      ],
      "examples": [
        "it = iter([1,2,3])",
        "print(next(it))  # → 1",
        "print(next(it))  # → 2",
        "print(next(it))  # → 3",
        "print(next(it, 'end'))  # → end",
        "# iter со sentinel",
        "import io",
        "f = io.StringIO('line1\\nline2\\n')",
        "for line in iter(f.readline, ''):",
        "    print(line.strip())  # → line1 / line2",
        "    # iter строки",
        "    it2 = iter('hello')",
        "    for ch in it2:",
        "        print(ch, end='')  # → hello",
        "        # next с default предотвращает исключение",
        "        it3 = iter([])",
        "        print(next(it3, None))  # → None",
        "# Бесконечный итератор + islice",
        "from itertools import islice, count",
        "it4 = count(1)",
        "print(list(islice(it4, 5)))  # → [1,2,3,4,5]"
      ],
      "related": [
        "итератор-__iter__-__next__",
        "stopiteration",
        "generator-function-yield"
      ],
      "related_errors": [
        "StopIteration",
        "TypeError"
      ]
    },
    {
      "id": "itertools.accumulate",
      "title": "itertools.accumulate",
      "kind": "term",
      "summary": {
        "ru": "Накапливающиеся промежуточные результаты применения функции. По умолчанию — сумма.",
        "en": "The running intermediate results of applying a function. Sums by default."
      },
      "body": {
        "ru": "Отличие от functools.reduce: reduce сворачивает всё в одно число, accumulate отдаёт всю историю свёртки — на выходе столько же элементов, сколько на входе. Результат ленивый итератор, а не список: печатать его напрямую бесполезно, нужен list() или обход циклом, и пройти по нему можно один раз. Функция должна принимать два аргумента; параметр initial (с Python 3.8) подставляет стартовое значение и делает выход на один элемент длиннее.",
        "en": "The difference from functools.reduce: reduce collapses everything into a single value, while accumulate hands you the whole running history — the output has as many items as the input. It returns a lazy iterator, not a list, so printing it directly is useless; wrap it in list() or loop over it, and remember you only get one pass. The function must take two arguments, and the initial parameter (Python 3.8+) seeds the accumulation and makes the output one item longer."
      },
      "syntax": "from itertools import accumulate\naccumulate(iterable, func=operator.add)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-generator",
      "version": "",
      "section": "Итераторы и генераторы",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "накопительная сумма",
        "нарастающий итог",
        "кумулятивная сумма"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "from itertools import accumulate",
        "print(list(accumulate([1,2,3,4,5])))  # → [1,3,6,10,15] (накопленные суммы)",
        "import operator",
        "print(list(accumulate([1,2,3,4,5], operator.mul)))  # → [1,2,6,24,120] (факториалы)",
        "# running max",
        "print(list(accumulate([3,1,4,1,5,9,2,6], max)))  # → [3,3,4,4,5,9,9,9]",
        "# С начальным значением (Python 3.8+)",
        "print(list(accumulate([1,2,3], initial=100)))  # → [100,101,103,106]",
        "# Применение в финансах (баланс счёта)",
        "transactions = [1000, -200, -50, 300, -100]",
        "balances = list(accumulate(transactions))",
        "print(balances)  # → [1000,800,750,1050,950]"
      ],
      "related": [
        "functools.reduce",
        "префиксные-суммы",
        "sum"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.chain",
      "title": "itertools.chain",
      "kind": "term",
      "summary": {
        "ru": "Последовательно перебирает несколько итерируемых как одно целое.",
        "en": "Walks through several iterables one after another as if they were a single one."
      },
      "body": {
        "ru": "В отличие от a + b, chain ничего не копирует: элементы берутся по одному по мере обхода, память постоянная, и это работает с любыми итерируемыми — множествами, файлами, генераторами, а не только со списками одного типа. Разница между chain(*matrix) и chain.from_iterable(matrix) существенна: звёздочка сразу разворачивает внешнюю последовательность в аргументы, from_iterable берёт её лениво и потому переживает длинные или бесконечные внешние источники. Итог — обычный одноразовый итератор, второго прохода по нему не будет.",
        "en": "Unlike a + b, chain copies nothing: items are pulled one at a time as you iterate, memory stays constant, and it accepts any iterables — sets, files, generators — not just lists of one type. The gap between chain(*matrix) and chain.from_iterable(matrix) matters: the star expands the outer sequence into arguments immediately, while from_iterable consumes it lazily and therefore survives very long or endless outer sources. What you get back is an ordinary single-pass iterator, so there is no second walk over it."
      },
      "syntax": "from itertools import chain\nchain(a, b, c)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-generator",
      "version": "",
      "section": "Итераторы и генераторы",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "перебрать несколько списков подряд",
        "объединить итерируемые в одну цепочку"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "from itertools import chain",
        "result = list(chain([1,2],[3,4],[5,6]))",
        "print(result)  # → [1,2,3,4,5,6]",
        "# chain.from_iterable",
        "matrix = [[1,2],[3,4],[5,6]]",
        "flat = list(chain.from_iterable(matrix))",
        "print(flat)  # → [1,2,3,4,5,6]",
        "# Объединение строк",
        "words = list(chain('hello', ' ', 'world'))",
        "print(''.join(words))  # → hello world",
        "# С генераторами",
        "def gen1(): yield 1; yield 2",
        "def gen2(): yield 3; yield 4",
        "print(list(chain(gen1(), gen2())))  # → [1,2,3,4]",
        "# chain vs list + list",
        "a = range(1000000); b = range(1000000)",
        "# chain(a,b) — без создания списка в памяти!"
      ],
      "related": [
        "list.extend",
        "itertools.islice",
        "yield-from"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.groupby",
      "title": "itertools.groupby",
      "kind": "term",
      "summary": {
        "ru": "Группирует последовательные одинаковые элементы. Данные должны быть отсортированы по ключу!",
        "en": "Groups consecutive equal items. The data must be sorted by the key first!"
      },
      "body": {
        "ru": "Главная ловушка после «забыл отсортировать» — группа живёт только до перехода к следующей. Все группы делят один исходный итератор, поэтому list(groupby(data)) даёт пустые группы: пока вы дойдёте до конца, предыдущие уже съедены. Нужен весь результат сразу — забирайте list(group) внутри цикла, до следующей итерации.",
        "en": "Beyond the classic \"forgot to sort\" mistake, a group stays valid only until you move on to the next one. All groups share the same underlying iterator, so list(groupby(data)) hands you empty groups: by the time the loop ends, the earlier ones have been consumed. If you need everything at once, call list(group) inside the loop body, before advancing."
      },
      "syntax": "from itertools import groupby\ngroupby(iterable, key=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-generator",
      "version": "",
      "section": "Итераторы и генераторы",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "группировка по ключу",
        "сгруппировать подряд идущие одинаковые элементы"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "from itertools import groupby",
        "data = ['A','A','B','B','B','A']",
        "for key, group in groupby(data):",
        "    print(key, list(group))  # → A [A,A] / B [B,B,B] / A [A]",
        "    # Сортировка перед groupby!",
        "    words = ['apple','apricot','banana','blueberry','cherry']",
        "    for letter, group in groupby(words, key=lambda w: w[0]):",
        "        print(letter, list(group))",
        "        # Подсчёт групп",
        "        data2 = sorted([1,1,2,2,2,3])",
        "        result = {k: list(v) for k,v in groupby(data2)}",
        "        print(result)  # → {1:[1,1], 2:[2,2,2], 3:[3]}",
        "        # Группировка по условию",
        "        nums = [1,2,4,6,3,7,8]",
        "        nums.sort(key=lambda x: x%2)",
        "    for parity, grp in groupby(nums, key=lambda x: x%2):",
        "        print('odd' if parity else 'even', list(grp))",
        "        # Длины рядов",
        "        data3 = 'AAABBBCCDDDDEE'",
        "        runs = [(k, len(list(v))) for k,v in groupby(data3)]",
        "        print(runs)  # → [('A',3),('B',3),('C',2),('D',4),('E',2)]"
      ],
      "related": [
        "sorted-с-key",
        "collections.counter",
        "collections.defaultdict"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.islice",
      "title": "itertools.islice",
      "kind": "term",
      "summary": {
        "ru": "Срез итератора без материализации. Аналог slice для итерируемых.",
        "en": "Slices an iterator without materializing it. The counterpart of a slice for iterables."
      },
      "body": {
        "ru": "islice не возвращается назад: отрицательные start, stop и step запрещены, шаг только положительный. И он реально расходует итератор — элементы до start прочитаны и выброшены, а после вызова исходный итератор стоит на месте, докуда дошёл срез, так что повторный islice продолжит, а не начнёт заново.",
        "en": "islice never looks backwards: negative start, stop and step are rejected, and the step must be positive. It also genuinely consumes the source — items before start are read and thrown away, and afterwards the underlying iterator sits wherever the slice stopped, so a second islice continues from there instead of starting over."
      },
      "syntax": "from itertools import islice\nislice(iterable, stop)\nislice(iterable, start, stop[, step])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-generator",
      "version": "",
      "section": "Итераторы и генераторы",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "срез итератора",
        "взять первые элементы генератора"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "from itertools import islice",
        "print(list(islice(range(100), 5)))  # → [0,1,2,3,4]",
        "print(list(islice(range(100), 2, 7)))  # → [2,3,4,5,6]",
        "print(list(islice(range(100), 0, 10, 2)))  # → [0,2,4,6,8]",
        "# Первые N строк файла (ленивый)",
        "# with open('big.txt') as f:",
        "#     head = list(islice(f, 10))",
        "# islice с бесконечным генератором",
        "def naturals():",
        "    n = 0",
        "    while True: yield n; n+=1",
        "    print(list(islice(naturals(), 5, 10)))  # → [5,6,7,8,9]",
        "    # Пагинация",
        "    data = range(100)",
        "    page = list(islice(data, 20, 30))  # страница 3 (по 10)",
        "    print(page)  # → [20...29]",
        "    # Каждый N-й элемент",
        "    print(list(islice(range(20), 0, None, 3)))  # → [0,3,6,9,12,15,18]"
      ],
      "related": [
        "срезы-списка",
        "itertools.takewhile",
        "itertools.chain"
      ],
      "related_errors": []
    },
    {
      "id": "yield-from",
      "title": "yield from",
      "kind": "term",
      "summary": {
        "ru": "Делегирует yield другому генератору или итерируемому. Упрощает вложенные генераторы.",
        "en": "Delegates yielding to another generator or iterable. It simplifies nested generators."
      },
      "body": {
        "ru": "Это не просто сокращение для цикла с yield: yield from пробрасывает во вложенный генератор send() и throw(), а значение из его return становится результатом самого выражения yield from. Ради обычного перебора экономия невелика, а вот для сопрограмм и рекурсивного обхода (дерево, вложенные списки) — именно то, ради чего конструкцию добавили в Python 3.3.",
        "en": "It is more than shorthand for a for loop with yield: yield from forwards send() and throw() into the subgenerator, and whatever the subgenerator returns becomes the value of the yield from expression itself. For plain iteration the savings are cosmetic; the real payoff is coroutines and recursive traversal (trees, nested lists), which is why Python 3.3 introduced it."
      },
      "syntax": "yield from iterable",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-generator",
      "version": "",
      "section": "Итераторы и генераторы",
      "subcat": "генераторы",
      "color_group": "iter",
      "aliases": [
        "делегирование генератора",
        "вложенные генераторы"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "def chain(*iterables):",
        "for it in iterables:",
        "yield from it",
        "print(list(chain([1,2],[3,4],[5])))  # → [1,2,3,4,5]",
        "def flatten(lst):",
        "for item in lst:",
        "if isinstance(item, list):",
        "yield from flatten(item)",
        "else:",
        "yield item",
        "print(list(flatten([1,[2,[3,4]],5])))  # → [1,2,3,4,5]",
        "def gen_a():",
        "yield 1; yield 2",
        "def gen_b():",
        "yield from gen_a()",
        "yield 3",
        "print(list(gen_b()))  # → [1,2,3]",
        "# yield from с range",
        "def gen_range(n):",
        "yield from range(n)",
        "print(list(gen_range(5)))  # → [0,1,2,3,4]",
        "# yield from возвращает значение (return в под-генераторе)",
        "def sub():",
        "yield 1; yield 2",
        "return 'done'",
        "def main():",
        "result = yield from sub()",
        "print('sub returned:', result)  # → done",
        "list(main())  # → [1,2]"
      ],
      "related": [
        "generator-function-yield",
        "itertools.chain",
        "генераторное-выражение"
      ],
      "related_errors": []
    },
    {
      "id": "генераторное-выражение",
      "title": "Генераторное выражение",
      "kind": "term",
      "summary": {
        "ru": "Ленивый аналог list comprehension в круглых скобках. Не создаёт список в памяти.",
        "en": "The lazy counterpart of a list comprehension, written in parentheses. It does not build a list in memory."
      },
      "body": {
        "ru": "Генератор одноразовый: после list(gen) второй проход даст пустоту, а не прежние значения — частая причина «почему сумма ноль». Ещё тонкость: самое левое iterable вычисляется сразу при создании выражения, а всё остальное — лениво, поэтому изменение внешних переменных после создания генератора повлияет на результат.",
        "en": "A generator expression is single-use: after list(gen) a second pass yields nothing rather than the old values, which is a common cause of a mysterious zero sum. One more subtlety: the leftmost iterable is evaluated immediately when the expression is created, while everything else is deferred, so changing outer variables afterwards still affects the output."
      },
      "syntax": "(expr for x in iterable if condition)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-generator",
      "version": "",
      "section": "Итераторы и генераторы",
      "subcat": "генераторы",
      "color_group": "iter",
      "aliases": [
        "генератор в круглых скобках",
        "ленивая генерация без списка в памяти"
      ],
      "keywords": [],
      "tags": [
        "iter"
      ],
      "examples": [
        "gen = (x**2 for x in range(5))",
        "print(type(gen))  # → generator",
        "print(list(gen))  # → [0,1,4,9,16]",
        "# Ленивость — память не расходуется",
        "big_gen = (x**2 for x in range(10**6))",
        "print(next(big_gen))  # → 0",
        "print(next(big_gen))  # → 1",
        "# sum с генератором",
        "print(sum(x**2 for x in range(10)))  # → 285",
        "# Фильтрация",
        "evens = (x for x in range(20) if x%2==0)",
        "print(list(evens))  # → [0,2,4,6,8,10,12,14,16,18]",
        "# В функции — скобки не нужны",
        "print(max(len(s) for s in ['hi','hello','ok']))  # → 5",
        "# Вложенный генератор",
        "matrix = [[1,2],[3,4],[5,6]]",
        "flat = (x for row in matrix for x in row)",
        "print(list(flat))  # → [1,2,3,4,5,6]"
      ],
      "related": [
        "списочные-выражения-list-comprehension",
        "generator-function-yield",
        "генераторы-множеств-set-comprehension"
      ],
      "related_errors": []
    },
    {
      "id": "итератор-__iter__-__next__",
      "title": "Итератор __iter__ / __next__",
      "kind": "term",
      "summary": {
        "ru": "Итератор — объект с __iter__ и __next__. __next__ возвращает следующее значение или поднимает StopIteration.",
        "en": "An iterator is an object with __iter__ and __next__. __next__ returns the next value or raises StopIteration."
      },
      "body": {
        "ru": "Не путайте итерируемое и итератор: список можно обойти сколько угодно раз, а итератор — ровно один, и после исчерпания он обязан продолжать поднимать StopIteration, а не начинать заново. Поэтому класс, у которого __iter__ возвращает self, во втором цикле for молча ничего не выдаст; чтобы объект переиспользовался, __iter__ должен возвращать каждый раз новый объект-итератор.",
        "en": "Do not confuse an iterable with an iterator: a list can be traversed any number of times, an iterator exactly once, and once exhausted it must keep raising StopIteration rather than restarting. That is why a class whose __iter__ returns self silently produces nothing in a second for loop; to make the object reusable, __iter__ should hand back a fresh iterator object each time."
      },
      "syntax": "class Iter:\n    def __iter__(self): return self\n    def __next__(self): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-generator",
      "version": "",
      "section": "Итераторы и генераторы",
      "subcat": "протокол итератора",
      "color_group": "iter",
      "aliases": [
        "протокол итератора",
        "сделать класс перебираемым",
        "свой итератор"
      ],
      "keywords": [
        "__iter__",
        "__next__"
      ],
      "tags": [
        "iter"
      ],
      "examples": [
        "class CountUp:",
        "def __init__(self, start, stop):",
        "self.current = start",
        "self.stop = stop",
        "def __iter__(self):",
        "return self",
        "def __next__(self):",
        "if self.current >= self.stop:",
        "raise StopIteration",
        "val = self.current",
        "self.current += 1",
        "return val",
        "for x in CountUp(1, 4):",
        "print(x, end=' ')  # → 1 2 3",
        "class Squares:",
        "def __init__(self, n):",
        "self.i = 0; self.n = n",
        "def __iter__(self): return self",
        "def __next__(self):",
        "if self.i >= self.n: raise StopIteration",
        "val = self.i**2",
        "self.i += 1",
        "return val",
        "print(list(Squares(5)))  # → [0,1,4,9,16]",
        "# Итератор — одноразовый!",
        "it = iter([1,2,3])",
        "print(list(it))  # → [1,2,3]",
        "print(list(it))  # → [] исчерпан!",
        "class FibIter:",
        "def __init__(self, max_n):",
        "self.a, self.b = 0, 1",
        "self.max_n = max_n",
        "def __iter__(self): return self",
        "def __next__(self):",
        "if self.a > self.max_n: raise StopIteration",
        "val = self.a",
        "self.a, self.b = self.b, self.a+self.b",
        "return val",
        "print(list(FibIter(50)))  # → [0,1,1,2,3,5,8,13,21,34]",
        "# for цикл использует __iter__/__next__",
        "lst = [10, 20, 30]",
        "it = iter(lst)",
        "while True:",
        "try:",
        "print(next(it))",
        "except StopIteration:",
        "break  # → 10 20 30",
        "class Reversed:",
        "def __init__(self, data):",
        "self.data = data",
        "self.i = len(data) - 1",
        "def __iter__(self): return self",
        "def __next__(self):",
        "if self.i < 0: raise StopIteration",
        "val = self.data[self.i]",
        "self.i -= 1",
        "return val",
        "print(list(Reversed([1,2,3,4])))  # → [4,3,2,1]"
      ],
      "related": [
        "iter-next",
        "stopiteration",
        "generator-function-yield",
        "collections.abc.Iterator"
      ],
      "related_errors": []
    },
    {
      "id": "len-min-max-sum-для-кортежей",
      "title": "len/min/max/sum для кортежей",
      "kind": "term",
      "summary": {
        "ru": "Встроенные функции работают с кортежами так же, как со списками.",
        "en": "The built-in functions work with tuples exactly as they do with lists."
      },
      "body": {
        "ru": "len у кортежа отдаётся за O(1) — длина хранится готовой, а вот min, max и sum каждый раз обходят все элементы, так что четыре вызова подряд означают четыре прохода. На пустом кортеже sum спокойно вернёт 0, а min и max упадут с ValueError.",
        "en": "len on a tuple is O(1) because the length is stored, whereas min, max and sum each walk every element, so calling them one after another means several passes over the same data. On an empty tuple sum quietly returns 0, but min and max raise ValueError."
      },
      "syntax": "len(t)  |  min(t)  |  max(t)  |  sum(t)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
      "version": "",
      "section": "Кортежи (tuple)",
      "subcat": "агрегация",
      "color_group": "seq",
      "aliases": [
        "длина кортежа",
        "сумма элементов кортежа",
        "максимум и минимум в кортеже"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "t = (3, 1, 4, 1, 5, 9, 2, 6)",
        "print(len(t))",
        "# → 8",
        "print(min(t))",
        "# → 1",
        "print(max(t))",
        "# → 9",
        "print(sum(t))",
        "# → 31"
      ],
      "related": [
        "len",
        "sum",
        "min",
        "max"
      ],
      "related_errors": []
    },
    {
      "id": "tuple.count",
      "title": "tuple.count",
      "kind": "function",
      "summary": {
        "ru": "Возвращает число вхождений значения в кортеж (сравнение через ==).",
        "en": "Return the number of occurrences of a value in the tuple (compared with ==)."
      },
      "body": {
        "ru": "Каждый вызов — линейный проход по кортежу, O(n). Считать так десяток разных значений подряд расточительно: дешевле один раз построить collections.Counter(t). Сравнение идёт через ==, поэтому t.count(1) заодно посчитает все True, а t.count(0) — все False.",
        "en": "Every call walks the whole tuple, O(n), so counting a dozen different values one by one is wasteful — build collections.Counter(t) once instead. Matching uses ==, which means t.count(1) also counts every True in the tuple, and t.count(0) counts every False."
      },
      "syntax": "t.count(value)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
      "version": "",
      "section": "Кортежи (tuple)",
      "subcat": "методы",
      "color_group": "seq",
      "aliases": [
        "сколько раз элемент встречается в кортеже",
        "подсчёт вхождений в кортеже"
      ],
      "keywords": [],
      "tags": [
        "tuple"
      ],
      "examples": [
        "t = (1, 2, 2, 3, 2)",
        "print(t.count(2))   # → 3",
        "print(t.count(9))   # → 0"
      ],
      "related": [
        "tuple.index",
        "list.count",
        "in"
      ],
      "related_errors": []
    },
    {
      "id": "tuple.index",
      "title": "tuple.index",
      "kind": "function",
      "summary": {
        "ru": "Возвращает индекс первого вхождения значения; опциональные start/end сужают диапазон; ValueError, если не найдено.",
        "en": "Return the index of the first matching value; optional start/end narrow the search; raises ValueError if absent."
      },
      "body": {
        "ru": "Если значения нет, index бросает ValueError — мягкого аналога str.find(), возвращающего -1, у кортежей не существует, так что либо оборачивайте вызов в try, либо сначала проверяйте value in t. Аргумент start только сдвигает точку начала поиска: возвращаемый индекс всё равно отсчитывается от начала кортежа, а не от start.",
        "en": "A missing value raises ValueError — tuples have no forgiving counterpart to str.find() that returns -1, so either wrap the call in try or test value in t first. The start argument only moves where the scan begins; the index you get back is still counted from the beginning of the tuple, not from start."
      },
      "syntax": "t.index(value, start=0, stop=len)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
      "version": "",
      "section": "Кортежи (tuple)",
      "subcat": "методы",
      "color_group": "seq",
      "aliases": [
        "найти индекс элемента в кортеже",
        "позиция элемента в кортеже"
      ],
      "keywords": [],
      "tags": [
        "tuple"
      ],
      "examples": [
        "t = (10, 20, 30, 20)",
        "print(t.index(20))      # → 1",
        "print(t.index(20, 2))   # → 3"
      ],
      "related": [
        "tuple.count",
        "list.index",
        "valueerror"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "zip-с-кортежами",
      "title": "zip() с кортежами",
      "kind": "term",
      "summary": {
        "ru": "zip() работает с кортежами так же, как со списками. Возвращает итератор кортежей.",
        "en": "zip() works with tuples exactly as it does with lists. It returns an iterator of tuples."
      },
      "body": {
        "ru": "zip молча останавливается на самой короткой последовательности: если кортежи разной длины, хвост потеряется без всякого предупреждения — с Python 3.10 на этот случай есть zip(t1, t2, strict=True), который бросит ValueError. Результат ленивый и одноразовый: после list(zip(...)) или первого прохода в цикле повторно пройтись по тому же объекту уже не получится, он пуст.",
        "en": "zip stops at the shortest argument without a word, so mismatched tuple lengths silently drop the tail; since Python 3.10 you can pass strict=True to turn that into a ValueError instead. The result is lazy and single-use: once you have consumed it with list() or a for loop, iterating the same zip object again yields nothing."
      },
      "syntax": "zip(t1, t2, ...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#zip",
      "version": "",
      "section": "Кортежи (tuple)",
      "subcat": "итерация",
      "color_group": "seq",
      "aliases": [
        "объединить кортежи попарно",
        "параллельный перебор двух кортежей"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "t1 = (1, 2, 3)",
        "t2 = ('a', 'b', 'c')",
        "print(list(zip(t1, t2)))",
        "# → [(1, 'a'), (2, 'b'), (3, 'c')]",
        "print(tuple(zip(t1, t2)))",
        "# → ((1, 'a'), (2, 'b'), (3, 'c'))",
        "for x, y in zip(t1, t2):",
        "    print(x, y)",
        "    # → 1 a / 2 b / 3 c",
        "    keys = ('a', 'b', 'c')",
        "    vals = (1, 2, 3)",
        "    print(dict(zip(keys, vals)))",
        "    # → {'a': 1, 'b': 2, 'c': 3}"
      ],
      "related": [
        "zip",
        "zip-со-списками",
        "распаковка-кортежа"
      ],
      "related_errors": []
    },
    {
      "id": "индексирование-и-срезы-кортежа",
      "title": "Индексирование и срезы кортежа",
      "kind": "term",
      "summary": {
        "ru": "Индексирование и срезы работают так же, как у строк и списков, но результат — кортеж или элемент (неизменяемый).",
        "en": "Indexing and slicing work just as they do for strings and lists, but the result is a tuple or an item (immutable)."
      },
      "body": {
        "ru": "Срез всегда возвращает кортеж, даже когда в него попал ровно один элемент: t[0] — это само число, а t[0:1] — кортеж из одного числа. Срез не ругается на выход за границы и молча обрезается до реальной длины, тогда как t[i] с несуществующим индексом сразу даёт IndexError. Копировать кортеж срезом бессмысленно: в CPython t[:] возвращает тот же самый объект, а не копию.",
        "en": "A slice always yields a tuple, even a one-element one: t[0] is the item itself, while t[0:1] is a tuple holding that item. Slices clamp silently to the real length instead of complaining about out-of-range bounds, whereas t[i] with a bad index raises IndexError immediately. Copying a tuple by slicing is pointless — in CPython t[:] hands back the very same object, not a copy."
      },
      "syntax": "t[i]  |  t[a:b:c]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
      "version": "",
      "section": "Кортежи (tuple)",
      "subcat": "индексы/срезы",
      "color_group": "seq",
      "aliases": [
        "срез кортежа",
        "элемент кортежа по индексу",
        "перевернуть кортеж"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "t = (10, 20, 30, 40, 50)",
        "print(t[0])",
        "# → 10",
        "print(t[-1])",
        "# → 50",
        "print(t[1:4])",
        "# → (20, 30, 40)",
        "print(t[::-1])",
        "# → (50, 40, 30, 20, 10)",
        "print(t[::2])",
        "# → (10, 30, 50)",
        "print(t[2:4])",
        "# → (30, 40)"
      ],
      "related": [
        "индексирование-списка",
        "срезы-списка",
        "неизменяемость-кортежа"
      ],
      "related_errors": []
    },
    {
      "id": "кортеж-в-return-функции",
      "title": "Кортеж в return функции",
      "kind": "term",
      "summary": {
        "ru": "Функция может возвращать несколько значений — Python упаковывает их в кортеж.",
        "en": "A function can return several values — Python packs them into a tuple."
      },
      "body": {
        "ru": "Никаких «нескольких значений» на самом деле не возвращается — уходит один кортеж, а разбирает его распаковка на стороне вызова. Отсюда классическая поломка: добавили в return четвёртое значение — и все прежние вызовы вида a, b, c = f() падают с ValueError. Когда значений больше трёх или их порядок легко перепутать, надёжнее NamedTuple или dataclass: там поля читают по имени, а не по позиции.",
        "en": "Nothing genuinely multi-valued happens here: one tuple goes back, and the unpacking at the call site takes it apart. That is where the classic break comes from — add a fourth value to the return and every existing a, b, c = f() raises ValueError. Past three values, or when the order is easy to mix up, a NamedTuple or dataclass is safer, because callers read fields by name instead of by position."
      },
      "syntax": "return a, b, c  # ≡ return (a, b, c)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/simple_stmts.html#the-return-statement",
      "version": "",
      "section": "Кортежи (tuple)",
      "subcat": "применение",
      "color_group": "seq",
      "aliases": [
        "вернуть несколько значений из функции",
        "функция возвращает два значения",
        "множественный возврат"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "def minmax(lst):",
        "    return min(lst), max(lst)",
        "lo, hi = minmax([3, 1, 4, 1, 5, 9])",
        "print(lo, hi)",
        "# → 1 9",
        "def divmod_ex(a, b):",
        "    return a // b, a % b",
        "q, r = divmod_ex(17, 5)",
        "print(q, r)",
        "# → 3 2",
        "def parse_point(s):",
        "    x, y = s.split(',')",
        "    return int(x), int(y)",
        "print(parse_point('3,4'))",
        "# → (3, 4)",
        "result = minmax([3, 1, 4])",
        "print(type(result))",
        "# → <class 'tuple'>",
        "def triple(x):",
        "    return x, x*2, x*3",
        "a, b, c = triple(5)",
        "print(a, b, c)",
        "# → 5 10 15"
      ],
      "related": [
        "распаковка-кортежа",
        "return",
        "создание-кортежа"
      ],
      "related_errors": []
    },
    {
      "id": "кортеж-как-ключ-словаря",
      "title": "Кортеж как ключ словаря",
      "kind": "term",
      "summary": {
        "ru": "Кортежи хэшируемы (если все элементы хэшируемы), поэтому могут быть ключами словаря. Списки — нет.",
        "en": "Tuples are hashable (if all their items are), so they can serve as dictionary keys. Lists cannot."
      },
      "body": {
        "ru": "Неизменяемости самой по себе мало: (1, [2]) — кортеж, но список внутри делает его нехэшируемым, и словарь ответит TypeError: unhashable type: 'list'. В индексе скобки можно опустить — d[1, 2] и d[(1, 2)] это один и тот же ключ, а вот порядок значим: (1, 2) и (2, 1) — два разных ключа.",
        "en": "Immutability alone is not enough: (1, [2]) is a tuple, but the list inside makes it unhashable and the dict answers TypeError: unhashable type: 'list'. Inside a subscript the parentheses are optional — d[1, 2] and d[(1, 2)] are the same lookup — while order does matter, so (1, 2) and (2, 1) are two distinct keys."
      },
      "syntax": "d[tuple_key] = val",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#mapping-types-dict",
      "version": "",
      "section": "Кортежи (tuple)",
      "subcat": "применение",
      "color_group": "seq",
      "aliases": [
        "составной ключ словаря",
        "ключ из нескольких значений",
        "почему список не может быть ключом словаря"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "coords = {(0, 0): 'origin', (1, 0): 'right', (0, 1): 'up'}",
        "print(coords[(0, 0)])",
        "# → 'origin'",
        "grid = {}",
        "for i in range(3):",
        "for j in range(3):",
        "grid[(i, j)] = i * 3 + j",
        "print(grid[(2, 1)])",
        "# → 7",
        "try:",
        "d = {[1, 2]: 'list'}",
        "except TypeError as e:",
        "print(e)",
        "# → unhashable type: 'list'",
        "pair_count = {}",
        "pairs = [(1, 2), (3, 4), (1, 2)]",
        "for p in pairs:",
        "pair_count[p] = pair_count.get(p, 0) + 1",
        "print(pair_count)",
        "# → {(1, 2): 2, (3, 4): 1}",
        "print(hash((1, 2, 3)))",
        "# → число (хэш кортежа)"
      ],
      "related": [
        "неизменяемость-кортежа",
        "hash",
        "frozenset-неизменяемое-множество"
      ],
      "related_errors": []
    },
    {
      "id": "наименованный-кортеж-namedtuple",
      "title": "Наименованный кортеж namedtuple",
      "kind": "term",
      "summary": {
        "ru": "namedtuple создаёт класс кортежа с именованными полями. Поля доступны по имени и по индексу.",
        "en": "namedtuple creates a tuple class with named fields. The fields are reachable both by name and by index."
      },
      "body": {
        "ru": "Это по-прежнему кортеж: полю нельзя присвоить новое значение, изменённую копию делают через _replace(). Подчёркивание в _replace, _asdict и _fields не означает «приватный метод» — так сделано, чтобы имена методов не столкнулись с именами ваших полей. При сравнении namedtuple равен обычному кортежу с теми же значениями, поэтому == не отличит Point(1, 2) от (1, 2).",
        "en": "It is still a tuple: you cannot assign to a field, and you build a modified copy with _replace(). The underscore in _replace, _asdict and _fields does not mean private — it keeps the method names from clashing with your own field names. A namedtuple compares equal to a plain tuple holding the same values, so == will not tell Point(1, 2) apart from (1, 2)."
      },
      "syntax": "from collections import namedtuple\nNT = namedtuple('NT', ['field1', 'field2'])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.namedtuple",
      "version": "",
      "section": "Кортежи (tuple)",
      "subcat": "namedtuple",
      "color_group": "seq",
      "aliases": [
        "кортеж с именованными полями",
        "обращение к полям по имени вместо индекса"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "from collections import namedtuple",
        "Point = namedtuple('Point', ['x', 'y'])",
        "p = Point(3, 4)",
        "print(p)",
        "# → Point(x=3, y=4)",
        "print(p.x, p.y)",
        "# → 3 4",
        "print(p[0], p[1])",
        "# → 3 4 (доступ по индексу)",
        "print(p._asdict())",
        "# → {'x': 3, 'y': 4}",
        "p2 = p._replace(x=10)",
        "print(p2)",
        "# → Point(x=10, y=4)",
        "print(Point._fields)",
        "# → ('x', 'y')",
        "Card = namedtuple('Card', 'rank suit')",
        "c = Card('A', 'spades')",
        "print(f'{c.rank} of {c.suit}')",
        "# → 'A of spades'"
      ],
      "related": [
        "collections.namedtuple",
        "dataclass",
        "typeddict"
      ],
      "related_errors": []
    },
    {
      "id": "неизменяемость-кортежа",
      "title": "Неизменяемость кортежа",
      "kind": "term",
      "summary": {
        "ru": "Кортежи неизменяемы: нельзя изменить, добавить или удалить элемент. Обходы: создать новый кортеж, преобразовать в список.",
        "en": "Tuples are immutable: an item cannot be changed, added or removed. Ways around it: build a new tuple, or convert it to a list."
      },
      "body": {
        "ru": "Неизменяемость тут поверхностная: кортеж запрещает подменять свои ссылки, но если внутри лежит список, менять его никто не мешает — t[0].append(4) отработает штатно. Отсюда и хешируемость: положить кортеж в множество или в ключ словаря можно, только когда хешируемы все его элементы. Классическая ловушка — t[0] += [4] для списка внутри кортежа: список реально расширится, и лишь потом прилетит TypeError на попытке присваивания.",
        "en": "The immutability is shallow: a tuple freezes its references, not the objects behind them, so if it holds a list, t[0].append(4) works fine. That is also why hashability depends on the contents — a tuple can be a dict key or a set member only if every element inside is hashable. The classic trap is t[0] += [4] on a nested list: the list really does grow, and only then does the assignment step raise TypeError."
      },
      "syntax": "t[i] = val  # TypeError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#tuples",
      "version": "",
      "section": "Кортежи (tuple)",
      "subcat": "неизменяемость",
      "color_group": "seq",
      "aliases": [
        "почему нельзя изменить кортеж",
        "как изменить кортеж",
        "ошибка при присваивании элементу кортежа"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "t = (1, 2, 3)",
        "try:",
        "    t[0] = 99",
        "except TypeError as e:",
        "    print(e)",
        "    # → 'tuple' object does not support item assignment",
        "    t2 = t + (4,)",
        "    print(t2)",
        "    # → (1, 2, 3, 4) (новый кортеж)",
        "    lst = list(t)",
        "    lst[0] = 99",
        "    t = tuple(lst)",
        "    print(t)",
        "    # → (99, 2, 3) (через список)",
        "    nested = ([1, 2], [3, 4])",
        "    nested[0].append(5)",
        "    print(nested)",
        "# → ([1, 2, 5], [3, 4]) (вложенный mutable — можно изменить!)",
        "try:",
        "    del t[0]",
        "except TypeError as e:",
        "    print(e)",
        "    # → 'tuple' object doesn't support item deletion"
      ],
      "related": [
        "кортеж-как-ключ-словаря",
        "typeerror",
        "frozenset-неизменяемое-множество"
      ],
      "related_errors": []
    },
    {
      "id": "распаковка-кортежа",
      "title": "Распаковка кортежа",
      "kind": "term",
      "summary": {
        "ru": "Распаковка позволяет присвоить элементы переменным. * захватывает остаток. _ — соглашение для игнорируемых значений.",
        "en": "Unpacking assigns the items to variables. * captures the rest. _ is the convention for values that are ignored."
      },
      "body": {
        "ru": "Ошибка почти всегда одна: слева не столько имён, сколько элементов справа — ValueError: not enough values to unpack. Звёздочка от этого спасает, но *rest всегда становится списком, даже если распаковывали кортеж, и звёздочка в присваивании допустима только одна. Само _ — обычная переменная: значение в неё реально кладётся, просто по соглашению его потом не читают.",
        "en": "The usual failure is a count mismatch — ValueError: not enough values to unpack when the names on the left don't match the items on the right. A starred name absorbs the rest, but it always produces a list even when the source was a tuple, and only one star is allowed per assignment. And _ is an ordinary variable: the value really is stored in it, it is just a convention that nobody reads it afterwards."
      },
      "syntax": "a, b = t  |  a, *rest = t  |  a, _, b = t",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/simple_stmts.html#assignment-statements",
      "version": "",
      "section": "Кортежи (tuple)",
      "subcat": "распаковка",
      "color_group": "seq",
      "aliases": [
        "множественное присваивание",
        "присвоить нескольким переменным сразу",
        "поменять местами две переменные"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "a, b, c = (1, 2, 3)",
        "print(a, b, c)",
        "# → 1 2 3",
        "x, y = (10, 20)",
        "x, y = y, x",
        "print(x, y)",
        "# → 20 10 (swap)",
        "first, *rest = (1, 2, 3, 4, 5)",
        "print(first, rest)",
        "# → 1 [2, 3, 4, 5]",
        "*head, last = (1, 2, 3, 4, 5)",
        "print(head, last)",
        "# → [1, 2, 3, 4] 5",
        "a, _, c = (1, 999, 3)",
        "print(a, c)",
        "# → 1 3 (_ игнорируется)",
        "point = (3, 4)",
        "import math",
        "dist = math.sqrt(sum(v**2 for v in point))",
        "print(dist)",
        "# → 5.0",
        "(a, b), c = (1, 2), 3",
        "print(a, b, c)",
        "# → 1 2 3 (вложенная распаковка)",
        "for a, b in [(1, 2), (3, 4)]:",
        "    print(a + b)",
        "    # → 3 / 7"
      ],
      "related": [
        "распаковка-в-for",
        "распаковка-списка",
        "кортеж-в-return-функции"
      ],
      "related_errors": []
    },
    {
      "id": "создание-кортежа",
      "title": "Создание кортежа",
      "kind": "term",
      "summary": {
        "ru": "Кортеж создаётся скобками (), конструктором tuple(), или простым перечислением. Один элемент требует замыкающей запятой: (x,).",
        "en": "A tuple is written with parentheses (), with the tuple() constructor, or as a plain comma-separated list. A single item needs a trailing comma: (x,)."
      },
      "body": {
        "ru": "Кортеж делает запятая, а не скобки: (1) — это просто число, зато случайно оставленная запятая в конце строки, x = 5, молча превращает 5 в (5,), и позже это вылезает лишними скобками при печати. Конструктор tuple() разбирает итерируемое поэлементно — tuple('abc') даст ('a', 'b', 'c'), а tuple(5) поднимет TypeError, потому что число не итерируемо.",
        "en": "The comma makes the tuple, not the parentheses: (1) is just a number, while a stray trailing comma in x = 5, silently turns 5 into (5,) and later shows up as unexpected parentheses in the output. The tuple() constructor consumes an iterable item by item, so tuple('abc') gives ('a', 'b', 'c') while tuple(5) raises TypeError, because a number is not iterable."
      },
      "syntax": "()  |  (x,)  |  (a, b)  |  tuple(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#tuple",
      "version": "",
      "section": "Кортежи (tuple)",
      "subcat": "создание",
      "color_group": "seq",
      "aliases": [
        "кортеж из одного элемента",
        "преобразовать список в кортеж",
        "пустой кортеж"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "t0 = ()",
        "# → ()",
        "t1 = (1,)",
        "# → (1,) (не (1) — это просто 1)",
        "t2 = (1, 2, 3)",
        "# → (1, 2, 3)",
        "t3 = 1, 2, 3",
        "# → (1, 2, 3)",
        "t4 = tuple([1, 2, 3])",
        "# → (1, 2, 3)",
        "t5 = tuple('abc')",
        "# → ('a', 'b', 'c')",
        "print(type((1)))",
        "# → <class 'int'> (без запятой — не кортеж!)"
      ],
      "related": [
        "tuple",
        "неизменяемость-кортежа",
        "создание-списка"
      ],
      "related_errors": []
    },
    {
      "id": "frozenset-неизменяемое-множество",
      "title": "frozenset — неизменяемое множество",
      "kind": "term",
      "summary": {
        "ru": "frozenset — неизменяемая версия set. Хэшируемо, может быть ключом словаря или элементом другого множества.",
        "en": "frozenset is the immutable version of set. It is hashable, so it can be a dictionary key or an item of another set."
      },
      "body": {
        "ru": "Именно ради неизменяемости frozenset и берут: множество множеств или набор-ключ в словаре иначе не собрать — обычный set нехэшируем и падает с TypeError: unhashable type: 'set'. Методов, меняющих объект на месте (add, discard, pop, update), здесь нет, а вот сравнение работает через границу типов: frozenset({1, 2}) == {1, 2} даёт True. В смешанных операциях тип результата задаёт левый операнд: frozenset | set вернёт frozenset, а set | frozenset — обычный set.",
        "en": "The point of frozenset is hashability: a set of sets or a set-shaped dict key is impossible with plain set, which raises TypeError: unhashable type: 'set'. There are no in-place mutators (no add, discard, pop, update), but comparison crosses the type boundary — frozenset({1, 2}) == {1, 2} is True. In mixed operations the left operand decides the result type: frozenset | set gives a frozenset, set | frozenset gives a set."
      },
      "syntax": "frozenset(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#frozenset",
      "version": "",
      "section": "Множества (set)",
      "subcat": "frozenset",
      "color_group": "mapset",
      "aliases": [
        "замороженное множество",
        "множество как ключ словаря",
        "хэшируемое множество"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "fs = frozenset([1, 2, 3])",
        "print(fs)",
        "# → frozenset({1, 2, 3})",
        "try:",
        "    fs.add(4)",
        "except AttributeError as e:",
        "    print(e)",
        "    # → 'frozenset' object has no attribute 'add'",
        "    d = {frozenset([1, 2]): 'pair'}",
        "    print(d[frozenset([1, 2])])",
        "    # → 'pair' (как ключ словаря)",
        "    s = {frozenset([1, 2]), frozenset([3, 4])}",
        "    print(len(s))",
        "    # → 2 (frozenset как элемент множества)",
        "    fs2 = frozenset([1, 2, 3])",
        "    fs3 = frozenset([2, 3, 4])",
        "    print(fs2 & fs3)",
        "    # → frozenset({2, 3})"
      ],
      "related": [
        "frozenset",
        "set",
        "кортеж-как-ключ-словаря"
      ],
      "related_errors": []
    },
    {
      "id": "frozenset.copy",
      "title": "frozenset.copy",
      "kind": "function",
      "summary": {
        "ru": "У неизменяемого frozenset copy() возвращает тот же объект (копировать нечего).",
        "en": "For an immutable frozenset, copy() returns the same object (nothing to copy)."
      },
      "body": {
        "ru": "Метод оставлен ради единого интерфейса: код, работающий с любым множеством, может звать copy(), не разбираясь, set ему передали или frozenset. Защищать от правок неизменяемый объект не от чего, поэтому копия не создаётся — возвращается он сам. Если нужна копия, которую потом можно менять, берите set(f): copy() мутабельного набора не даст.",
        "en": "The method exists for interface parity: code that accepts any set can call copy() without checking whether it got a set or a frozenset. There is nothing to protect from mutation, so no new object is built — you get the original back. When you need a copy you can actually modify, use set(f); copy() will never hand you a mutable one."
      },
      "syntax": "f.copy()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#frozenset.copy",
      "version": "",
      "section": "Множества (set)",
      "subcat": "frozenset",
      "color_group": "mapset",
      "aliases": [
        "копия неизменяемого множества",
        "копирование замороженного множества"
      ],
      "keywords": [],
      "tags": [
        "frozenset"
      ],
      "examples": [
        "f = frozenset({1, 2})",
        "print(f.copy() is f)   # → True",
        "print(f.copy())   # → frozenset({1, 2})",
        "print(frozenset().copy())   # → frozenset()",
        "s = {1, 2}",
        "print(s.copy() is s)   # → False"
      ],
      "related": [
        "set.copy",
        "frozenset-неизменяемое-множество",
        "copy.copy"
      ],
      "related_errors": []
    },
    {
      "id": "frozenset.difference",
      "title": "frozenset.difference",
      "kind": "function",
      "summary": {
        "ru": "Возвращает новый frozenset — разность (элементы, которых нет в переданных).",
        "en": "Return a new frozenset — the difference (elements not in the others)."
      },
      "body": {
        "ru": "От оператора минус метод отличается тем, что принимает: f - [1, 2] упадёт с TypeError, а f.difference([1, 2]) съест любой итерируемый объект и сколько угодно аргументов за раз. Операция несимметрична — f.difference(g) и g.difference(f) дают разное; если нужно «что есть только у одного из двух», это symmetric_difference. Парного difference_update у frozenset нет: менять на месте нечего, результат всегда новый объект.",
        "en": "The difference from the minus operator is what it accepts: f - [1, 2] raises TypeError, while f.difference([1, 2]) takes any iterable, and as many of them as you like. The operation is not symmetric — f.difference(g) and g.difference(f) differ; for \"in one or the other but not both\" use symmetric_difference. There is no difference_update on frozenset: nothing can be changed in place, so you always get a new object."
      },
      "syntax": "f.difference(*others)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#frozenset.difference",
      "version": "",
      "section": "Множества (set)",
      "subcat": "frozenset",
      "color_group": "mapset",
      "aliases": [
        "разность неизменяемых множеств",
        "вычитание замороженных множеств"
      ],
      "keywords": [],
      "tags": [
        "frozenset"
      ],
      "examples": [
        "print(frozenset({1, 2, 3}).difference({2}))   # → frozenset({1, 3})",
        "print(frozenset({1, 2, 3, 4}).difference({2}, {4}))   # → frozenset({1, 3})",
        "print(frozenset({1, 2, 3}).difference([1, 2]))   # → frozenset({3})",
        "print(sorted(frozenset('hello').difference('aeiou')))   # → ['h', 'l']",
        "print(frozenset({1, 2}).difference({1, 2}))   # → frozenset()",
        "print(frozenset({1, 2, 3}) - [1])   # → TypeError"
      ],
      "related": [
        "set.difference",
        "frozenset.intersection",
        "frozenset.union"
      ],
      "related_errors": []
    },
    {
      "id": "frozenset.intersection",
      "title": "frozenset.intersection",
      "kind": "function",
      "summary": {
        "ru": "Возвращает новый frozenset — пересечение с переданными.",
        "en": "Return a new frozenset — the intersection with the others."
      },
      "body": {
        "ru": "Когда с обеих сторон множества, CPython обходит меньшее и проверяет принадлежность в большем, так что цена — порядка размера меньшего набора, а не суммы. Аргументом метода (в отличие от оператора &) может быть любой итерируемый объект, и не один: f.intersection(a, b) пересекает сразу с обоими. Если сам результат не нужен, а важен лишь факт наличия общих элементов, дешевле isdisjoint — он обрывается на первом совпадении и не строит новый набор.",
        "en": "When both sides are sets, CPython walks the smaller one and tests membership in the larger, so the cost scales with the smaller set, not with the total. Unlike the & operator, the method takes any iterable, and several at once: f.intersection(a, b) intersects with both. If you only need to know whether anything overlaps, isdisjoint is cheaper — it stops at the first common element instead of building a whole new set."
      },
      "syntax": "f.intersection(*others)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#frozenset.intersection",
      "version": "",
      "section": "Множества (set)",
      "subcat": "frozenset",
      "color_group": "mapset",
      "aliases": [
        "пересечение неизменяемых множеств",
        "общие элементы замороженных множеств"
      ],
      "keywords": [],
      "tags": [
        "frozenset"
      ],
      "examples": [
        "print(frozenset({1, 2, 3}).intersection({2, 3}))   # → frozenset({2, 3})",
        "print(frozenset({1, 2, 3, 4}).intersection({2, 3, 4}, {3, 4, 5}))   # → frozenset({3, 4})",
        "print(frozenset({1, 2, 3}).intersection([3, 4, 5]))   # → frozenset({3})",
        "print(sorted(frozenset('hello').intersection('world')))   # → ['l', 'o']",
        "print(frozenset({1, 2}).intersection({3, 4}))   # → frozenset()",
        "print(frozenset({1, 2, 3}) & [2, 3])   # → TypeError"
      ],
      "related": [
        "set.intersection",
        "frozenset.union",
        "frozenset.difference",
        "операции-над-множествами"
      ],
      "related_errors": []
    },
    {
      "id": "frozenset.isdisjoint",
      "title": "frozenset.isdisjoint",
      "kind": "function",
      "summary": {
        "ru": "True, если у frozenset нет общих элементов с переданным множеством.",
        "en": "True if the frozenset has no elements in common with the other set."
      },
      "body": {
        "ru": "В отличие от not (f & other), isdisjoint не строит промежуточное множество и обрывается на первом же общем элементе — на длинных данных разница заметна. Аргументом годится любой итерируемый объект, но итератор при этом вычерпывается частично или целиком, и второй раз его уже не проверить. Пустое множество не пересекается ни с чем, включая само себя: frozenset().isdisjoint(frozenset()) — True.",
        "en": "Unlike not (f & other), isdisjoint builds no intermediate set and bails out at the first shared element, which matters on large inputs. Any iterable works as the argument, but a plain iterator gets consumed partially or fully in the process, so you cannot test it again. The empty set is disjoint from everything, itself included: frozenset().isdisjoint(frozenset()) is True."
      },
      "syntax": "f.isdisjoint(other)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#frozenset.isdisjoint",
      "version": "",
      "section": "Множества (set)",
      "subcat": "frozenset",
      "color_group": "mapset",
      "aliases": [
        "непересекающиеся замороженные множества",
        "нет общих элементов у неизменяемого множества"
      ],
      "keywords": [],
      "tags": [
        "frozenset"
      ],
      "examples": [
        "print(frozenset({1, 2}).isdisjoint({3, 4}))   # → True",
        "print(frozenset({1, 2}).isdisjoint({2, 3}))   # → False",
        "print(frozenset({1, 2}).isdisjoint([3, 4, 5]))   # → True",
        "print(frozenset('rhythm').isdisjoint('aeiou'))   # → True",
        "print(frozenset({1, 2, 3}).isdisjoint(range(3, 6)))   # → False",
        "print(frozenset().isdisjoint({1, 2}))   # → True"
      ],
      "related": [
        "set.isdisjoint",
        "frozenset.intersection",
        "frozenset.issubset"
      ],
      "related_errors": []
    },
    {
      "id": "frozenset.issubset",
      "title": "frozenset.issubset",
      "kind": "function",
      "summary": {
        "ru": "True, если все элементы frozenset входят в другое множество (подмножество).",
        "en": "True if every element of the frozenset is in the other set (subset)."
      },
      "body": {
        "ru": "Метод принимает любой итерируемый объект, а оператор <= — только множество: f <= [1, 2, 3] упадёт с TypeError, а f.issubset([1, 2, 3]) сработает. Множество считается подмножеством самого себя, поэтому равные множества дают True; для строгого подмножества нужен оператор <. Строка разбирается посимвольно, так что frozenset('cat').issubset('character') — про буквы, а не про подстроку.",
        "en": "The method accepts any iterable, while the <= operator insists on a real set: f <= [1, 2, 3] raises TypeError, but f.issubset([1, 2, 3]) works. A set is a subset of itself, so equal sets give True; for a strict subset use <. Strings are consumed character by character, so frozenset('cat').issubset('character') is about letters, not substrings."
      },
      "syntax": "f.issubset(other)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#frozenset.issubset",
      "version": "",
      "section": "Множества (set)",
      "subcat": "frozenset",
      "color_group": "mapset",
      "aliases": [
        "подмножество неизменяемого множества",
        "проверка вложенности замороженных множеств"
      ],
      "keywords": [],
      "tags": [
        "frozenset"
      ],
      "examples": [
        "print(frozenset({1, 2}).issubset({1, 2, 3}))   # → True",
        "print(frozenset({1, 4}).issubset({1, 2, 3}))   # → False",
        "print(frozenset({1, 2}).issubset([1, 2, 3]))   # → True",
        "print(frozenset('cat').issubset('character'))   # → True",
        "print(frozenset({1, 2}).issubset({1, 2}))   # → True",
        "print(frozenset({1, 2}) < {1, 2})   # → False"
      ],
      "related": [
        "frozenset.issuperset",
        "set.issubset",
        "frozenset.isdisjoint"
      ],
      "related_errors": []
    },
    {
      "id": "frozenset.issuperset",
      "title": "frozenset.issuperset",
      "kind": "function",
      "summary": {
        "ru": "True, если frozenset содержит все элементы другого множества (надмножество).",
        "en": "True if the frozenset contains every element of the other set (superset)."
      },
      "body": {
        "ru": "Эти две проверки не противоположны: у {1, 2} и {2, 3} ни одно не содержит другого, и issubset, и issuperset вернут False. Оператор >= требует множества с обеих сторон, тогда как методу сгодится любой итерируемый — список, кортеж, строка.",
        "en": "Subset and superset are not opposites: for {1, 2} and {2, 3} neither contains the other, so both issubset and issuperset return False. The >= operator demands sets on both sides, whereas the method happily takes any iterable — a list, tuple or string."
      },
      "syntax": "f.issuperset(other)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#frozenset.issuperset",
      "version": "",
      "section": "Множества (set)",
      "subcat": "frozenset",
      "color_group": "mapset",
      "aliases": [
        "надмножество неизменяемого множества",
        "замороженное множество содержит другое"
      ],
      "keywords": [],
      "tags": [
        "frozenset"
      ],
      "examples": [
        "print(frozenset({1, 2, 3}).issuperset({1, 2}))   # → True",
        "print(frozenset({1, 2, 3}).issuperset({1, 5}))   # → False",
        "print(frozenset({1, 2, 3}).issuperset([2, 3]))   # → True",
        "print(frozenset('0123456789').issuperset('2026'))   # → True",
        "print(frozenset({1, 2}).issuperset([]))   # → True",
        "print(frozenset({1, 2}) > {1, 2})   # → False"
      ],
      "related": [
        "frozenset.issubset",
        "set.issuperset",
        "frozenset.isdisjoint"
      ],
      "related_errors": []
    },
    {
      "id": "frozenset.symmetric_difference",
      "title": "frozenset.symmetric_difference",
      "kind": "function",
      "summary": {
        "ru": "Возвращает новый frozenset — симметрическую разность двух множеств.",
        "en": "Return a new frozenset — the symmetric difference of the two sets."
      },
      "body": {
        "ru": "Симметрическая разность — элементы, попавшие ровно в одно из множеств: объединение минус пересечение. В отличие от union() и difference(), метод принимает ровно один аргумент, свернуть три множества за вызов не выйдет. Оператор ^ считает то же самое, но требует множества с обеих сторон.",
        "en": "The symmetric difference keeps items that occur in exactly one of the sets — the union minus the intersection. Unlike union() or difference(), this method takes exactly one argument, so three sets cannot be folded in a single call. The ^ operator computes the same result but requires sets on both sides."
      },
      "syntax": "f.symmetric_difference(other)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#frozenset.symmetric_difference",
      "version": "",
      "section": "Множества (set)",
      "subcat": "frozenset",
      "color_group": "mapset",
      "aliases": [
        "симметрическая разность неизменяемых множеств",
        "элементы только в одном из замороженных множеств"
      ],
      "keywords": [],
      "tags": [
        "frozenset"
      ],
      "examples": [
        "print(frozenset({1, 2}).symmetric_difference({2, 3}))   # → frozenset({1, 3})",
        "print(frozenset({1, 2}).symmetric_difference({3, 4}))   # → frozenset({1, 2, 3, 4})",
        "print(frozenset({1, 2, 3}).symmetric_difference([3, 4]))   # → frozenset({1, 2, 4})",
        "print(sorted(frozenset('abc').symmetric_difference('bcd')))   # → ['a', 'd']",
        "print(frozenset({1, 2}).symmetric_difference({1, 2}))   # → frozenset()",
        "print(frozenset({1, 2}) ^ {2, 3})   # → frozenset({1, 3})"
      ],
      "related": [
        "set.symmetric_difference",
        "frozenset.difference",
        "frozenset.union",
        "операции-над-множествами"
      ],
      "related_errors": []
    },
    {
      "id": "frozenset.union",
      "title": "frozenset.union",
      "kind": "function",
      "summary": {
        "ru": "Возвращает новый frozenset — объединение с переданными итерируемыми.",
        "en": "Return a new frozenset — the union with the given iterables."
      },
      "body": {
        "ru": "Оператор | требует множеств с обеих сторон, а union() принимает любые итерируемые и сразу несколько за один вызов. Тип результата задаёт объект слева: frozenset.union(обычное_множество) вернёт именно frozenset. Изменить frozenset на месте нельзя — метода update() у него нет, только новый объект.",
        "en": "The | operator wants sets on both sides; union() swallows any iterables, and several of them in one call. The result type comes from the object the method is called on, so frozenset.union(a_set) still yields a frozenset. A frozenset is never updated in place — it has no update(), only a freshly built object."
      },
      "syntax": "f.union(*others)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#frozenset.union",
      "version": "",
      "section": "Множества (set)",
      "subcat": "frozenset",
      "color_group": "mapset",
      "aliases": [
        "объединение неизменяемых множеств",
        "слить замороженные множества"
      ],
      "keywords": [],
      "tags": [
        "frozenset"
      ],
      "examples": [
        "print(frozenset({1, 2}).union({3}))   # → frozenset({1, 2, 3})",
        "print(sorted(frozenset({1, 2}).union([2, 3], (4,))))   # → [1, 2, 3, 4]",
        "print(sorted(frozenset('ab').union('bc')))   # → ['a', 'b', 'c']",
        "print(frozenset().union())   # → frozenset()",
        "print(type(frozenset({1}).union({2, 3})).__name__)   # → frozenset",
        "print(frozenset({1, 2}) | [3])   # → TypeError"
      ],
      "related": [
        "set.union",
        "frozenset.intersection",
        "frozenset.difference",
        "операции-над-множествами"
      ],
      "related_errors": []
    },
    {
      "id": "in-not-in-для-множеств-o-1",
      "title": "in / not in для множеств — O(1)",
      "kind": "function",
      "summary": {
        "ru": "Проверка принадлежности в множестве работает за O(1) (хэш-таблица), в отличие от O(n) для списков.",
        "en": "A membership test on a set takes O(1) (a hash table), unlike O(n) for lists."
      },
      "body": {
        "ru": "O(1) — усреднённая оценка работы хеш-таблицы, а не гарантия, и она требует, чтобы искомое значение было хешируемым: поиск списка внутри множества упадёт с TypeError. Перегонять список в множество ради единственной проверки смысла нет — само построение стоит O(n) и окупается, только если проверок много.",
        "en": "The O(1) is an average over the hash table rather than a guarantee, and it requires the value to be hashable — looking up a list raises TypeError. Converting a list to a set for one membership test gains nothing, because building the set is O(n); it only pays off when you test many times."
      },
      "syntax": "item in s  |  item not in s",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset",
      "version": "",
      "section": "Множества (set)",
      "subcat": "поиск",
      "color_group": "mapset",
      "aliases": [
        "проверка принадлежности",
        "есть ли элемент в множестве",
        "быстрый поиск элемента"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "s = {1, 2, 3, 4, 5}",
        "print(3 in s)",
        "# → True",
        "print(6 in s)",
        "# → False",
        "print(6 not in s)",
        "# → True",
        "big = set(range(10**6))",
        "print(999999 in big)",
        "# → True (мгновенно, O(1))",
        "banned = {'spam', 'ads', 'junk'}",
        "msg = 'spam email'",
        "print(any(w in banned for w in msg.split()))",
        "# → True"
      ],
      "related": [
        "in-not-in-для-списков",
        "in-not-in-для-словаря",
        "хеш-таблица-dict",
        "создание-множества"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "set.add",
      "title": "set.add",
      "kind": "function",
      "summary": {
        "ru": "Добавляет элемент в множество (на месте); дубликаты игнорируются.",
        "en": "Add an element to the set in place; duplicates are ignored."
      },
      "body": {
        "ru": "Метод возвращает None, поэтому s = s.add(3) молча превратит множество в None — классическая ошибка. Элемент обязан быть хешируемым: список добавить нельзя (TypeError: unhashable type), понадобится кортеж или frozenset. Совпадение ищется по хешу и ==, так что 1, 1.0 и True для множества один элемент, и повторный add не заменит уже лежащий там объект.",
        "en": "It returns None, so s = s.add(3) quietly turns your set into None — a classic slip. The element must be hashable: a list raises TypeError (unhashable type), so pass a tuple or a frozenset. Membership is decided by hash and ==, which makes 1, 1.0 and True the same element, and adding a duplicate leaves the stored object untouched."
      },
      "syntax": "s.add(elem)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.add",
      "version": "",
      "section": "Множества (set)",
      "subcat": "добавление",
      "color_group": "mapset",
      "aliases": [
        "добавить элемент в множество",
        "вставить значение в множество"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "s = {1, 2}",
        "s.add(3)",
        "print(sorted(s))   # → [1, 2, 3]",
        "s.add(2)",
        "print(sorted(s))   # → [1, 2, 3]"
      ],
      "related": [
        "set.update",
        "set.discard",
        "set.remove",
        "list.append"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "set.clear",
      "title": "set.clear",
      "kind": "function",
      "summary": {
        "ru": "Удаляет все элементы, оставляя пустое множество (на месте).",
        "en": "Remove all elements, leaving an empty set (in place)."
      },
      "body": {
        "ru": "Метод очищает сам объект, поэтому все имена, ссылающиеся на это множество, увидят его пустым. Присваивание нового пустого множества, наоборот, перевешивает только одно имя — остальные ссылки продолжат смотреть на старые данные. На уже пустом множестве вызов не ошибка.",
        "en": "clear() empties the object itself, so every name bound to that set sees it become empty. Rebinding a name to a fresh empty set instead changes only that one name and leaves other references pointing at the old data. Calling it on an already empty set is harmless."
      },
      "syntax": "s.clear()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.clear",
      "version": "",
      "section": "Множества (set)",
      "subcat": "изменение",
      "color_group": "mapset",
      "aliases": [
        "очистить множество",
        "удалить все элементы множества"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "s = {1, 2, 3}",
        "s.clear()",
        "print(s)   # → set()"
      ],
      "related": [
        "list.clear",
        "dict.clear",
        "set.discard",
        "set.pop"
      ],
      "related_errors": []
    },
    {
      "id": "set.copy",
      "title": "set.copy",
      "kind": "function",
      "summary": {
        "ru": "Возвращает поверхностную копию множества (новый независимый set).",
        "en": "Return a shallow copy of the set (a new independent set)."
      },
      "body": {
        "ru": "Простое присваивание копии не делает: оба имени указывают на одно множество, и добавление через любое из них видно через другое — независимый объект даёт именно copy(). Поверхностность тут почти не мешает, потому что элементы множества обязаны быть хешируемыми, то есть на практике это числа, строки и кортежи; разделяться между копиями могут разве что свои классы с собственным хешем.",
        "en": "Plain assignment is not a copy: both names refer to the same set, so adding through one is visible through the other, and only copy() gives an independent object. The shallowness rarely bites here, since set elements must be hashable — in practice numbers, strings and tuples; the only objects still shared between the copies are custom classes with their own hash."
      },
      "syntax": "s.copy()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.copy",
      "version": "",
      "section": "Множества (set)",
      "subcat": "копирование",
      "color_group": "mapset",
      "aliases": [
        "копия множества",
        "скопировать множество"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "a = {1, 2}",
        "b = a.copy()",
        "b.add(3)",
        "print(sorted(a))   # → [1, 2]",
        "print(sorted(b))   # → [1, 2, 3]"
      ],
      "related": [
        "frozenset.copy",
        "list.copy",
        "dict.copy",
        "copy.deepcopy"
      ],
      "related_errors": []
    },
    {
      "id": "set.difference",
      "title": "set.difference",
      "kind": "function",
      "summary": {
        "ru": "Возвращает новое множество из элементов, которых нет в переданных (разность). Оператор — -.",
        "en": "Return a new set of elements not in the others (difference). Operator: -."
      },
      "body": {
        "ru": "Метод принимает любые итерируемые объекты — список, строку, генератор, а оператор - работает только между двумя множествами и на списке справа падает с TypeError. Разность несимметрична: поменяв операнды местами, получите другой ответ; исходное множество при этом не меняется — для изменения на месте есть difference_update().",
        "en": "The method accepts any iterables — a list, a string, a generator — while the - operator only works between two sets and raises TypeError on anything else. Difference is not symmetric: swap the operands and you get a different answer. The original set is left untouched; use difference_update() when you want to shrink it in place."
      },
      "syntax": "s.difference(*others)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.difference",
      "version": "",
      "section": "Множества (set)",
      "subcat": "теория множеств",
      "color_group": "mapset",
      "aliases": [
        "разность множеств",
        "вычесть одно множество из другого",
        "элементы только в первом множестве"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "print(sorted({1, 2, 3}.difference({2, 3})))   # → [1]",
        "print(sorted({1, 2, 3, 4}.difference({2}, [3])))   # → [1, 4]",
        "print(sorted({1, 2, 3} - {3}))   # → [1, 2]",
        "print(sorted({1, 2, 3}.difference({3, 4})), sorted({3, 4}.difference({1, 2, 3})))   # → [1, 2] [4]",
        "print(sorted({1, 2}.difference({1, 2, 3})))   # → []",
        "print({1, 2, 3} - [3])   # → TypeError"
      ],
      "related": [
        "set.difference_update",
        "set.intersection",
        "set.union",
        "операции-над-множествами"
      ],
      "related_errors": []
    },
    {
      "id": "set.difference_update",
      "title": "set.difference_update",
      "kind": "function",
      "summary": {
        "ru": "Удаляет из множества элементы, найденные в переданных (разность на месте).",
        "en": "Remove from the set the elements found in the others (in-place difference)."
      },
      "body": {
        "ru": "Метод меняет множество на месте и возвращает None — если присвоить результат обратно в переменную, множество затрётся значением None. Элементы, которых в множестве не было, просто игнорируются: никакого KeyError, в отличие от remove().",
        "en": "It mutates the set in place and returns None, so assigning the result back to your variable silently replaces the set with None. Elements that were not there in the first place are ignored — no KeyError, unlike remove()."
      },
      "syntax": "s.difference_update(*others)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.difference_update",
      "version": "",
      "section": "Множества (set)",
      "subcat": "теория множеств",
      "color_group": "mapset",
      "aliases": [
        "разность множеств на месте",
        "удалить элементы другого множества"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "s = {1, 2, 3, 4}",
        "s.difference_update({2, 4})",
        "print(sorted(s))   # → [1, 3]"
      ],
      "related": [
        "set.difference",
        "set.intersection_update",
        "set.symmetric_difference_update",
        "операции-над-множествами"
      ],
      "related_errors": []
    },
    {
      "id": "set.discard",
      "title": "set.discard",
      "kind": "function",
      "summary": {
        "ru": "Удаляет элемент, если он есть; отсутствие элемента ошибкой не считается (в отличие от remove).",
        "en": "Remove an element if present; a missing element is not an error (unlike remove)."
      },
      "body": {
        "ru": "Именно за молчаливость discard чаще всего и попадаются: опечатка в элементе или не тот тип (строка '2' вместо числа 2) ничего не удалят и никак себя не проявят. Берите discard, когда отсутствие элемента — нормальный ход событий; если элемент обязан там быть, remove сразу покажет ошибку в логике.",
        "en": "The silence of discard is exactly what trips people up: a typo or a mismatched type (the string '2' instead of the number 2) removes nothing and reports nothing. Reach for discard when a missing element is a normal outcome; if the element is supposed to be there, remove will surface the logic error immediately."
      },
      "syntax": "s.discard(elem)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.discard",
      "version": "",
      "section": "Множества (set)",
      "subcat": "удаление",
      "color_group": "mapset",
      "aliases": [
        "удалить элемент без ошибки",
        "убрать элемент если он есть"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "s = {1, 2, 3}",
        "s.discard(2)",
        "s.discard(9)",
        "print(sorted(s))   # → [1, 3]"
      ],
      "related": [
        "set.remove",
        "set.pop",
        "set.add"
      ],
      "related_errors": []
    },
    {
      "id": "set.intersection",
      "title": "set.intersection",
      "kind": "function",
      "summary": {
        "ru": "Возвращает новое множество из элементов, общих для всех (пересечение). Оператор — &.",
        "en": "Return a new set of elements common to all (intersection). Operator: &."
      },
      "body": {
        "ru": "Метод принимает любые итерируемые объекты, а оператор & — только множества. Если нужен лишь ответ «пересекаются ли вообще», берите isdisjoint(): он даёт его, не собирая целое новое множество.",
        "en": "The method takes any iterables; the & operator takes sets only. When all you need is whether two collections overlap at all, isdisjoint() answers that without building a whole new set."
      },
      "syntax": "s.intersection(*others)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.intersection",
      "version": "",
      "section": "Множества (set)",
      "subcat": "теория множеств",
      "color_group": "mapset",
      "aliases": [
        "пересечение множеств",
        "общие элементы двух списков"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "print(sorted({1, 2, 3}.intersection({2, 3, 4})))   # → [2, 3]",
        "print(sorted({1, 2, 3}.intersection({2, 3}, [3, 9])))   # → [3]",
        "print(sorted({1, 2, 3} & {3, 4}))   # → [3]",
        "print(sorted(set('python').intersection('typo')))   # → ['o', 'p', 't', 'y']",
        "print({1, 2}.intersection({3}))   # → set()",
        "print({1, 2} & [2])   # → TypeError"
      ],
      "related": [
        "set.intersection_update",
        "set.union",
        "set.difference",
        "операции-над-множествами"
      ],
      "related_errors": []
    },
    {
      "id": "set.intersection_update",
      "title": "set.intersection_update",
      "kind": "function",
      "summary": {
        "ru": "Оставляет только элементы, общие со всеми переданными (пересечение на месте).",
        "en": "Keep only elements found in all the others (in-place intersection)."
      },
      "body": {
        "ru": "Меняет множество на месте и возвращает None, так что вызывать его в цепочке или присваивать результат бессмысленно. Все имена, указывающие на это же множество, увидят усечённый вариант — если исходный набор ещё понадобится, используйте intersection() и новую переменную.",
        "en": "It shrinks the set in place and returns None, so chaining the call or assigning its result is a mistake. Every name bound to that same set sees the trimmed version; if you still need the original contents, use intersection() and store the result in a new variable."
      },
      "syntax": "s.intersection_update(*others)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.intersection_update",
      "version": "",
      "section": "Множества (set)",
      "subcat": "теория множеств",
      "color_group": "mapset",
      "aliases": [
        "пересечение на месте",
        "оставить только общие элементы"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "s = {1, 2, 3, 4}",
        "s.intersection_update({2, 3, 5})",
        "print(sorted(s))   # → [2, 3]"
      ],
      "related": [
        "set.intersection",
        "set.difference_update",
        "set.symmetric_difference_update"
      ],
      "related_errors": []
    },
    {
      "id": "set.isdisjoint",
      "title": "set.isdisjoint",
      "kind": "function",
      "summary": {
        "ru": "True, если у множеств нет общих элементов (пересечение пусто).",
        "en": "True if the two sets have no elements in common (empty intersection)."
      },
      "body": {
        "ru": "Дешевле, чем not (a & b): промежуточное множество не строится, а перебор прекращается на первом же общем элементе. Аргументом может быть любой итерируемый объект, а не только множество — оператор & такой вольности не допускает. Пустое множество не пересекается ни с чем, поэтому для него ответ всегда True.",
        "en": "Cheaper than not (a & b): no intermediate set is built and the scan stops at the first shared element it finds. The argument may be any iterable, not just a set — the & operator refuses anything but a set. An empty set shares nothing with anything, so it is always disjoint."
      },
      "syntax": "s.isdisjoint(other)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.isdisjoint",
      "version": "",
      "section": "Множества (set)",
      "subcat": "проверка",
      "color_group": "mapset",
      "aliases": [
        "нет общих элементов",
        "непересекающиеся множества",
        "пересечение пусто"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "print({1, 2}.isdisjoint({3, 4}))   # → True",
        "print({1, 2}.isdisjoint({2, 3}))   # → False",
        "print({1, 2}.isdisjoint([3, 4, 5]))   # → True",
        "print({1, 2}.isdisjoint(set()))   # → True",
        "print({'spam', 'ads'}.isdisjoint('hello world spam'.split()))   # → False",
        "print(not ({1, 2} & {2, 3}))   # → False"
      ],
      "related": [
        "set.intersection",
        "set.issubset",
        "frozenset.isdisjoint"
      ],
      "related_errors": []
    },
    {
      "id": "set.issubset",
      "title": "set.issubset",
      "kind": "function",
      "summary": {
        "ru": "True, если все элементы множества входят в другое (подмножество). Оператор — <=.",
        "en": "True if every element is contained in the other set (subset). Operator: <=."
      },
      "body": {
        "ru": "Метод глотает любой итерируемый объект, а оператор <= требует множества с обеих сторон: {1, 2} <= [1, 2, 3] падает с TypeError. Пустое множество — подмножество любого, включая само себя; строгое подмножество (без равенства) проверяется оператором <.",
        "en": "The method accepts any iterable, while the <= operator demands sets on both sides: {1, 2} <= [1, 2, 3] raises TypeError. The empty set is a subset of everything, and every set is a subset of itself — for a proper subset, one that excludes equality, use <."
      },
      "syntax": "s.issubset(other)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.issubset",
      "version": "",
      "section": "Множества (set)",
      "subcat": "проверка",
      "color_group": "mapset",
      "aliases": [
        "подмножество",
        "проверка подмножества",
        "все элементы входят в другое множество"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "print({1, 2}.issubset({1, 2, 3}))   # → True",
        "print({1, 4}.issubset({1, 2, 3}))   # → False",
        "print({1, 2}.issubset([1, 2, 3]))   # → True",
        "print(set().issubset({1, 2}))   # → True",
        "print({1, 2} <= {1, 2}, {1, 2} < {1, 2})   # → True False",
        "print({1, 2} <= [1, 2, 3])   # → TypeError"
      ],
      "related": [
        "set.issuperset",
        "set.isdisjoint",
        "frozenset.issubset"
      ],
      "related_errors": []
    },
    {
      "id": "set.issuperset",
      "title": "set.issuperset",
      "kind": "function",
      "summary": {
        "ru": "True, если множество содержит все элементы другого (надмножество). Оператор — >=.",
        "en": "True if the set contains every element of the other (superset). Operator: >=."
      },
      "body": {
        "ru": "Сравнение множеств задаёт лишь частичный порядок: у {1, 2} и {2, 3} ни одно не содержит другое, так что False вернут сразу оба сравнения. Поэтому из not a.issuperset(b) не следует, что b надмножество a, а sorted() или max() по списку множеств дают результат, зависящий от исходного расположения, и смысла не несут.",
        "en": "Set comparison is only a partial order: neither {1, 2} nor {2, 3} contains the other, so both directions come back False. That means not a.issuperset(b) does not imply the reverse relation, and calling sorted() or max() on a list of sets yields an arbitrary answer that depends on the starting arrangement."
      },
      "syntax": "s.issuperset(other)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.issuperset",
      "version": "",
      "section": "Множества (set)",
      "subcat": "проверка",
      "color_group": "mapset",
      "aliases": [
        "надмножество",
        "проверка надмножества",
        "множество содержит другое"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "print({1, 2, 3}.issuperset({1, 2}))   # → True",
        "print({1, 2}.issuperset({1, 3}))      # → False",
        "print({1, 2, 3} >= {1, 2})            # → True",
        "print({1, 2, 3}.issuperset([1, 3]))   # → True",
        "print({1, 2}.issuperset(set()))       # → True",
        "print({1, 2} >= [1])                  # → TypeError"
      ],
      "related": [
        "set.issubset",
        "set.isdisjoint",
        "frozenset.issuperset"
      ],
      "related_errors": []
    },
    {
      "id": "set.pop",
      "title": "set.pop",
      "kind": "function",
      "summary": {
        "ru": "Удаляет и возвращает произвольный элемент множества; KeyError на пустом множестве.",
        "en": "Remove and return an arbitrary element; raises KeyError if the set is empty."
      },
      "body": {
        "ru": "Произвольный не значит случайный: какой элемент уйдёт, определяет внутренний порядок хеш-таблицы, и для маленьких множеств целых чисел это обычно выглядит как «самый маленький». Для честного случайного выбора сначала преобразуйте множество в список — random.choice(list(s)); random.sample с множеством перестал работать в Python 3.11. Сам pop хорош, чтобы разгребать множество в цикле while s.",
        "en": "Arbitrary does not mean random: which element leaves is decided by the hash-table layout, and for small sets of integers it usually looks like the smallest one. For a genuinely random pick convert first — random.choice(list(s)); random.sample stopped accepting sets in Python 3.11. pop itself shines when draining a set in a while s loop."
      },
      "syntax": "s.pop()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.pop",
      "version": "",
      "section": "Множества (set)",
      "subcat": "удаление",
      "color_group": "mapset",
      "aliases": [
        "извлечь элемент из множества",
        "удалить произвольный элемент",
        "достать любой элемент множества"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "s = {1, 2, 3}",
        "x = s.pop()",
        "print(x in {1, 2, 3})   # → True",
        "print(len(s))           # → 2"
      ],
      "related": [
        "set.remove",
        "set.discard",
        "list.pop",
        "set.clear"
      ],
      "related_errors": [
        "KeyError"
      ]
    },
    {
      "id": "set.remove",
      "title": "set.remove",
      "kind": "function",
      "summary": {
        "ru": "Удаляет элемент; KeyError, если элемента нет (в отличие от discard).",
        "en": "Remove an element; raises KeyError if it is absent (unlike discard)."
      },
      "body": {
        "ru": "KeyError здесь говорит, что элемента в множестве уже не было, а это почти всегда ошибка в логике, а не рядовой случай, так что глушить исключение через try/except — плохой знак: если отсутствие нормально, нужен discard. Отдельная ловушка — удалять элементы прямо во время перебора того же множества: цикл упадёт с RuntimeError про изменение размера, перебирать надо копию.",
        "en": "A KeyError here means the element was already gone, which is normally a bug in your logic rather than an everyday case — wrapping the call in try/except is a smell, and if absence is expected you want discard. A separate trap is removing items while iterating over the same set: the loop dies with a RuntimeError about the size changing, so iterate over a copy."
      },
      "syntax": "s.remove(elem)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.remove",
      "version": "",
      "section": "Множества (set)",
      "subcat": "удаление",
      "color_group": "mapset",
      "aliases": [
        "удалить элемент из множества",
        "ошибка при удалении несуществующего элемента"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "s = {1, 2, 3}",
        "s.remove(2)",
        "print(sorted(s))   # → [1, 3]"
      ],
      "related": [
        "set.discard",
        "set.pop",
        "list.remove",
        "keyerror"
      ],
      "related_errors": [
        "KeyError"
      ]
    },
    {
      "id": "set.symmetric_difference",
      "title": "set.symmetric_difference",
      "kind": "function",
      "summary": {
        "ru": "Возвращает элементы, входящие ровно в одно из двух множеств (симметрическая разность). Оператор — ^.",
        "en": "Return elements in exactly one of the two sets (symmetric difference). Operator: ^."
      },
      "body": {
        "ru": "В отличие от difference() и intersection(), принимает ровно один аргумент — несколько наборов за раз сюда не передать. Результат не зависит от порядка операндов, поэтому это самый прямой способ спросить «чем два набора вообще различаются», не решая заранее, у кого что лишнее; оператору ^, как обычно, нужны множества с обеих сторон.",
        "en": "Unlike difference() and intersection(), this one takes exactly one argument — you cannot pass several collections at once. The result does not depend on operand order, which makes it the direct way to ask how two collections differ at all, without deciding in advance which side has the extras; the ^ operator, as usual, needs sets on both sides."
      },
      "syntax": "s.symmetric_difference(other)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.symmetric_difference",
      "version": "",
      "section": "Множества (set)",
      "subcat": "теория множеств",
      "color_group": "mapset",
      "aliases": [
        "симметрическая разность",
        "элементы только в одном из множеств",
        "различия двух множеств"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "print(sorted({1, 2, 3}.symmetric_difference({2, 3, 4})))   # → [1, 4]",
        "print(sorted({1, 2, 3} ^ {2, 3, 4}))                       # → [1, 4]",
        "print(sorted({1, 2}.symmetric_difference([2, 3])))         # → [1, 3]",
        "print({1, 2}.symmetric_difference({1, 2}))                 # → set()",
        "print(sorted({1, 2, 3}.difference({2, 3, 4})))             # → [1]",
        "print({1, 2} ^ [2, 3])                                     # → TypeError"
      ],
      "related": [
        "set.symmetric_difference_update",
        "set.difference",
        "set.union",
        "операции-над-множествами"
      ],
      "related_errors": []
    },
    {
      "id": "set.symmetric_difference_update",
      "title": "set.symmetric_difference_update",
      "kind": "function",
      "summary": {
        "ru": "Оставляет элементы, входящие ровно в одно из множеств (симметрическая разность на месте).",
        "en": "Update the set to elements in exactly one of the sets (in-place symmetric difference)."
      },
      "body": {
        "ru": "У метода есть операторная форма ^=, но она требует множество справа, а сам метод принимает любой итерируемый объект — список, кортеж, генератор. Изменение идёт на месте, возвращается None, поэтому запись s = s.symmetric_difference_update(other) вместо результата положит в переменную None; когда нужен новый объект, берите symmetric_difference.",
        "en": "The operator form is ^=, but it insists on a set on the right-hand side, whereas the method happily takes any iterable — a list, a tuple, a generator. It mutates in place and returns None, so s = s.symmetric_difference_update(other) leaves None in the variable instead of the result; use symmetric_difference when you want a fresh set."
      },
      "syntax": "s.symmetric_difference_update(other)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.symmetric_difference_update",
      "version": "",
      "section": "Множества (set)",
      "subcat": "теория множеств",
      "color_group": "mapset",
      "aliases": [
        "симметрическая разность на месте",
        "оставить несовпадающие элементы"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "s = {1, 2, 3}",
        "s.symmetric_difference_update({3, 4})",
        "print(sorted(s))   # → [1, 2, 4]"
      ],
      "related": [
        "set.symmetric_difference",
        "set.difference_update",
        "set.intersection_update"
      ],
      "related_errors": []
    },
    {
      "id": "set.union",
      "title": "set.union",
      "kind": "function",
      "summary": {
        "ru": "Возвращает новое множество со всеми элементами исходного и переданных (объединение). Оператор — |.",
        "en": "Return a new set with all elements from this set and the others (union). Operator: |."
      },
      "body": {
        "ru": "union принимает любые итерируемые аргументы — список, кортеж, строку, генератор, а оператор | работает только между множествами, поэтому {1, 2} | [3] падает с TypeError. Метод строит новое множество и исходное не трогает: чтобы добавить элементы в уже существующее, нужен update.",
        "en": "union accepts any iterables — lists, tuples, strings, generators — while the | operator only works between sets, which is why {1, 2} | [3] raises TypeError. The method builds a new set and leaves the original untouched; to grow an existing set in place, use update instead."
      },
      "syntax": "s.union(*others)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.union",
      "version": "",
      "section": "Множества (set)",
      "subcat": "теория множеств",
      "color_group": "mapset",
      "aliases": [
        "объединение множеств",
        "объединить два множества",
        "слить множества"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "print(sorted({1, 2}.union({2, 3})))                # → [1, 2, 3]",
        "print(sorted({1}.union({2}, {3})))                 # → [1, 2, 3]",
        "print(sorted({1, 2} | {2, 3}))                     # → [1, 2, 3]",
        "print(sorted({1, 2}.union([2, 3], (4,))))          # → [1, 2, 3, 4]",
        "print(sorted(set('abc').union('bcd')))             # → ['a', 'b', 'c', 'd']",
        "print({1, 2} | [3])                                # → TypeError"
      ],
      "related": [
        "set.update",
        "set.intersection",
        "set.difference",
        "операции-над-множествами"
      ],
      "related_errors": []
    },
    {
      "id": "set.update",
      "title": "set.update",
      "kind": "function",
      "summary": {
        "ru": "Добавляет элементы из одного или нескольких итерируемых объектов (на месте); объединение с присваиванием.",
        "en": "Add elements from one or more iterables in place (in-place union)."
      },
      "body": {
        "ru": "Аргументом может быть любой итерируемый объект, и здесь легко промахнуться: строка разложится на отдельные символы, а не добавится целиком — для одного элемента нужен add(). Оператор |= делает то же самое, но требует справа именно множество, тогда как update() примет список, кортеж или генератор, причём несколько сразу за один вызов.",
        "en": "The argument may be any iterable, which is exactly where students trip: a string is split into its characters instead of being added whole — use add() for a single element. The |= operator does the same job but insists on a set on the right, while update() happily swallows lists, tuples or generators, several of them in one call."
      },
      "syntax": "s.update(*iterables)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set.update",
      "version": "",
      "section": "Множества (set)",
      "subcat": "добавление",
      "color_group": "mapset",
      "aliases": [
        "добавить сразу несколько элементов в множество",
        "объединить множества на месте",
        "дополнить множество из списка"
      ],
      "keywords": [],
      "tags": [
        "set"
      ],
      "examples": [
        "s = {1, 2}",
        "s.update({3, 4}, {5})",
        "print(sorted(s))   # → [1, 2, 3, 4, 5]"
      ],
      "related": [
        "set.add",
        "set.union",
        "list.extend",
        "операции-над-множествами"
      ],
      "related_errors": []
    },
    {
      "id": "генераторы-множеств-set-comprehension",
      "title": "Генераторы множеств (set comprehension)",
      "kind": "function",
      "summary": {
        "ru": "Set comprehension создаёт множество аналогично list comprehension, но с фигурными скобками. Дубли автоматически удаляются.",
        "en": "A set comprehension builds a set the same way a list comprehension builds a list, but with curly braces. Duplicates are removed automatically."
      },
      "body": {
        "ru": "Фигурные скобки делятся со словарями: двоеточие внутри превращает выражение в dict comprehension, а пустое множество литералом не записать — {} это словарь, нужен set(). Порядок элементов в результате не определён, так что для стабильного вывода оборачивайте в sorted(); и каждый вычисленный элемент обязан быть хэшируемым — множество из списков не соберётся.",
        "en": "Curly braces are shared with dicts: a colon inside turns the expression into a dict comprehension, and there is no literal for an empty set — {} is a dict, so use set(). The result has no defined order, so wrap it in sorted() when the output must be stable, and every produced item has to be hashable — a set of lists will not build."
      },
      "syntax": "{expr for var in iterable [if cond]}",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#set-displays",
      "version": "3.5",
      "section": "Множества (set)",
      "subcat": "comprehension",
      "color_group": "mapset",
      "aliases": [
        "множество одной строкой",
        "собрать множество из цикла",
        "уникальные значения одним выражением"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "squares = {x**2 for x in range(6)}",
        "print(sorted(squares))",
        "# → [0, 1, 4, 9, 16, 25]",
        "evens = {x for x in range(10) if x % 2 == 0}",
        "print(evens)",
        "# → {0, 2, 4, 6, 8}",
        "words = ['hello', 'world', 'hello', 'python']",
        "unique_len = {len(w) for w in words}",
        "print(unique_len)",
        "# → {5, 6} (уникальные длины)",
        "unique_first = {w[0] for w in words}",
        "print(sorted(unique_first))",
        "# → ['h', 'p', 'w'] (у множества строк порядок не определён, поэтому сортируем)",
        "no_vowels = {c for c in 'hello' if c not in 'aeiou'}",
        "print(sorted(no_vowels))",
        "# → ['h', 'l']"
      ],
      "related": [
        "списочные-выражения-list-comprehension",
        "словарные-выражения-dict-comprehension",
        "генераторное-выражение",
        "создание-множества"
      ],
      "related_errors": []
    },
    {
      "id": "операции-над-множествами",
      "title": "Операции над множествами: | & - ^",
      "kind": "term",
      "summary": {
        "ru": "| — объединение, & — пересечение, - — разность, ^ — симметричная разность. |=, &=, -=, ^= изменяют на месте.",
        "en": "| — union, & — intersection, - — difference, ^ — symmetric difference. |=, &=, -= and ^= change the set in place."
      },
      "body": {
        "ru": "Операторы требуют множество (или frozenset) с обеих сторон: список или строка справа дадут TypeError, а именованные аналоги union(), intersection(), difference() примут любой итерируемый объект. Разность несимметрична — поменяв операнды местами, получите другой результат, тогда как ^ оставляет то, что лежит ровно в одном из множеств.",
        "en": "The operators demand a set (or frozenset) on both sides — a list or a string on the right raises TypeError, whereas the named forms union(), intersection() and difference() accept any iterable. Difference is not symmetric, so swapping the operands changes the answer, while ^ keeps exactly those elements that belong to one set only."
      },
      "syntax": "a | b  |  a & b  |  a - b  |  a ^ b",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset",
      "version": "",
      "section": "Множества (set)",
      "subcat": "операторы",
      "color_group": "mapset",
      "aliases": [
        "объединение множеств",
        "пересечение множеств",
        "разность множеств",
        "симметрическая разность множеств"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "a = {1, 2, 3, 4}",
        "b = {3, 4, 5, 6}",
        "print(a | b)",
        "# → {1, 2, 3, 4, 5, 6}",
        "print(a & b)",
        "# → {3, 4}",
        "print(a - b)",
        "# → {1, 2}",
        "print(b - a)",
        "# → {5, 6}",
        "print(a ^ b)",
        "# → {1, 2, 5, 6}",
        "c = {1, 2}",
        "c |= {3, 4}",
        "print(c)",
        "# → {1, 2, 3, 4}",
        "d = {1, 2, 3}",
        "d &= {2, 3, 4}",
        "print(d)",
        "# → {2, 3}",
        "print({1, 2} ^ {2, 3})",
        "# → {1, 3}"
      ],
      "related": [
        "set.union",
        "set.intersection",
        "set.difference",
        "set.symmetric_difference"
      ],
      "related_errors": []
    },
    {
      "id": "практические-задачи-с-множествами",
      "title": "Практические задачи с множествами",
      "kind": "term",
      "summary": {
        "ru": "Типичное применение: уникальные элементы, пересечения, разности, быстрая проверка принадлежности.",
        "en": "Typical uses: unique items, intersections, differences and fast membership tests."
      },
      "body": {
        "ru": "Множество выбрасывает порядок вместе с дубликатами: если нужны уникальные значения в исходном порядке, берите list(dict.fromkeys(lst)), а не set(). Главная выгода — проверка x in s за константное время против линейного перебора списка, поэтому перед серией проверок принадлежности список стоит один раз превратить в множество. И учтите: внутрь попадут только хешируемые объекты, список или словарь туда не положить.",
        "en": "Turning a list into a set drops the order along with the duplicates; when you need the unique values in their original order, use list(dict.fromkeys(lst)) instead. The real win is that x in s costs constant time while scanning a list is linear, so convert once before running many membership tests. Only hashable objects fit inside, which rules out lists and dicts as elements."
      },
      "syntax": "set(lst)  |  a & b  |  a - b  |  a ^ b",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set",
      "version": "",
      "section": "Множества (set)",
      "subcat": "практика",
      "color_group": "mapset",
      "aliases": [
        "убрать дубликаты",
        "уникальные элементы списка",
        "удалить повторы"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "lst = [1, 2, 2, 3, 3, 3, 4]",
        "unique = sorted(set(lst))",
        "print(unique)",
        "# → [1, 2, 3, 4]",
        "a = [1, 2, 3, 4]",
        "b = [3, 4, 5, 6]",
        "common = set(a) & set(b)",
        "print(common)",
        "# → {3, 4}",
        "only_a = set(a) - set(b)",
        "print(only_a)",
        "# → {1, 2}",
        "print(len(set(lst)))",
        "# → 4 (число уникальных)",
        "has_dup = len(lst) != len(set(lst))",
        "print(has_dup)",
        "# → True (есть дубликаты)",
        "tags1 = {'python', 'data', 'ml'}",
        "tags2 = {'java', 'data', 'web'}",
        "print(sorted(tags1.symmetric_difference(tags2)))",
        "# → ['java', 'ml', 'python', 'web'] (у множества строк порядок не определён, поэтому сортируем)"
      ],
      "related": [
        "создание-множества",
        "операции-над-множествами",
        "in-not-in-для-множеств-o-1",
        "генераторы-множеств-set-comprehension"
      ],
      "related_errors": []
    },
    {
      "id": "создание-множества",
      "title": "Создание множества",
      "kind": "term",
      "summary": {
        "ru": "Множество создаётся литералом {}, конструктором set() или frozenset(). Дубликаты автоматически удаляются. Порядок не гарантирован.",
        "en": "A set is written as a {} literal or built with set() or frozenset(). Duplicates are removed automatically. The order is not guaranteed."
      },
      "body": {
        "ru": "Пустое множество создаётся только вызовом set(): фигурные скобки {} дают пустой словарь. Элементы обязаны быть хешируемыми — список внутрь не положить (кортеж можно), и обычное множество не может быть элементом другого множества, для этого есть неизменяемый frozenset. Ещё ловушка: set('abc') разбирает строку на отдельные символы, а не кладёт её целиком.",
        "en": "An empty set can only be made with set(); bare {} gives an empty dict. Elements must be hashable, so a list cannot go in (a tuple can), and a plain set cannot be an element of another set — that is what the immutable frozenset is for. Watch out for set('abc') too: it splits the string into individual characters rather than storing it whole."
      },
      "syntax": "{a, b}  |  set(iterable)  |  frozenset(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset",
      "version": "",
      "section": "Множества (set)",
      "subcat": "создание",
      "color_group": "mapset",
      "aliases": [
        "как создать множество",
        "множество из списка",
        "объявить множество"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "s = {1, 2, 3}",
        "# → {1, 2, 3}",
        "s2 = set([1, 2, 2, 3, 3])",
        "print(s2)",
        "# → {1, 2, 3} (дубли удалены)",
        "s3 = set('hello')",
        "print(s3)",
        "# → {'h', 'e', 'l', 'o'}",
        "empty = set()",
        "print(type(empty))",
        "# → <class 'set'> ({} — это dict!)",
        "fs = frozenset([1, 2, 3])",
        "print(fs)",
        "# → frozenset({1, 2, 3})",
        "nums = [1, 1, 2, 2, 3]",
        "unique = list(set(nums))",
        "print(sorted(unique))",
        "# → [1, 2, 3]",
        "s4 = {x**2 for x in range(5)}",
        "print(sorted(s4))",
        "# → [0, 1, 4, 9, 16]"
      ],
      "related": [
        "set",
        "генераторы-множеств-set-comprehension",
        "frozenset-неизменяемое-множество",
        "операции-над-множествами"
      ],
      "related_errors": []
    },
    {
      "id": "abc.ABC",
      "title": "abc.ABC",
      "kind": "term",
      "summary": {
        "ru": "Вспомогательный базовый класс с метаклассом ABCMeta: наследование от него делает класс абстрактным (нельзя создать экземпляр при наличии абстрактных методов).",
        "en": "A helper base class using ABCMeta; subclassing it makes a class abstract."
      },
      "body": {
        "ru": "Абстрактность проверяется только в момент создания экземпляра: если наследник забыл переопределить хотя бы один абстрактный метод, TypeError прилетит на вызове конструктора, а не при определении класса. Само наследование от ABC ничего не запрещает — потомок без абстрактных методов создаётся спокойно. Если нужна проверка «подходит ли объект по набору методов» без наследования, смотрите в сторону typing.Protocol со структурной типизацией.",
        "en": "The abstractness check happens at instantiation time, not at class definition: a subclass that forgets to override an abstract method fails with TypeError only when you call its constructor. Inheriting from ABC by itself forbids nothing — a class with no abstract methods left over instantiates fine. When you want \"does this object have the right methods\" without an inheritance link, reach for typing.Protocol and structural typing instead."
      },
      "syntax": "class Base(abc.ABC): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/abc.html#abc.ABC",
      "version": "3.4",
      "section": "Модуль abc",
      "subcat": "абстрактные классы",
      "color_group": "module",
      "aliases": [
        "абстрактный базовый класс",
        "запретить создание экземпляра класса"
      ],
      "keywords": [],
      "tags": [
        "abc"
      ],
      "examples": [
        "import abc",
        "class Base(abc.ABC):",
        "    @abc.abstractmethod",
        "    def area(self): ...",
        "try:",
        "    Base()",
        "except TypeError:",
        "    print('нельзя создать абстрактный')   # → нельзя создать абстрактный"
      ],
      "related": [
        "abc.ABCMeta",
        "abc.abstractmethod",
        "абстрактные-классы",
        "protocol"
      ],
      "related_errors": []
    },
    {
      "id": "abc.ABCMeta",
      "title": "abc.ABCMeta",
      "kind": "term",
      "summary": {
        "ru": "Метакласс для определения абстрактных базовых классов; ABC — удобная обёртка над ним.",
        "en": "The metaclass for defining abstract base classes; ABC is a convenience wrapper."
      },
      "body": {
        "ru": "Прямо к метаклассу обращаются, когда наследоваться от ABC нельзя: у класса уже есть свой метакласс, и его делают наследником ABCMeta. Вторая причина — метод register(): чужой класс объявляется виртуальным подклассом, после чего isinstance и issubclass отвечают True, но ничего не наследуется и абстрактные методы у него никто не проверяет.",
        "en": "You touch the metaclass directly when subclassing ABC is not an option — typically the class already has its own metaclass, which you then derive from ABCMeta. The other reason is register(): it declares an unrelated class a virtual subclass, so isinstance and issubclass start saying True, yet nothing is inherited and its abstract methods are never enforced."
      },
      "syntax": "class C(metaclass=abc.ABCMeta): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/abc.html#abc.ABCMeta",
      "version": "",
      "section": "Модуль abc",
      "subcat": "абстрактные классы",
      "color_group": "module",
      "aliases": [
        "метакласс абстрактного класса",
        "абстрактный класс через метакласс"
      ],
      "keywords": [],
      "tags": [
        "abc"
      ],
      "examples": [
        "import abc",
        "print(type(abc.ABC) is abc.ABCMeta)   # → True",
        "class Shape(metaclass=abc.ABCMeta):",
        "    @abc.abstractmethod",
        "    def area(self): return 0",
        "print(type(Shape) is abc.ABCMeta)     # → True",
        "print(Shape.__abstractmethods__)      # → frozenset({'area'})",
        "class Circle(Shape): area = lambda self: 3.14",
        "print(Circle().area())                # → 3.14",
        "print(Shape())                        # → TypeError"
      ],
      "related": [
        "abc.ABC",
        "abc.abstractmethod",
        "abc.get_cache_token"
      ],
      "related_errors": []
    },
    {
      "id": "abc.abstractclassmethod",
      "title": "abc.abstractclassmethod",
      "kind": "term",
      "summary": {
        "ru": "Устарело (deprecated): абстрактный classmethod. Используйте @classmethod поверх @abstractmethod.",
        "en": "Deprecated: an abstract classmethod. Use @classmethod on top of @abstractmethod."
      },
      "body": {
        "ru": "Обёртка родом из времён, когда classmethod ещё не умел пробрасывать наружу флаг абстрактности; с Python 3.3 обычная пара @classmethod поверх @abstractmethod делает то же самое, и отдельный декоратор объявлен устаревшим. В стандартной библиотеке он оставлен только ради старого кода — в новом брать не нужно.",
        "en": "This wrapper dates back to a time when classmethod could not propagate the abstractness flag; since Python 3.3 stacking @classmethod on top of @abstractmethod does the same job, and the separate decorator has been deprecated ever since. It survives in the standard library purely for old code — do not reach for it in anything new."
      },
      "syntax": "@abc.abstractclassmethod  # deprecated",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/abc.html#abc.abstractclassmethod",
      "version": "3.2",
      "section": "Модуль abc",
      "subcat": "абстрактные классы",
      "color_group": "module",
      "aliases": [
        "абстрактный метод класса",
        "обязательный метод класса в наследнике"
      ],
      "keywords": [],
      "tags": [
        "abc"
      ],
      "examples": [
        "import abc",
        "print(callable(abc.abstractclassmethod))            # → True",
        "print(issubclass(abc.abstractclassmethod, classmethod))   # → True",
        "class Base(abc.ABC):",
        "    @abc.abstractclassmethod",
        "    def create(cls): ...",
        "print(Base.__abstractmethods__)                     # → frozenset({'create'})",
        "class Modern(abc.ABC):",
        "    @classmethod",
        "    @abc.abstractmethod",
        "    def create(cls): ...",
        "print(Modern.__abstractmethods__)                   # → frozenset({'create'})",
        "print(Base())                                       # → TypeError"
      ],
      "related": [
        "abc.abstractmethod",
        "classmethod",
        "abc.abstractstaticmethod"
      ],
      "related_errors": []
    },
    {
      "id": "abc.abstractmethod",
      "title": "abc.abstractmethod",
      "kind": "function",
      "summary": {
        "ru": "Декоратор, помечающий метод абстрактным: подкласс обязан его переопределить, иначе остаётся абстрактным.",
        "en": "A decorator marking a method abstract; subclasses must override it."
      },
      "body": {
        "ru": "Сам декоратор ничего не запрещает — он лишь ставит на функции флаг __isabstractmethod__; отказ создавать экземпляр обеспечивает метакласс ABCMeta. Поэтому в классе без ABC или ABCMeta пометка тихо ни на что не влияет, и «абстрактный» метод спокойно вызывается. В связке с другими декораторами abstractmethod должен быть самым внутренним: @classmethod или @property идут выше него.",
        "en": "The decorator enforces nothing on its own — it just sets the __isabstractmethod__ flag on the function; refusing instantiation is the job of the ABCMeta metaclass. In a class that uses neither ABC nor ABCMeta the mark is silently ignored and the \"abstract\" method stays perfectly callable. When stacking decorators, abstractmethod must be the innermost one, with @classmethod or @property sitting above it."
      },
      "syntax": "@abc.abstractmethod",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/abc.html#abc.abstractmethod",
      "version": "",
      "section": "Модуль abc",
      "subcat": "абстрактные классы",
      "color_group": "module",
      "aliases": [
        "объявить абстрактный метод",
        "обязательный метод в наследнике",
        "заставить подкласс переопределить метод"
      ],
      "keywords": [],
      "tags": [
        "abc"
      ],
      "examples": [
        "import abc",
        "class B(abc.ABC):",
        "    @abc.abstractmethod",
        "    def f(self): ...",
        "print('f' in B.__abstractmethods__)   # → True"
      ],
      "related": [
        "abc.ABC",
        "абстрактные-классы",
        "notimplementederror",
        "abc.ABCMeta"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "abc.abstractproperty",
      "title": "abc.abstractproperty",
      "kind": "term",
      "summary": {
        "ru": "Устарело (deprecated): абстрактное свойство. Используйте @property поверх @abstractmethod.",
        "en": "Deprecated: an abstract property. Use @property on top of @abstractmethod instead."
      },
      "body": {
        "ru": "История та же, что у abstractclassmethod: начиная с Python 3.3 property сама сообщает об абстрактности вложенной функции, поэтому отдельная обёртка стала лишней. В современной замене важен порядок — @property внешним, @abstractmethod внутренним, не наоборот.",
        "en": "Same story as abstractclassmethod: since Python 3.3 property itself reports the abstractness of the function it wraps, which made the dedicated decorator redundant. In the modern replacement the order matters — @property on the outside, @abstractmethod directly above the function, never the other way round."
      },
      "syntax": "@abc.abstractproperty  # deprecated",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/abc.html#abc.abstractproperty",
      "version": "",
      "section": "Модуль abc",
      "subcat": "абстрактные классы",
      "color_group": "module",
      "aliases": [
        "абстрактное свойство",
        "обязательное свойство в наследнике"
      ],
      "keywords": [],
      "tags": [
        "abc"
      ],
      "examples": [
        "import abc",
        "print(callable(abc.abstractproperty))   # → True",
        "print(issubclass(abc.abstractproperty, property))   # → True",
        "class Shape(abc.ABC): area = abc.abstractproperty(lambda self: 0)",
        "print(Shape.__abstractmethods__)   # → frozenset({'area'})",
        "print(Shape())   # → TypeError (класс абстрактный)"
      ],
      "related": [
        "abc.abstractmethod",
        "property",
        "abc.abstractclassmethod"
      ],
      "related_errors": []
    },
    {
      "id": "abc.abstractstaticmethod",
      "title": "abc.abstractstaticmethod",
      "kind": "term",
      "summary": {
        "ru": "Устарело (deprecated): абстрактный staticmethod. Используйте @staticmethod поверх @abstractmethod.",
        "en": "Deprecated: an abstract staticmethod. Use @staticmethod on top of @abstractmethod."
      },
      "body": {
        "ru": "Существует по историческим причинам: до Python 3.3 обычный staticmethod «терял» пометку абстрактности, и нужны были отдельные обёртки. Сейчас staticmethod пробрасывает __isabstractmethod__ сам, поэтому пишут связку из двух декораторов — и порядок важен: @staticmethod должен стоять сверху, @abstractmethod — ближе к функции.",
        "en": "It exists for historical reasons: before Python 3.3 a plain staticmethod dropped the abstractness flag, so separate wrappers were needed. Today staticmethod propagates __isabstractmethod__ itself, so you stack two decorators instead — and the order matters: @staticmethod goes on top, @abstractmethod closest to the function."
      },
      "syntax": "@abc.abstractstaticmethod  # deprecated",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/abc.html#abc.abstractstaticmethod",
      "version": "3.2",
      "section": "Модуль abc",
      "subcat": "абстрактные классы",
      "color_group": "module",
      "aliases": [
        "абстрактный статический метод",
        "обязательный статический метод в наследнике"
      ],
      "keywords": [],
      "tags": [
        "abc"
      ],
      "examples": [
        "import abc",
        "print(callable(abc.abstractstaticmethod))   # → True",
        "print(issubclass(abc.abstractstaticmethod, staticmethod))   # → True",
        "class Base(abc.ABC): make = abc.abstractstaticmethod(lambda: 1)",
        "print(Base.__abstractmethods__)   # → frozenset({'make'})",
        "print(Base())   # → TypeError (не реализован make)"
      ],
      "related": [
        "abc.abstractmethod",
        "staticmethod",
        "abc.abstractclassmethod"
      ],
      "related_errors": []
    },
    {
      "id": "abc.get_cache_token",
      "title": "abc.get_cache_token",
      "kind": "function",
      "summary": {
        "ru": "Возвращает текущий токен кеша ABC; меняется при регистрации новых виртуальных подклассов (для инвалидации кешей).",
        "en": "Return the current ABC cache token; changes when virtual subclasses are registered."
      },
      "body": {
        "ru": "Нужен потому, что ABC умеет принимать виртуальные подклассы уже во время работы программы через register(): результат isinstance() для одной и той же пары может измениться позже. Если вы кешируете такие проверки у себя, храните рядом токен и сбрасывайте кеш, когда он изменился; сам токен — непрозрачное число, сравнивать его осмысленно только на равенство.",
        "en": "It exists because ABCs accept virtual subclasses at runtime via register(), so the answer isinstance() gives for the same pair can change later. If you cache such checks yourself, store the token alongside and drop the cache when it differs; the token is an opaque value meaningful only for equality comparison, not for ordering or arithmetic."
      },
      "syntax": "abc.get_cache_token()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/abc.html#abc.get_cache_token",
      "version": "3.4",
      "section": "Модуль abc",
      "subcat": "abc-служебные",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "abc"
      ],
      "examples": [
        "import abc",
        "print(isinstance(abc.get_cache_token(), int))   # → True",
        "t = abc.get_cache_token()",
        "class Virtual(abc.ABC): pass",
        "print(Virtual.register(list) is list)   # → True",
        "print(abc.get_cache_token() != t)   # → True"
      ],
      "related": [
        "abc.ABCMeta",
        "abc.update_abstractmethods"
      ],
      "related_errors": []
    },
    {
      "id": "abc.update_abstractmethods",
      "title": "abc.update_abstractmethods",
      "kind": "function",
      "summary": {
        "ru": "Пересчитывает набор абстрактных методов класса (напр. после динамического добавления методов; Python 3.10+).",
        "en": "Recompute a class's set of abstract methods (3.10+)."
      },
      "body": {
        "ru": "Набор __abstractmethods__ считается один раз, в момент создания класса, поэтому всё, что дописывает или подменяет методы позже (декоратор класса, миксин, ручное присваивание), оставляет его протухшим: класс либо спокойно создаётся с недореализованными методами, либо отказывается создаваться, хотя всё уже реализовано. Функция возвращает тот же самый класс, так что её удобно вешать декоратором.",
        "en": "A class's __abstractmethods__ set is computed once, at class creation, so anything that adds or replaces methods afterwards (a class decorator, a mixin, a plain assignment) leaves it stale: the class either instantiates happily with unimplemented methods or refuses to instantiate although everything is implemented. It returns the same class object, which makes it convenient to apply as a decorator."
      },
      "syntax": "abc.update_abstractmethods(cls)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/abc.html#abc.update_abstractmethods",
      "version": "3.10",
      "section": "Модуль abc",
      "subcat": "abc-служебные",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "abc"
      ],
      "examples": [
        "import abc",
        "class C: pass",
        "print(abc.update_abstractmethods(C) is C)   # → True",
        "class Base(abc.ABC): pass",
        "Base.run = abc.abstractmethod(lambda self: None)",
        "print(abc.update_abstractmethods(Base).__abstractmethods__)   # → frozenset({'run'})"
      ],
      "related": [
        "abc.abstractmethod",
        "abc.ABCMeta",
        "abc.get_cache_token"
      ],
      "related_errors": []
    },
    {
      "id": "bisect.bisect",
      "title": "bisect.bisect()",
      "kind": "function",
      "summary": {
        "ru": "bisect.bisect() — алиас для bisect_right(). Возвращает позицию вставки после равных элементов.",
        "en": "bisect.bisect() is an alias of bisect_right(). It returns the insertion position after any equal items."
      },
      "body": {
        "ru": "Выбор между bisect и bisect_left важен только при дубликатах: bisect_left(a, x) — сколько элементов строго меньше x, bisect(a, x) — сколько меньше или равно, а их разность даёт количество вхождений x. В новом коде обычно пишут явное имя bisect_right — читателю сразу видно, какая из двух границ имеется в виду.",
        "en": "The choice between bisect and bisect_left only matters when duplicates exist: bisect_left(a, x) counts items strictly less than x, bisect(a, x) counts items less than or equal, and their difference is how many times x occurs. New code usually spells out bisect_right, so the reader can see which of the two boundaries is meant."
      },
      "syntax": "bisect.bisect(a, x)  # то же что bisect_right",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/bisect.html#bisect.bisect",
      "version": "",
      "section": "Модуль bisect",
      "subcat": "позиция",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import bisect",
        "a = [1, 2, 3, 3, 5]",
        "bisect.bisect(a, 3) # → 4",
        "bisect.bisect(a, 2.5) # → 2",
        "bisect.bisect(a, 0) # → 0",
        "bisect.bisect(a, 10) # → 5"
      ],
      "related": [
        "bisect.bisect_right",
        "bisect.bisect_left",
        "bisect.insort_right"
      ],
      "related_errors": []
    },
    {
      "id": "bisect.bisect_left",
      "title": "bisect.bisect_left()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает индекс, куда вставить x в отсортированный список, чтобы порядок сохранился — ПЕРЕД уже равными элементами. Поиск за O(log n).",
        "en": "Return the index at which to insert x into a sorted list keeping it sorted — before any equal entries. O(log n) search."
      },
      "body": {
        "ru": "Список обязан быть отсортирован — проверки нет, на неотсортированных данных функция молча вернёт бессмысленный индекс. И O(log n) относится только к поиску: сама вставка через insort остаётся O(n), потому что список сдвигает хвост, так что bisect не превращает list в быструю сортированную структуру при массовых вставках.",
        "en": "The list must already be sorted — nothing checks this, and on unsorted data you silently get a meaningless index. Also, the O(log n) covers the search only: inserting with insort is still O(n) because the tail of the list has to shift, so bisect does not turn a list into a fast sorted container when you insert a lot."
      },
      "syntax": "bisect.bisect_left(a, x, lo=0, hi=len(a))",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/bisect.html#bisect.bisect_left",
      "version": "",
      "section": "Модуль bisect",
      "subcat": "позиция",
      "color_group": "module",
      "aliases": [
        "индекс вставки перед равными элементами",
        "первое вхождение в отсортированном списке"
      ],
      "keywords": [
        "bisect.bisect_left",
        "bisect_left"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import bisect",
        "a = [1, 3, 3, 5, 7]",
        "bisect.bisect_left(a, 3) # → 1 (перед равными)",
        "bisect.bisect_left(a, 4) # → 3",
        "bisect.bisect_left(a, 8) # → 5"
      ],
      "related": [
        "bisect.bisect_right",
        "bisect.insort_left"
      ],
      "related_errors": []
    },
    {
      "id": "bisect.bisect_right",
      "title": "bisect.bisect_right()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает индекс, куда вставить x в отсортированный список, чтобы порядок сохранился — ПОСЛЕ уже равных элементов. Синоним bisect().",
        "en": "Return the index at which to insert x into a sorted list keeping it sorted — after any equal entries. Alias of bisect()."
      },
      "body": {
        "ru": "Функция ничего не проверяет: если список не отсортирован, она молча вернёт бессмысленный индекс. Отличие от bisect_left заметно только на равных элементах — для ответа «есть ли такой элемент» нужен bisect_left, а bisect_right удобен, когда значение ровно на границе должно уйти в верхний диапазон. С Python 3.10 есть параметр key, но он применяется только к элементам списка: сам x передают уже в виде ключа.",
        "en": "Nothing here validates the input — on an unsorted list you silently get a meaningless index. The difference from bisect_left shows up only among equal entries: use bisect_left to test membership, and bisect_right when a value sitting exactly on a boundary should fall into the upper range. Since Python 3.10 there is a key parameter, but it is applied to list elements only, so x must already be passed as a key."
      },
      "syntax": "bisect.bisect_right(a, x, lo=0, hi=len(a))",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/bisect.html#bisect.bisect_right",
      "version": "",
      "section": "Модуль bisect",
      "subcat": "позиция",
      "color_group": "module",
      "aliases": [
        "индекс вставки после равных элементов",
        "позиция за последним равным элементом"
      ],
      "keywords": [
        "bisect.bisect_right",
        "bisect_right"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import bisect",
        "a = [1, 3, 3, 5, 7]",
        "bisect.bisect_right(a, 3) # → 3 (после равных)",
        "bisect.bisect_right(a, 0) # → 0",
        "bisect.bisect_right(a, 3) - bisect.bisect_left(a, 3) # → 2 (сколько троек)"
      ],
      "related": [
        "bisect.bisect_left",
        "bisect.insort_right"
      ],
      "related_errors": []
    },
    {
      "id": "bisect.insort_left",
      "title": "bisect.insort_left()",
      "kind": "function",
      "summary": {
        "ru": "Вставляет x в отсортированный список ПЕРЕД равными элементами, сохраняя порядок. Поиск места O(log n), но сама вставка O(n) из-за сдвига.",
        "en": "Insert x into a sorted list before any equal entries, keeping it sorted; the search is O(log n) but the insertion itself is O(n) due to shifting."
      },
      "body": {
        "ru": "Вставка линейна, поэтому собирать список из n элементов последовательными insort_left — это O(n^2); дешевле накопить всё и один раз вызвать sort(). Отличие от insort_right проявляется только тогда, когда среди равных по сравнению элементов есть различимые объекты: left ставит новый перед ними, ломая порядок поступления, right — после.",
        "en": "Each insertion is linear, so building an n-element list by repeated insort_left costs O(n^2) — collecting everything and calling sort() once is far cheaper. The choice between left and right matters only when items that compare equal are still distinguishable objects: left puts the newcomer ahead of them, breaking arrival order, while right appends after."
      },
      "syntax": "bisect.insort_left(a, x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/bisect.html#bisect.insort_left",
      "version": "",
      "section": "Модуль bisect",
      "subcat": "вставка",
      "color_group": "module",
      "aliases": [
        "вставить в отсортированный список перед равными",
        "добавить элемент с сохранением сортировки слева"
      ],
      "keywords": [
        "bisect.insort_left",
        "insort_left"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import bisect",
        "a = [1, 3, 4, 5]",
        "bisect.insort_left(a, 3)",
        "a # → [1, 3, 3, 4, 5]"
      ],
      "related": [
        "bisect.insort_right",
        "bisect.bisect_left"
      ],
      "related_errors": []
    },
    {
      "id": "bisect.insort_right",
      "title": "bisect.insort_right()",
      "kind": "function",
      "summary": {
        "ru": "Вставляет x в отсортированный список ПОСЛЕ равных элементов, сохраняя порядок. Синоним insort(). Вставка O(n) из-за сдвига.",
        "en": "Insert x into a sorted list after any equal entries, keeping it sorted; alias of insort(). The insertion is O(n) due to shifting."
      },
      "body": {
        "ru": "insort — просто второе имя этой функции, и обычно нужна именно она: новый элемент встаёт после равных, поэтому среди одинаковых по ключу сохраняется порядок добавления. Линейный сдвиг терпим на сотнях и тысячах элементов, но не при миллионах или потоке частых вставок — там уместнее heapq, если нужен только минимум, или специализированная отсортированная структура.",
        "en": "insort is just another name for this function, and it is the one you normally want: a new item lands after its equals, so items with the same key keep their arrival order. The linear shift is fine for hundreds or thousands of elements, but not for millions or a stream of frequent inserts — reach for heapq if you only need the minimum, or a dedicated sorted container."
      },
      "syntax": "bisect.insort_right(a, x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/bisect.html#bisect.insort_right",
      "version": "",
      "section": "Модуль bisect",
      "subcat": "вставка",
      "color_group": "module",
      "aliases": [
        "вставить в отсортированный список после равных",
        "добавить элемент с сохранением сортировки справа"
      ],
      "keywords": [
        "bisect.insort_right",
        "insort_right"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import bisect",
        "a = [1, 3, 5]",
        "bisect.insort_right(a, 4)",
        "a # → [1, 3, 4, 5]",
        "bisect.insort_right(a, 0)",
        "a # → [0, 1, 3, 4, 5]"
      ],
      "related": [
        "bisect.insort_left",
        "bisect.bisect_right"
      ],
      "related_errors": []
    },
    {
      "id": "grade-buckets-range-lookups",
      "title": "Поиск по диапазонам через bisect()",
      "kind": "term",
      "summary": {
        "ru": "Идиома из документации bisect: bisect.bisect() по списку границ возвращает номер диапазона, которым индексируют список меток. Табличный поиск за O(log n) вместо цепочки if/elif — оценки, тарифы, уровни.",
        "en": "The idiom from the bisect docs: bisect.bisect() over a list of breakpoints returns the range index used to index a list of labels. An O(log n) table lookup instead of an if/elif chain — grades, price tiers, levels."
      },
      "body": {
        "ru": "Список меток должен быть ровно на один длиннее списка границ, иначе на крайних значениях вы получите IndexError или, что хуже, молча неверную метку. bisect() — это bisect_right, поэтому значение, точно равное границе, попадает в верхний диапазон; если граница должна принадлежать нижнему, берите bisect_left. И сами границы обязаны быть отсортированы по возрастанию — проверки нет.",
        "en": "The label list must be exactly one longer than the list of breakpoints; otherwise extreme values raise IndexError or, worse, quietly return the wrong label. bisect() is bisect_right, so a value equal to a breakpoint falls into the upper range — switch to bisect_left if boundaries should belong to the lower one. The breakpoints themselves must be in ascending order; nothing checks that for you."
      },
      "syntax": "i = bisect.bisect(breakpoints, x)  # номер диапазона\nlabels[i]                          # метка диапазона",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/bisect.html#examples",
      "version": "",
      "section": "Модуль bisect",
      "subcat": "применение",
      "color_group": "module",
      "aliases": [
        "Grade buckets / range lookups",
        "табличный поиск",
        "range lookup"
      ],
      "keywords": [
        "bisect.bisect",
        "bisect"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import bisect",
        "breaks, letters = [60, 70, 80, 90], 'FDCBA'",
        "print(letters[bisect.bisect(breaks, 33)])  # → 'F'",
        "print(letters[bisect.bisect(breaks, 70)])  # → 'C' (граница уходит вверх)",
        "print(letters[bisect.bisect(breaks, 89)])  # → 'B'",
        "print(letters[bisect.bisect(breaks, 100)])  # → 'A'",
        "print(letters[bisect.bisect_left(breaks, 70)])  # → 'D' (bisect_left оставляет границу внизу)"
      ],
      "related": [
        "bisect.bisect",
        "bisect.bisect_left",
        "бинарный-поиск-через-bisect"
      ],
      "related_errors": []
    },
    {
      "id": "бинарный-поиск-через-bisect",
      "title": "Бинарный поиск через bisect",
      "kind": "term",
      "summary": {
        "ru": "bisect не проверяет наличие элемента, но можно реализовать бинарный поиск, проверив найденную позицию.",
        "en": "bisect does not check whether the item is present, but a binary search can be built on it by checking the position it returns."
      },
      "body": {
        "ru": "Порядок условий важен: i < len(a) должно стоять первым, иначе для x больше всех элементов a[i] выбросит IndexError. Брать нужно именно bisect_left — bisect_right вернул бы позицию за найденным элементом, и сравнивать пришлось бы с a[i-1]. Смысл затеи — заменить O(n)-проверку x in a на O(log n), но это работает только на заранее отсортированном списке.",
        "en": "Order the conditions carefully: i < len(a) must come first, or a[i] raises IndexError when x is larger than everything in the list. Use bisect_left specifically — bisect_right lands past the matching entry, forcing you to compare against a[i-1] instead. The whole point is trading the O(n) scan of x in a for an O(log n) probe, which only pays off on a list that is already sorted."
      },
      "syntax": "i = bisect_left(a, x)\nif i < len(a) and a[i] == x: found",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/bisect.html#searching-sorted-lists",
      "version": "",
      "section": "Модуль bisect",
      "subcat": "применение",
      "color_group": "module",
      "aliases": [
        "проверить наличие элемента в отсортированном списке",
        "двоичный поиск средствами стандартной библиотеки"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import bisect",
        "def search(a, x):",
        "    i = bisect.bisect_left(a, x)",
        "    return i if i < len(a) and a[i] == x else -1",
        "a = [1, 3, 5, 7, 9]",
        "search(a, 5) # → 2",
        "search(a, 4) # → -1",
        "search(a, 9) # → 4"
      ],
      "related": [
        "bisect.bisect_left",
        "бинарный-поиск",
        "list.index",
        "grade-buckets-range-lookups"
      ],
      "related_errors": []
    },
    {
      "id": "collections.UserDict",
      "title": "collections.UserDict",
      "kind": "term",
      "summary": {
        "ru": "Обёртка над dict с содержимым в атрибуте .data; основа для собственных словарных классов через наследование (в отличие от прямого наследования dict, все методы идут через .data).",
        "en": "A dict wrapper exposing its contents via .data; a base for custom mapping classes."
      },
      "body": {
        "ru": "Смысл обёртки — в наследовании: если унаследоваться от dict напрямую, переопределённый __setitem__ тихо обойдут update(), setdefault() и сам конструктор, потому что они реализованы на C и пишут в хранилище мимо ваших методов; UserDict проводит через ваш код каждую запись. Плата — скорость и то, что isinstance(obj, dict) даёт False: код, проверяющий тип, ваш объект не примет, туда отдавайте .data.",
        "en": "The wrapper exists for subclassing: derive from dict directly and your overridden __setitem__ gets quietly bypassed by update(), setdefault() and the constructor, which are C-level and write straight into the storage; UserDict funnels every write through your method instead. The cost is speed plus the fact that isinstance(obj, dict) is False, so code that type-checks will reject the object — pass .data there."
      },
      "syntax": "class MyDict(collections.UserDict): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.UserDict",
      "version": "",
      "section": "Модуль collections",
      "subcat": "обёртки типов",
      "color_group": "module",
      "aliases": [
        "свой класс на основе словаря",
        "наследование от словаря"
      ],
      "keywords": [],
      "tags": [
        "collections"
      ],
      "examples": [
        "from collections import UserDict",
        "d = UserDict({'a': 1})",
        "d['b'] = 2",
        "print(d['a'], d['b'], isinstance(d.data, dict))   # → 1 2 True"
      ],
      "related": [
        "collections.UserList",
        "collections.abc.MutableMapping",
        "collections.UserString"
      ],
      "related_errors": []
    },
    {
      "id": "collections.UserList",
      "title": "collections.UserList",
      "kind": "term",
      "summary": {
        "ru": "Обёртка над list с содержимым в атрибуте .data; удобна для создания собственных списочных классов наследованием.",
        "en": "A list wrapper exposing its contents via .data; a base for custom list classes."
      },
      "body": {
        "ru": "Отличие от наследования от list: у UserList срез, сложение и копия возвращают экземпляр вашего класса, а не голый список, и всё содержимое лежит в обычном списке .data, который удобно валидировать или подменить. Обратная сторона — объект не является list (isinstance(x, list) → False) и заметно медленнее из-за делегирования; в чужой код, ждущий настоящий список, передавайте .data или list(x).",
        "en": "Compared with subclassing list: slicing, concatenation and copying on a UserList give back an instance of your class rather than a plain list, and the payload sits in an ordinary list at .data that you can validate or swap out. The trade-off is that the object is not a list (isinstance(x, list) is False) and delegation makes it noticeably slower — hand .data or list(x) to code that expects the real thing."
      },
      "syntax": "class MyList(collections.UserList): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.UserList",
      "version": "",
      "section": "Модуль collections",
      "subcat": "обёртки типов",
      "color_group": "module",
      "aliases": [
        "свой класс на основе списка",
        "наследование от списка"
      ],
      "keywords": [],
      "tags": [
        "collections"
      ],
      "examples": [
        "from collections import UserList",
        "l = UserList([1, 2])",
        "l.append(3)",
        "print(list(l))   # → [1, 2, 3]"
      ],
      "related": [
        "collections.UserDict",
        "collections.abc.MutableSequence",
        "collections.UserString"
      ],
      "related_errors": []
    },
    {
      "id": "collections.UserString",
      "title": "collections.UserString",
      "kind": "term",
      "summary": {
        "ru": "Обёртка над str с содержимым в атрибуте .data; удобна для создания собственных строковых классов наследованием.",
        "en": "A str wrapper exposing its contents via .data; a base for custom string classes."
      },
      "body": {
        "ru": "Отличие от наследования от str: у подкласса str методы upper(), strip() и срезы возвращают обычную строку, и ваш класс теряется после первой же операции, а UserString каждый раз пересобирает результат как self.__class__ из .data. Цена та же, что у остальных User*-обёрток: isinstance(s, str) даёт False и всё работает через лишний слой, поэтому наружу удобнее отдавать str(s).",
        "en": "The difference from subclassing str: on a str subclass methods like upper(), strip() and slicing hand back a plain string, so your class evaporates after the first operation, whereas UserString rebuilds each result as self.__class__ from .data. The usual User* price applies — isinstance(s, str) is False and every call goes through an extra layer — so convert with str(s) before handing the value to outside code."
      },
      "syntax": "class MyStr(collections.UserString): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.UserString",
      "version": "",
      "section": "Модуль collections",
      "subcat": "обёртки типов",
      "color_group": "module",
      "aliases": [
        "свой класс на основе строки",
        "наследование от строки"
      ],
      "keywords": [],
      "tags": [
        "collections"
      ],
      "examples": [
        "from collections import UserString",
        "s = UserString('ab')",
        "print(s.upper(), len(s))   # → AB 2",
        "print(s.data, type(s.data).__name__)   # → ab str",
        "print(s == 'ab', isinstance(s, str))   # → True False",
        "print(s + 'c', type(s + 'c').__name__)   # → abc UserString"
      ],
      "related": [
        "collections.UserDict",
        "collections.UserList",
        "str"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.AsyncGenerator",
      "title": "collections.abc.AsyncGenerator",
      "kind": "term",
      "summary": {
        "ru": "Протокол асинхронного генератора: async-итератор с asend/athrow/aclose.",
        "en": "The async generator protocol: an async iterator with asend/athrow/aclose."
      },
      "body": {
        "ru": "Проверка здесь структурная: наследовать ABC не нужно, достаточно наличия __aiter__, __anext__, asend, athrow и aclose, так что True скажет и любой самописный класс с этим набором. Главная практическая грабля не в проверке, а в самом объекте: вызов ag() ничего не выполняет, тело стартует только при async for внутри запущенного event loop, а если выйти из цикла через break, блоки finally отработают не сразу — закрывайте явно через aclose() или contextlib.aclosing.",
        "en": "The check is structural: nothing has to inherit from the ABC, having __aiter__, __anext__, asend, athrow and aclose is enough, so a hand-rolled class with those methods passes too. The real gotcha is the object itself — calling ag() runs no code, the body only starts under async for inside a running event loop, and breaking out of that loop leaves finally blocks pending, so close it explicitly with aclose() or contextlib.aclosing."
      },
      "syntax": "isinstance(obj, collections.abc.AsyncGenerator)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.AsyncGenerator",
      "version": "3.6",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "протокол асинхронного генератора",
        "проверка объекта на асинхронный генератор"
      ],
      "keywords": [
        "async"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "async def ag():",
        "    yield 1",
        "print(isinstance(ag(), abc.AsyncGenerator))   # → True"
      ],
      "related": [
        "collections.abc.AsyncIterator",
        "collections.abc.Generator",
        "async-for-async-with"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.AsyncIterable",
      "title": "collections.abc.AsyncIterable",
      "kind": "term",
      "summary": {
        "ru": "Протокол «можно перебирать в async for» (есть __aiter__).",
        "en": "The protocol for objects usable in async for (has __aiter__)."
      },
      "body": {
        "ru": "Проверка смотрит только на наличие __aiter__ и ничего не гарантирует дальше: перебор всё равно может упасть, если то, что вернёт __aiter__, не умеет __anext__ — за это отвечает более узкий AsyncIterator. И помните, что асинхронно перебираемое не является обычным Iterable: for, list() и itertools с ним не работают, нужен async for или списочное включение вида [x async for x in it].",
        "en": "The check only looks for __aiter__ and promises nothing beyond that: iteration can still blow up if whatever __aiter__ returns lacks __anext__ — that stricter guarantee belongs to AsyncIterator. Also keep in mind an async iterable is not a regular Iterable: for, list() and itertools will not touch it, you need async for or a comprehension like [x async for x in it]."
      },
      "syntax": "isinstance(obj, collections.abc.AsyncIterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.AsyncIterable",
      "version": "3.5",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "протокол асинхронной итерации",
        "можно перебирать асинхронно"
      ],
      "keywords": [
        "async"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "async def ag():",
        "    yield 1",
        "print(isinstance(ag(), abc.AsyncIterable))   # → True"
      ],
      "related": [
        "collections.abc.AsyncIterator",
        "collections.abc.Iterable",
        "async-for-async-with"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.AsyncIterator",
      "title": "collections.abc.AsyncIterator",
      "kind": "term",
      "summary": {
        "ru": "Протокол асинхронного итератора: есть __anext__ и __aiter__.",
        "en": "The async iterator protocol: has __anext__ and __aiter__."
      },
      "body": {
        "ru": "isinstance здесь работает структурно: любой объект с __anext__ и __aiter__ пройдёт проверку без наследования и регистрации, но проверяется лишь наличие методов, а не то, что они действительно делают. Асинхронный генератор одноразовый — после того как async for дошёл до конца, повторный проход не выдаст ничего. Руками этот протокол пишут редко: async def с yield внутри даёт его бесплатно.",
        "en": "The isinstance check is structural: anything defining __anext__ and __aiter__ passes, no subclassing or registration needed, but only the presence of the methods is verified, never their behaviour. An async generator is single-pass, so once an async for has drained it, a second loop yields nothing. You rarely implement the protocol by hand — an async def with yield in it gives you one for free."
      },
      "syntax": "isinstance(obj, collections.abc.AsyncIterator)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.AsyncIterator",
      "version": "3.5",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "протокол асинхронного итератора",
        "асинхронный итератор"
      ],
      "keywords": [
        "async"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "async def ag():",
        "    yield 1",
        "print(isinstance(ag(), abc.AsyncIterator))   # → True"
      ],
      "related": [
        "collections.abc.AsyncIterable",
        "collections.abc.Iterator",
        "anext",
        "collections.abc.AsyncGenerator"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Awaitable",
      "title": "collections.abc.Awaitable",
      "kind": "term",
      "summary": {
        "ru": "Протокол «можно await» (есть __await__).",
        "en": "The protocol for objects usable with await (has __await__)."
      },
      "body": {
        "ru": "Вызов async-функции ничего не запускает — он лишь создаёт корутину, и если её не дождаться, интерпретатор предупредит про coroutine was never awaited (потому в примере и стоит close()). Ждать корутину можно ровно один раз: повторный await по тому же объекту даст RuntimeError. Awaitable шире корутины — под него подходят также Task и Future, поэтому в аннотациях он уместнее, когда вам неважно, что именно ждут.",
        "en": "Calling an async function starts nothing; it just builds a coroutine object, and dropping it unawaited triggers the coroutine was never awaited warning, which is why the example closes it. A coroutine can be awaited only once — a second await on the same object raises RuntimeError. Awaitable is the wider notion: Tasks and Futures qualify too, so it is the better annotation when you accept anything that can be awaited."
      },
      "syntax": "isinstance(obj, collections.abc.Awaitable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Awaitable",
      "version": "3.5",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "ожидаемый объект",
        "протокол ожидаемого объекта"
      ],
      "keywords": [
        "async"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "async def f(): pass",
        "c = f()",
        "print(isinstance(c, abc.Awaitable))   # → True",
        "c.close()"
      ],
      "related": [
        "collections.abc.Coroutine",
        "async-def-await"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Buffer",
      "title": "collections.abc.Buffer",
      "kind": "term",
      "summary": {
        "ru": "Протокол буфера: объект отдаёт доступ к своей памяти (bytes, bytearray, memoryview; Python 3.12+).",
        "en": "The buffer protocol: an object exposing its memory (3.12+)."
      },
      "body": {
        "ru": "Буферный протокол исторически существовал только на уровне C, и до 3.12 проверить его из Python было нечем; PEP 688 добавил метод __buffer__, так что теперь и обычный питоновский класс может считаться Buffer. Неожиданность: str буфером не является, хотя байты из него получить можно — нужен явный encode(). В аннотациях Buffer — корректная замена устаревшему ByteString, когда функция берёт любые сырые байты, включая memoryview и array.",
        "en": "The buffer protocol used to live only at the C level, so before 3.12 there was no way to test for it from Python; PEP 688 added the __buffer__ method, and a plain Python class can now qualify as a Buffer. A surprise for many: str is not a buffer, even though bytes can be derived from it via an explicit encode(). As an annotation, Buffer is the proper successor to the deprecated ByteString whenever a function accepts any raw-bytes object, memoryview and array included."
      },
      "syntax": "isinstance(obj, collections.abc.Buffer)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Buffer",
      "version": "3.12",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "протокол буфера",
        "доступ к памяти объекта"
      ],
      "keywords": [
        "буфер"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance(b'x', abc.Buffer))   # → True",
        "print(isinstance(bytearray(b'x'), abc.Buffer))   # → True",
        "print(isinstance(memoryview(b'abc'), abc.Buffer))   # → True",
        "print(issubclass(bytes, abc.Buffer))   # → True",
        "print(isinstance('hello', abc.Buffer))   # → False"
      ],
      "related": [
        "collections.abc.ByteString",
        "memoryview",
        "bytes"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.ByteString",
      "title": "collections.abc.ByteString",
      "kind": "term",
      "summary": {
        "ru": "Устарело (deprecated, удаление в 3.17): общий тип bytes/bytearray. Используйте Buffer или конкретные типы.",
        "en": "Deprecated (removal in 3.17): a common type for bytes/bytearray. Use Buffer instead."
      },
      "body": {
        "ru": "Ловушка прямо в имени: ByteString покрывает только bytes и bytearray, а memoryview или array под него не подпадают — как аннотация «любые байты» он всегда обманывал. С 3.12 он помечен deprecated и исчезнет в 3.17; в новом коде пишите bytes | bytearray, если нужны именно эти два типа, либо Buffer, если годится любой объект с буфером.",
        "en": "The name oversells it: ByteString covers only bytes and bytearray, while memoryview and array do not match, so as an \"any bytes\" annotation it was always misleading. It has been deprecated since 3.12 and disappears in 3.17; write bytes | bytearray when you really mean those two types, or Buffer when any buffer-exposing object will do."
      },
      "syntax": "isinstance(obj, collections.abc.ByteString)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.ByteString",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [],
      "keywords": [
        "буфер"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance(b'x', abc.ByteString))   # → True",
        "print(isinstance(bytearray(b'x'), abc.ByteString))   # → True",
        "print(isinstance(memoryview(b'x'), abc.ByteString))   # → False",
        "print(isinstance('x', abc.ByteString))   # → False",
        "print(isinstance(b'x', (bytes, bytearray, memoryview)))   # → True"
      ],
      "related": [
        "collections.abc.Buffer",
        "bytes",
        "bytearray"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Collection",
      "title": "collections.abc.Collection",
      "kind": "term",
      "summary": {
        "ru": "Протокол-объединение Sized + Iterable + Container (типичный контейнер).",
        "en": "The combination of Sized + Iterable + Container (a typical container)."
      },
      "body": {
        "ru": "Проверка структурная: класс с __len__, __iter__ и __contains__ считается Collection без наследования и регистрации. Отсюда две ловушки — str тоже Collection, так что условие «коллекция, но не строка» приходится писать явно, а генератор и любой итератор ей не являются, потому что у них нет len(). Как аннотация Collection хорош, когда нужны длина и обход, но не нужен доступ по индексу; если нужен порядок и индексы — берите Sequence.",
        "en": "The check is structural: any class defining __len__, __iter__ and __contains__ counts as a Collection, with no inheritance or registration involved. Two traps follow — str is a Collection too, so \"a collection but not a string\" needs an explicit carve-out, and generators or other iterators are not, since they have no len(). Use it as an annotation when you need size and iteration but not indexing; if order and indexing matter, reach for Sequence instead."
      },
      "syntax": "isinstance(obj, collections.abc.Collection)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Collection",
      "version": "3.6",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "протокол коллекции",
        "признаки типичного контейнера"
      ],
      "keywords": [
        "контейнеры"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance([1], abc.Collection))   # → True",
        "print(isinstance({'a': 1}, abc.Collection))   # → True",
        "print(isinstance('abc', abc.Collection))   # → True",
        "print(isinstance((x for x in [1]), abc.Collection))   # → False",
        "print(issubclass(abc.Collection, abc.Sized))   # → True"
      ],
      "related": [
        "collections.abc.Container",
        "collections.abc.Sized",
        "collections.abc.Iterable",
        "collections.abc.Sequence"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Container",
      "title": "collections.abc.Container",
      "kind": "term",
      "summary": {
        "ru": "Протокол «поддерживает проверку in» (есть __contains__).",
        "en": "The protocol for membership testing with `in` (has __contains__)."
      },
      "body": {
        "ru": "Проверка isinstance смотрит ровно на одно — есть ли __contains__, — а оператор in работает шире: когда __contains__ нет, Python перебирает объект через __iter__ или через старый __getitem__ с целыми индексами. Поэтому объект вполне может поддерживать in и при этом не быть Container. В обратную сторону тоже осторожно: Container ничего не обещает ни про len(), ни про перебор — за это отвечают Sized и Iterable.",
        "en": "This check asks a single question: does the object define __contains__? The in operator is more forgiving — without __contains__ it falls back to iterating via __iter__, or even via legacy integer __getitem__ — so plenty of objects work with in yet fail the Container check. Note also that Container promises nothing about len() or iteration; those belong to Sized and Iterable."
      },
      "syntax": "isinstance(obj, collections.abc.Container)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Container",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "протокол контейнера",
        "поддержка проверки вхождения"
      ],
      "keywords": [
        "контейнеры"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance([1], abc.Container))   # → True",
        "print(isinstance('abc', abc.Container))   # → True",
        "print(isinstance({'a': 1}, abc.Container))   # → True",
        "print(isinstance(iter([1]), abc.Container))   # → False",
        "print(1 in iter([1, 2]))   # → True"
      ],
      "related": [
        "collections.abc.Collection",
        "__len__-__getitem__-__setitem__-__contai",
        "operator.contains"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Coroutine",
      "title": "collections.abc.Coroutine",
      "kind": "term",
      "summary": {
        "ru": "Протокол корутины: awaitable с методами send/throw/close.",
        "en": "The coroutine protocol: an awaitable with send/throw/close."
      },
      "body": {
        "ru": "Вызов async-функции ничего не выполняет — он лишь создаёт объект-корутину, и если его не дождаться через await или не передать в asyncio, тело так и не отработает, а при сборке мусора вы получите RuntimeWarning про «coroutine was never awaited». Coroutine — более узкий тип, чем Awaitable: Task и Future ждать можно, но эту проверку они не проходят, так что для «можно ли сделать await» проверяйте Awaitable.",
        "en": "Calling an async function runs no code at all: it just builds a coroutine object, and if nobody awaits it or hands it to asyncio, the body never executes and the interpreter emits a \"coroutine was never awaited\" RuntimeWarning when the object is collected. Coroutine is narrower than Awaitable — Tasks and Futures can be awaited but fail this check, so test against Awaitable when the question is merely \"can I await this?\"."
      },
      "syntax": "isinstance(obj, collections.abc.Coroutine)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Coroutine",
      "version": "3.5",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "протокол корутины",
        "проверить, что объект корутина"
      ],
      "keywords": [
        "async"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "async def f(): pass",
        "c = f()",
        "print(isinstance(c, abc.Coroutine))   # → True",
        "c.close()"
      ],
      "related": [
        "collections.abc.Awaitable",
        "async-def-await",
        "collections.abc.Generator"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Generator",
      "title": "collections.abc.Generator",
      "kind": "term",
      "summary": {
        "ru": "Протокол генератора: итератор с методами send/throw/close.",
        "en": "The generator protocol: an iterator with send/throw/close."
      },
      "body": {
        "ru": "Проверку проходит только настоящий генератор — функция с yield или генераторное выражение; ваш собственный класс с __iter__ и __next__ опознается как Iterator, но не как Generator, потому что здесь дополнительно требуются send, throw и close. Если вам нужно просто «это ленивый одноразовый перебор», спрашивайте Iterator. И не забывайте про главную ловушку самих генераторов: после одного полного прохода объект исчерпан, второй цикл по нему молча не выдаст ничего.",
        "en": "Only a real generator passes — a function with yield or a generator expression; your own class with __iter__ and __next__ registers as an Iterator but not as a Generator, since send, throw and close are required too. When you only mean \"lazy one-shot iteration\", check Iterator instead. And remember the trap generators themselves carry: once fully consumed, the object is spent, and a second loop over it silently yields nothing."
      },
      "syntax": "isinstance(obj, collections.abc.Generator)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Generator",
      "version": "3.5",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "протокол генератора",
        "проверить, что объект генератор"
      ],
      "keywords": [
        "итерация"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "def g():",
        "    yield 1",
        "print(isinstance(g(), abc.Generator))   # → True"
      ],
      "related": [
        "collections.abc.Iterator",
        "generator-function-yield",
        "collections.abc.AsyncGenerator"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Hashable",
      "title": "collections.abc.Hashable",
      "kind": "term",
      "summary": {
        "ru": "Протокол «хешируемый» (есть __hash__): можно класть в set / ключом dict.",
        "en": "The protocol for hashable objects (has __hash__)."
      },
      "body": {
        "ru": "Проверка означает лишь, что у типа __hash__ не выставлен в None, а вовсе не то, что хеш реально посчитается. Классический пример — кортеж со списком внутри: проверку он пройдёт, а hash() на нём упадёт с TypeError, потому что хеш кортежа считается по его элементам. Если нужна настоящая гарантия, надёжнее один раз позвать hash() в try/except, чем полагаться на isinstance.",
        "en": "Passing this check only means the type's __hash__ is not None — it does not promise that hashing will succeed. The classic counterexample is a tuple holding a list: it registers as Hashable, yet hash() raises TypeError because a tuple hashes its elements. When you need a real guarantee, calling hash() inside try/except is more honest than the isinstance test."
      },
      "syntax": "isinstance(obj, collections.abc.Hashable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Hashable",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "хешируемый объект",
        "можно ли использовать как ключ словаря",
        "почему список нельзя положить в множество"
      ],
      "keywords": [
        "контейнеры"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance(1, abc.Hashable))    # → True",
        "print(isinstance([], abc.Hashable))   # → False",
        "print(isinstance('abc', abc.Hashable))   # → True",
        "print(isinstance({1: 2}, abc.Hashable))   # → False",
        "print(isinstance(([1], 2), abc.Hashable))   # → True",
        "print(hash(([1], 2)))   # → TypeError"
      ],
      "related": [
        "hash",
        "кортеж-как-ключ-словаря",
        "frozenset-неизменяемое-множество"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.ItemsView",
      "title": "collections.abc.ItemsView",
      "kind": "term",
      "summary": {
        "ru": "Тип представления пар словаря (d.items()).",
        "en": "The type of a dict items view (d.items())."
      },
      "body": {
        "ru": "Представление не копирует словарь, а смотрит в него: изменения в d видны сразу, зато добавление или удаление ключей прямо во время цикла по items() обрывает итерацию с RuntimeError. Ещё одна неочевидная деталь — items() ведёт себя как множество и поддерживает операции & | - ^, но только пока все значения хешируемы; у values() таких возможностей нет никогда.",
        "en": "An items view is a live window on the dict rather than a copy: updates to the dict show up immediately, but inserting or deleting keys while looping over items() aborts the iteration with RuntimeError. Less obvious is that items() is set-like and supports &, |, - and ^ — though only while every value is hashable; values() never offers this."
      },
      "syntax": "isinstance(obj, collections.abc.ItemsView)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.ItemsView",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "представление пар ключ-значение",
        "перебор пар словаря"
      ],
      "keywords": [
        "представления"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance({}.items(), abc.ItemsView))   # → True",
        "print(isinstance({}.keys(), abc.ItemsView))  # → False",
        "print(list({'a': 1, 'b': 2}.items()))  # → [('a', 1), ('b', 2)]",
        "print(('a', 1) in {'a': 1, 'b': 2}.items())  # → True",
        "print(isinstance({}.items(), abc.Set))  # → True"
      ],
      "related": [
        "dict.items",
        "collections.abc.KeysView",
        "collections.abc.ValuesView",
        "collections.abc.MappingView"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Iterable",
      "title": "collections.abc.Iterable",
      "kind": "term",
      "summary": {
        "ru": "Протокол «можно перебирать в for» (есть __iter__).",
        "en": "The protocol for objects usable in a for loop (has __iter__)."
      },
      "body": {
        "ru": "Проверка isinstance ищет только метод __iter__, поэтому старый класс с одним __getitem__ вернёт False, хотя в for он прекрасно перебирается — честнее просто вызвать iter(obj) внутри try/except TypeError. И помните, что строка тоже Iterable: рекурсивный обход вложенных списков с условием «если Iterable — заходим внутрь» зациклится на отдельных символах.",
        "en": "The isinstance check looks for __iter__ and nothing else, so a legacy class defining only __getitem__ reports False even though a for loop happily walks it; calling iter(obj) inside try/except TypeError is the more honest test. Also, str is Iterable, so a recursive flattener that descends into anything Iterable will spin forever on single characters."
      },
      "syntax": "isinstance(obj, collections.abc.Iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "проверка на итерируемость",
        "можно ли перебрать объект в цикле"
      ],
      "keywords": [
        "итерация"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance([1, 2], abc.Iterable))   # → True",
        "print(isinstance('hi', abc.Iterable))  # → True",
        "print(isinstance(42, abc.Iterable))  # → False",
        "print(issubclass(range, abc.Iterable))  # → True",
        "print([x for x in [1, [2, 3], 'ab'] if isinstance(x, abc.Iterable)])  # → [[2, 3], 'ab']"
      ],
      "related": [
        "collections.abc.Iterator",
        "collections.abc.Collection",
        "iter-next"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Iterator",
      "title": "collections.abc.Iterator",
      "kind": "term",
      "summary": {
        "ru": "Протокол итератора: есть __next__ и __iter__ (возвращает себя).",
        "en": "The iterator protocol: has __next__ and __iter__."
      },
      "body": {
        "ru": "Каждый Iterator — это Iterable, но не наоборот: список перебирать можно, а сам итератором он не является, им становится результат iter(списка). Практический смысл такой проверки — узнать, что объект одноразовый: после полного прохода итератор исчерпан, второй for по нему не даст ничего, поэтому функции, которой нужен повторный проход по входу, стоит сразу материализовать его в list.",
        "en": "Every Iterator is also Iterable, but not the other way round: a list can be looped over yet is not an iterator; iter(list) is. The practical value of the check is spotting single-use input — once drained, an iterator yields nothing on a second loop, so a function that needs two passes should materialise its argument into a list first."
      },
      "syntax": "isinstance(obj, collections.abc.Iterator)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterator",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "проверка на итератор",
        "чем итератор отличается от итерируемого объекта"
      ],
      "keywords": [
        "итерация"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance(iter([1]), abc.Iterator))   # → True",
        "print(isinstance([1, 2], abc.Iterator))  # → False",
        "print(isinstance([1, 2], abc.Iterable))  # → True",
        "print(isinstance(map(str, [1, 2]), abc.Iterator))  # → True",
        "it = iter([1])",
        "print(next(it, 'stop'), next(it, 'stop'))  # → 1 stop"
      ],
      "related": [
        "collections.abc.Iterable",
        "collections.abc.Generator",
        "итератор-__iter__-__next__",
        "iter-next"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.KeysView",
      "title": "collections.abc.KeysView",
      "kind": "term",
      "summary": {
        "ru": "Тип представления ключей словаря (d.keys()).",
        "en": "The type of a dict keys view (d.keys())."
      },
      "body": {
        "ru": "Это живое окно в словарь, а не копия: добавленный позже ключ представление сразу увидит, а изменение словаря прямо во время перебора keys() приведёт к RuntimeError. В отличие от values(), представление ключей ведёт себя как множество и поддерживает &, |, -, ^ — общие ключи двух словарей находятся как d1.keys() & d2.keys(), без промежуточных set().",
        "en": "A keys view is a live window on the dict, not a snapshot: keys added later show up immediately, and mutating the dict while iterating the view raises RuntimeError. Unlike the values view, it is set-like and supports &, |, -, ^, so the shared keys of two dicts come straight from d1.keys() & d2.keys() with no intermediate set()."
      },
      "syntax": "isinstance(obj, collections.abc.KeysView)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.KeysView",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "представление ключей словаря",
        "перебор ключей словаря"
      ],
      "keywords": [
        "представления"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance({}.keys(), abc.KeysView))   # → True",
        "print(isinstance({}.values(), abc.KeysView))  # → False",
        "print(list({'a': 1, 'b': 2}.keys()))  # → ['a', 'b']",
        "print({'a': 1, 'b': 2}.keys() & {'b', 'c'})  # → {'b'}",
        "print(isinstance({}.keys(), abc.Set))  # → True"
      ],
      "related": [
        "collections.abc.ValuesView",
        "collections.abc.ItemsView",
        "collections.abc.MappingView",
        "dict.keys"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Mapping",
      "title": "collections.abc.Mapping",
      "kind": "term",
      "summary": {
        "ru": "Протокол неизменяемого отображения ключ→значение (доступ по ключу, len, in).",
        "en": "The immutable mapping protocol (key access, len, in)."
      },
      "body": {
        "ru": "«Неизменяемое» здесь означает, что протокол не требует записи, а вовсе не то, что объект нельзя менять: обычный dict тоже проходит эту проверку, так что она не защитит вас от изменяемого словаря на входе. Если функции нужна запись, проверяйте MutableMapping. А при наследовании от Mapping достаточно написать __getitem__, __len__ и __iter__ — get, keys, items, values, in и сравнение достанутся бесплатно.",
        "en": "Immutable here describes the protocol, not the object: a plain dict passes this check too, so it does not guarantee that nobody can mutate what you were handed — require MutableMapping when your code needs to write. The upside is inheritance: subclass Mapping and implement just __getitem__, __len__ and __iter__, and you get get, keys, items, values, membership tests and equality for free."
      },
      "syntax": "isinstance(obj, collections.abc.Mapping)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "проверка на словарь",
        "словареподобный объект"
      ],
      "keywords": [
        "отображения"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "import types",
        "print(isinstance({}, abc.Mapping))   # → True",
        "print(isinstance([1, 2], abc.Mapping))  # → False",
        "print(isinstance({}, abc.MutableMapping))  # → True",
        "print(isinstance(types.MappingProxyType({'a': 1}), abc.Mapping))  # → True",
        "print(isinstance(types.MappingProxyType({'a': 1}), abc.MutableMapping))  # → False"
      ],
      "related": [
        "collections.abc.MutableMapping",
        "collections.abc.Collection",
        "collections.abc.Sequence"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.MappingView",
      "title": "collections.abc.MappingView",
      "kind": "term",
      "summary": {
        "ru": "Базовый класс представлений словаря (KeysView/ValuesView/ItemsView).",
        "en": "The base class of dict views (KeysView/ValuesView/ItemsView)."
      },
      "body": {
        "ru": "Сам по себе MappingView почти пуст — он даёт только длину и внятный repr; операции множеств и проверку вхождения добавляют уже наследники KeysView, ValuesView и ItemsView, поэтому напрямую его почти не используют. Смысл такой проверки один: понять, что перед вами живой вид на чужой словарь, а не снимок — если нужен снимок, оберните результат в list() или set().",
        "en": "MappingView itself is nearly empty: it contributes only a length and a readable repr, while set operations and membership tests come from its subclasses KeysView, ValuesView and ItemsView, so you rarely reference it directly. The one thing the check tells you is that you hold a live window onto someone else's dict rather than a copy — wrap it in list() or set() when you need a frozen snapshot."
      },
      "syntax": "isinstance(obj, collections.abc.MappingView)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.MappingView",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "базовый класс представлений словаря",
        "проверить, что объект — представление словаря"
      ],
      "keywords": [
        "представления"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance({}.keys(), abc.MappingView))   # → True",
        "print(isinstance({}.values(), abc.MappingView))   # → True",
        "print(isinstance({}.items(), abc.MappingView))   # → True",
        "d = {'a': 1, 'b': 2}; print(len(d.keys()))   # → 2",
        "print(isinstance([], abc.MappingView))   # → False",
        "print(issubclass(abc.ItemsView, abc.MappingView))   # → True"
      ],
      "related": [
        "collections.abc.KeysView",
        "collections.abc.ValuesView",
        "collections.abc.ItemsView"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.MutableMapping",
      "title": "collections.abc.MutableMapping",
      "kind": "term",
      "summary": {
        "ru": "Изменяемое отображение (dict): плюс __setitem__/__delitem__/update.",
        "en": "The mutable mapping protocol (dict): adds __setitem__/__delitem__/update."
      },
      "body": {
        "ru": "Проверяйте isinstance(obj, MutableMapping), когда важно «умеет читать и менять по ключу», а не «это буквально dict»: так проходят и os.environ, и ваши собственные классы-отображения. При наследовании достаточно написать пять методов — __getitem__, __setitem__, __delitem__, __iter__ и __len__ — остальное (get, pop, setdefault, update, items, keys) приедет миксинами. Импортировать только из collections.abc: старые псевдонимы вида collections.MutableMapping удалены в Python 3.10.",
        "en": "Use it as a type check when you care that something is key-addressable and writable rather than literally a dict — os.environ and hand-written mapping classes pass, dict subclass checks would miss them. Subclassing is the real payoff: implement __getitem__, __setitem__, __delitem__, __iter__ and __len__, and the mixins hand you get, pop, setdefault, update, items and keys for free. Import it from collections.abc — the old collections.MutableMapping alias was dropped in Python 3.10."
      },
      "syntax": "isinstance(obj, collections.abc.MutableMapping)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "изменяемый словарь",
        "базовый класс для своего словаря"
      ],
      "keywords": [
        "отображения"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "from types import MappingProxyType",
        "print(isinstance({}, abc.MutableMapping))   # → True",
        "d = {'a': 1}; d.update(b=2); print(d)   # → {'a': 1, 'b': 2}",
        "print(isinstance(MappingProxyType({'a': 1}), abc.Mapping))   # → True",
        "print(isinstance(MappingProxyType({'a': 1}), abc.MutableMapping))   # → False",
        "print(sorted(abc.MutableMapping.__abstractmethods__))   # → ['__delitem__', '__getitem__', '__iter__', '__len__', '__setitem__']"
      ],
      "related": [
        "collections.abc.Mapping",
        "collections.UserDict",
        "collections.abc.MutableSequence"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.MutableSequence",
      "title": "collections.abc.MutableSequence",
      "kind": "term",
      "summary": {
        "ru": "Изменяемая последовательность (list): плюс append/insert/__setitem__ — кортеж не подходит.",
        "en": "The mutable sequence protocol (list): adds append/insert/__setitem__."
      },
      "body": {
        "ru": "Для собственного класса нужно реализовать __getitem__, __setitem__, __delitem__, __len__ и insert — а append, extend, pop, remove, reverse и += появятся из миксинов сами. Как проверка типа это шире, чем isinstance(x, list): подойдут bytearray и collections.deque, а str и tuple отвалятся, потому что неизменяемы. В аннотации параметра MutableSequence честнее list — он прямо сообщает, что функция будет менять переданное на месте.",
        "en": "To build one yourself you supply __getitem__, __setitem__, __delitem__, __len__ and insert; append, extend, pop, remove, reverse and += come from the mixins. As a runtime check it is broader than isinstance(x, list) — bytearray and collections.deque qualify, while str and tuple do not, since they cannot be modified. As a parameter annotation it is more honest than list: it signals that the function intends to mutate what you pass in."
      },
      "syntax": "isinstance(obj, collections.abc.MutableSequence)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableSequence",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "изменяемый список",
        "проверка что список, а не кортеж"
      ],
      "keywords": [
        "последовательности"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance([], abc.MutableSequence))   # → True",
        "print(isinstance((), abc.MutableSequence))   # → False",
        "print(isinstance((), abc.Sequence))   # → True",
        "print(isinstance('abc', abc.MutableSequence))   # → False",
        "print(isinstance(bytearray(b'ab'), abc.MutableSequence))   # → True",
        "lst = [1, 2]; lst.insert(0, 0); print(lst)   # → [0, 1, 2]"
      ],
      "related": [
        "collections.abc.Sequence",
        "collections.UserList",
        "collections.abc.MutableMapping"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.MutableSet",
      "title": "collections.abc.MutableSet",
      "kind": "term",
      "summary": {
        "ru": "Изменяемое множество (set): add/discard — frozenset не подходит.",
        "en": "The mutable set protocol (set): add/discard."
      },
      "body": {
        "ru": "Своя реализация требует пяти методов — __contains__, __iter__, __len__, add и discard — а clear, pop, remove и операции |=, &=, -=, ^= достаются миксинами. Обратите внимание на разницу контрактов: discard молча игнорирует отсутствующий элемент, а remove на нём бросает KeyError, это не синонимы. И frozenset, и представления вроде dict.keys() — это Set, но не MutableSet: добавить в них ничего нельзя.",
        "en": "Implementing one means writing just __contains__, __iter__, __len__, add and discard; clear, pop, remove and the in-place |=, &=, -=, ^= operators are supplied by the mixins. Note that add's counterpart discard is silent about missing elements while remove raises KeyError — same idea, different contracts. frozenset and view objects such as dict.keys() register as Set but never as MutableSet, since nothing can be inserted into them."
      },
      "syntax": "isinstance(obj, collections.abc.MutableSet)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableSet",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "изменяемое множество",
        "множество, в которое можно добавлять"
      ],
      "keywords": [
        "множества"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance(set(), abc.MutableSet))         # → True",
        "print(isinstance(frozenset(), abc.MutableSet))   # → False",
        "print(isinstance(frozenset(), abc.Set))   # → True",
        "print(isinstance({}.keys(), abc.Set))   # → True",
        "s = {1, 2}; s.discard(3); print(s)   # → {1, 2}",
        "print(sorted(abc.MutableSet.__abstractmethods__))   # → ['__contains__', '__iter__', '__len__', 'add', 'discard']"
      ],
      "related": [
        "frozenset-неизменяемое-множество",
        "collections.abc.Collection",
        "collections.abc.MutableMapping"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Reversible",
      "title": "collections.abc.Reversible",
      "kind": "term",
      "summary": {
        "ru": "Протокол «можно перебрать в обратном порядке» (есть __reversed__).",
        "en": "The protocol for objects supporting reversed() (has __reversed__)."
      },
      "body": {
        "ru": "Проверка смотрит только на наличие __reversed__ и __iter__, а функция reversed() довольствуется и парой __len__ плюс __getitem__ — поэтому свой класс с обычным протоколом последовательности прекрасно развернётся, но isinstance на Reversible вернёт False. Если хотите, чтобы совпадало, наследуйтесь от Sequence: она уже объявлена Reversible. Генераторы и обычные итераторы сюда не входят — развернуть поток, у которого не известен конец, невозможно.",
        "en": "The check looks for __reversed__ (plus __iter__), but the reversed() builtin is happy with just __len__ and __getitem__ — so a plain sequence-protocol class reverses fine yet fails the isinstance test. Inherit from Sequence if you want the two to agree; Sequence is already declared Reversible. Generators and ordinary iterators are excluded, because a stream with no known end cannot be walked backwards."
      },
      "syntax": "isinstance(obj, collections.abc.Reversible)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Reversible",
      "version": "3.6",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "можно ли перебрать в обратном порядке",
        "обратный порядок обхода"
      ],
      "keywords": [
        "итерация"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance([1], abc.Reversible))   # → True",
        "print(isinstance(range(3), abc.Reversible))   # → True",
        "print(isinstance({'a': 1, 'b': 2}, abc.Reversible))   # → True",
        "print(list(reversed({'a': 1, 'b': 2})))   # → ['b', 'a']",
        "print(isinstance({1, 2}, abc.Reversible))   # → False",
        "print(isinstance((x for x in [1, 2]), abc.Reversible))   # → False"
      ],
      "related": [
        "collections.abc.Iterable",
        "reversed",
        "for-...-in-reversed"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Sequence",
      "title": "collections.abc.Sequence",
      "kind": "term",
      "summary": {
        "ru": "Протокол неизменяемой последовательности: индексация, срезы, len, in (list, tuple, str).",
        "en": "The immutable sequence protocol (indexing, slicing, len, in)."
      },
      "body": {
        "ru": "Главная ловушка: str тоже Sequence, поэтому проверка не отличит список строк от одной строки — функция спокойно переберёт её по символам, строку приходится отсеивать отдельной проверкой. Слово «неизменяемая» здесь условно: list тоже Sequence, ведь MutableSequence наследуется от него, и отдельной проверки «менять нельзя» в стандартной библиотеке нет. Наследнику хватает __getitem__ и __len__ — __contains__, __iter__, __reversed__, index и count дают миксины.",
        "en": "The classic trap: str is a Sequence, so this check will not separate a list of strings from a single string, and your function will happily iterate it character by character — screen strings out explicitly. Do not read it as \"read-only\" either: list is a Sequence too, because MutableSequence derives from it, and there is no ABC meaning \"cannot be modified\". For your own class only __getitem__ and __len__ are required; __contains__, __iter__, __reversed__, index and count arrive as mixins."
      },
      "syntax": "isinstance(obj, collections.abc.Sequence)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "последовательность с индексами",
        "проверка что объект список или кортеж"
      ],
      "keywords": [
        "последовательности"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance([1], abc.Sequence))   # → True",
        "print(isinstance((1,), abc.Sequence))   # → True",
        "print(isinstance('hello', abc.Sequence), isinstance(range(3), abc.Sequence))   # → True True",
        "print(isinstance({1, 2}, abc.Sequence), isinstance({'a': 1}, abc.Sequence))   # → False False",
        "print(isinstance(iter([1, 2]), abc.Sequence))   # → False",
        "print(issubclass(list, abc.MutableSequence), issubclass(tuple, abc.MutableSequence))   # → True False"
      ],
      "related": [
        "collections.abc.MutableSequence",
        "collections.abc.Collection",
        "collections.abc.Mapping"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.Sized",
      "title": "collections.abc.Sized",
      "kind": "term",
      "summary": {
        "ru": "Протокол «имеет длину» (есть __len__).",
        "en": "The protocol for objects with a length (has __len__)."
      },
      "body": {
        "ru": "Проверка структурная: isinstance вернёт True для любого класса, где определён __len__, — наследовать или регистрировать ничего не нужно. Sized ничего не говорит об итерируемости и индексации, и наоборот: генератор итерируем, но не Sized, поэтому len() на нём падает — именно этот случай Sized и позволяет отсеять заранее.",
        "en": "The check is structural: isinstance returns True for any class that defines __len__, with no inheritance or registration needed. Sized says nothing about iteration or indexing, and the reverse also holds — a generator is iterable but not Sized, so len() on it fails, and that is exactly the case Sized lets you screen out beforehand."
      },
      "syntax": "isinstance(obj, collections.abc.Sized)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.Sized",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "есть ли длина у объекта",
        "можно ли узнать длину объекта"
      ],
      "keywords": [
        "контейнеры"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance([1, 2], abc.Sized))   # → True",
        "print(isinstance('hello', abc.Sized), isinstance({'a': 1}, abc.Sized))   # → True True",
        "print(isinstance(42, abc.Sized))   # → False",
        "print(isinstance((i for i in range(3)), abc.Sized))   # → False",
        "print(issubclass(abc.Sequence, abc.Sized), issubclass(abc.Iterator, abc.Sized))   # → True False"
      ],
      "related": [
        "len",
        "collections.abc.Collection",
        "collections.abc.Container"
      ],
      "related_errors": []
    },
    {
      "id": "collections.abc.ValuesView",
      "title": "collections.abc.ValuesView",
      "kind": "term",
      "summary": {
        "ru": "Тип представления значений словаря (d.values()).",
        "en": "The type of a dict values view (d.values())."
      },
      "body": {
        "ru": "Это живое представление, а не копия: изменили словарь — изменилось и оно, а попытка добавить или удалить ключ прямо во время итерации по нему даёт RuntimeError. В отличие от представлений ключей и элементов, ValuesView не ведёт себя как множество (нет &, |, -): значения могут повторяться и быть нехешируемыми. Индексации и срезов тоже нет — нужен произвольный доступ, оборачивайте в list().",
        "en": "It is a live view, not a copy: change the dict and the view changes with it, and adding or removing keys while iterating over it raises RuntimeError. Unlike keys and items views, ValuesView is not set-like (no &, |, -), because values may repeat and may be unhashable. There is no indexing or slicing either — wrap it in list() if you need random access."
      },
      "syntax": "isinstance(obj, collections.abc.ValuesView)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.abc.html#collections.abc.ValuesView",
      "version": "",
      "section": "Модуль collections",
      "subcat": "collections.abc — протоколы",
      "color_group": "module",
      "aliases": [
        "представление значений словаря",
        "перебор значений словаря"
      ],
      "keywords": [
        "представления"
      ],
      "tags": [
        "collections"
      ],
      "examples": [
        "import collections.abc as abc",
        "print(isinstance({}.values(), abc.ValuesView))   # → True",
        "d = {'a': 1, 'b': 2}",
        "print(list(d.values()))   # → [1, 2]",
        "print(len(d.values()), sum(d.values()))   # → 2 3",
        "print(isinstance(d.keys(), abc.ValuesView), isinstance(d.keys(), abc.KeysView))   # → False True",
        "v = d.values()",
        "d['c'] = 10",
        "print(list(v))   # → [1, 2, 10]",
        "print(d.values()[0])   # → TypeError"
      ],
      "related": [
        "collections.abc.KeysView",
        "collections.abc.ItemsView",
        "collections.abc.MappingView",
        "dict.values"
      ],
      "related_errors": []
    },
    {
      "id": "collections.chainmap",
      "title": "collections.ChainMap",
      "kind": "term",
      "summary": {
        "ru": "Объединяет несколько словарей в один вид. Поиск идёт по порядку. maps — список словарей, new_child() — новый дочерний уровень, parents — все кроме первого.",
        "en": "Joins several dictionaries into a single view. Lookup goes through them in order. maps is the list of dictionaries, new_child() adds a new child level, parents is everything but the first one."
      },
      "body": {
        "ru": "Главное отличие от {**defaults, **user}: ChainMap не копирует, а держит ссылки на исходные словари, поэтому позже изменённый defaults сразу виден через цепочку. Запись, обновление и удаление затрагивают только первый словарь — удалить ключ, лежащий глубже, нельзя, будет KeyError. Отсюда типичный приём: new_child() для временного слоя настроек, который потом просто выбрасывается.",
        "en": "The key difference from {**defaults, **user}: ChainMap does not copy, it holds references to the original dicts, so a later change to defaults shows up through the chain immediately. Writes, updates and deletions touch only the first mapping — you cannot delete a key that lives deeper, you get a KeyError. Hence the usual pattern: new_child() for a temporary layer of settings that you later simply discard."
      },
      "syntax": "collections.ChainMap(*maps)\n.new_child(m=None)\n.parents",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.ChainMap",
      "version": "",
      "section": "Модуль collections",
      "subcat": "chainmap",
      "color_group": "module",
      "aliases": [
        "цепочка словарей",
        "поиск по нескольким словарям",
        "несколько словарей как один"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from collections import ChainMap",
        "defaults = {'color': 'red', 'size': 10}",
        "user = {'color': 'blue'}",
        "chain = ChainMap(user, defaults)",
        "print(chain['color'])  # → blue  (из user)",
        "print(chain['size'])  # → 10  (из defaults)",
        "print(chain.maps)  # → [{'color': 'blue'}, {'color': 'red', 'size': 10}]",
        "child = chain.new_child({'size': 20})",
        "print(child['size'])  # → 20"
      ],
      "related": [
        "chainmap",
        "объединение-словарей",
        "dict-merge",
        "collections.defaultdict"
      ],
      "related_errors": []
    },
    {
      "id": "collections.counter",
      "title": "collections.Counter",
      "kind": "term",
      "summary": {
        "ru": "Словарь для подсчёта элементов. Методы: most_common(n), elements(), поддерживает +, -, &, |.",
        "en": "A dictionary for counting items. Methods: most_common(n), elements(); it also supports +, -, & and |."
      },
      "body": {
        "ru": "Обращение к отсутствующему элементу возвращает 0 и, в отличие от defaultdict, ключ в словарь не добавляет — так что проверять наличие через c[x] безопасно. Осторожно с арифметикой: оператор минус выбрасывает нулевые и отрицательные счётчики, а метод subtract() их сохраняет, включая минусы. most_common() без аргумента сортирует всё целиком, при равных счётчиках порядок — как элементы впервые встретились.",
        "en": "Looking up a missing element returns 0 and, unlike defaultdict, does not insert the key, so testing with c[x] is harmless. Watch the arithmetic: the minus operator drops zero and negative counts, while the subtract() method keeps them, negatives included. most_common() with no argument sorts the whole thing, and ties are ordered by first encounter."
      },
      "syntax": "collections.Counter(iterable_or_mapping=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.Counter",
      "version": "3.1",
      "section": "Модуль collections",
      "subcat": "counter",
      "color_group": "module",
      "aliases": [
        "подсчёт элементов",
        "частота элементов",
        "самый частый элемент"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from collections import Counter",
        "c = Counter('abracadabra')",
        "print(c)  # → Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})",
        "print(c.most_common(2))  # → [('a', 5), ('b', 2)]",
        "c2 = Counter({'a': 1, 'b': 2})",
        "print(c + c2)  # → сложение счётчиков",
        "print(list(c.elements()))  # → все элементы с повторениями"
      ],
      "related": [
        "collections.defaultdict",
        "dict.get",
        "сортировка-словаря"
      ],
      "related_errors": []
    },
    {
      "id": "collections.defaultdict",
      "title": "collections.defaultdict",
      "kind": "term",
      "summary": {
        "ru": "Словарь с фабрикой значений по умолчанию. При обращении к отсутствующему ключу создаёт значение через фабрику.",
        "en": "A dictionary with a factory for default values. When a missing key is accessed, the factory creates the value."
      },
      "body": {
        "ru": "Главная ловушка: простое чтение d[key] тоже вызывает фабрику и вставляет ключ, так что после «проверки» словарь распухает пустыми списками. Хотите просто посмотреть — используйте key in d или d.get(key). Фабрика вызывается без аргументов, то есть значение по умолчанию не может зависеть от ключа; если нужна такая зависимость, переопределяйте __missing__ в наследнике dict.",
        "en": "The main trap: merely reading d[key] also calls the factory and inserts the key, so after a round of \"checking\" the dict is bloated with empty lists. To just look, use key in d or d.get(key). The factory is called with no arguments, so the default cannot depend on the key; if you need that, subclass dict and override __missing__."
      },
      "syntax": "collections.defaultdict(default_factory, ...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.defaultdict",
      "version": "",
      "section": "Модуль collections",
      "subcat": "defaultdict",
      "color_group": "module",
      "aliases": [
        "словарь со значением по умолчанию",
        "автоматическое создание ключа"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from collections import defaultdict",
        "d = defaultdict(int)",
        "d['a'] += 1  # → {'a': 1}, не KeyError",
        "d2 = defaultdict(list)",
        "d2['x'].append(1)  # → {'x': [1]}",
        "d3 = defaultdict(set)",
        "d3['k'].add('v')  # → {'k': {'v'}}",
        "print(d['missing'])  # → 0 (int())"
      ],
      "related": [
        "dict.setdefault",
        "collections.counter",
        "dict.get"
      ],
      "related_errors": []
    },
    {
      "id": "collections.deque",
      "title": "collections.deque",
      "kind": "term",
      "summary": {
        "ru": "Двусторонняя очередь. O(1) вставка/удаление с обоих концов. Поддерживает maxlen (скользящее окно).",
        "en": "A double-ended queue. O(1) insertion and removal at both ends. Supports maxlen (a sliding window)."
      },
      "body": {
        "ru": "Быстрые концы оплачены медленной серединой: обращение по индексу где-то в центре стоит O(n), а срезы deque вообще не поддерживает — если нужен произвольный доступ, берите list. Когда задан maxlen, очередь при переполнении молча выбрасывает элемент с противоположного конца, и это не ошибка, а весь смысл скользящего окна. Отдельный бонус — append и popleft атомарны, поэтому deque годится как очередь между потоками без блокировок.",
        "en": "Fast ends come at the cost of a slow middle: indexing somewhere in the center is O(n), and deque supports no slicing at all — if you need random access, use a list. With maxlen set, a full deque silently discards an item from the opposite end; that is not a bug but the whole point of a sliding window. A bonus: append and popleft are atomic, so a deque works as a lock-free queue between threads."
      },
      "syntax": "from collections import deque\ndeque(iterable=(), maxlen=None)\n.appendleft(x) / .popleft() / .rotate(n)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.deque",
      "version": "",
      "section": "Модуль collections",
      "subcat": "deque",
      "color_group": "module",
      "aliases": [
        "двусторонняя очередь",
        "быстрое удаление из начала списка",
        "скользящее окно"
      ],
      "keywords": [],
      "tags": [
        "collections"
      ],
      "examples": [
        "from collections import deque",
        "d = deque([1,2,3])",
        "d.append(4)      # → deque([1,2,3,4])",
        "d.appendleft(0)  # → deque([0,1,2,3,4])",
        "print(d)  # → deque([0,1,2,3,4])",
        "d.pop()          # → 4",
        "d.popleft()      # → 0",
        "print(d)  # → deque([1,2,3])",
        "# rotate",
        "d2 = deque([1,2,3,4,5])",
        "d2.rotate(2)",
        "print(d2)  # → deque([4,5,1,2,3])",
        "d2.rotate(-1)",
        "print(d2)  # → deque([5,1,2,3,4])",
        "# maxlen — скользящее окно",
        "last3 = deque(maxlen=3)",
        "for x in range(6):",
        "    last3.append(x)",
        "    print(list(last3))",
        "    # → [0] [0,1] [0,1,2] [1,2,3] [2,3,4] [3,4,5]",
        "    # Очередь (FIFO)",
        "    queue = deque()",
        "    queue.append('a'); queue.append('b'); queue.append('c')",
        "    print(queue.popleft())  # → a",
        "    print(queue.popleft())  # → b",
        "    # Стек (LIFO)",
        "    stack = deque()",
        "    stack.append(1); stack.append(2); stack.append(3)",
        "    print(stack.pop())  # → 3",
        "    print(stack.pop())  # → 2",
        "    # extend / extendleft",
        "    d3 = deque([1,2,3])",
        "    d3.extend([4,5])",
        "    d3.extendleft([0,-1])",
        "    print(d3)  # → deque([-1,0,1,2,3,4,5])"
      ],
      "related": [
        "очередь-queue",
        "стек-stack",
        "list.insert",
        "куча-как-приоритетная-очередь"
      ],
      "related_errors": []
    },
    {
      "id": "collections.namedtuple",
      "title": "collections.namedtuple",
      "kind": "term",
      "summary": {
        "ru": "Создаёт подкласс tuple с именованными полями. Поддерживает _replace() (копия с изменёнными полями) и _asdict() (в словарь).",
        "en": "Creates a subclass of tuple with named fields. It supports _replace() (a copy with some fields changed) and _asdict() (conversion to a dictionary)."
      },
      "body": {
        "ru": "Это настоящий кортеж, поэтому Point(1, 2) == (1, 2) даёт True, а поля можно распаковывать и перебирать в цикле — удобно, но легко нарваться на случайное равенство с обычным кортежем. Значения менять нельзя: _replace() возвращает новый объект, а не правит старый. Служебные методы начинаются с подчёркивания не по стеснительности, а чтобы не конфликтовать с вашими именами полей; если нужны изменяемость, значения по умолчанию и методы — берите dataclass.",
        "en": "It really is a tuple, so Point(1, 2) == (1, 2) is True and the fields unpack and iterate like tuple items — handy, but it means accidental equality with a plain tuple. The values are frozen: _replace() hands back a new object instead of editing the old one. The leading underscores on the helper methods exist so they cannot collide with your own field names; if you need mutability, defaults or methods, reach for a dataclass instead."
      },
      "syntax": "collections.namedtuple(typename, field_names)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.namedtuple",
      "version": "",
      "section": "Модуль collections",
      "subcat": "namedtuple",
      "color_group": "module",
      "aliases": [
        "кортеж с именами полей",
        "обращение к полям кортежа по имени"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from collections import namedtuple",
        "Point = namedtuple('Point', ['x', 'y'])",
        "p = Point(1, 2)",
        "print(p.x, p.y)  # → 1 2",
        "print(p._asdict())  # → {'x': 1, 'y': 2}",
        "p2 = p._replace(x=10)  # → Point(x=10, y=2)",
        "print(p2)  # → Point(x=10, y=2)"
      ],
      "related": [
        "namedtuple",
        "dataclass",
        "наименованный-кортеж-namedtuple"
      ],
      "related_errors": []
    },
    {
      "id": "collections.ordereddict",
      "title": "collections.OrderedDict",
      "kind": "term",
      "summary": {
        "ru": "Словарь, запоминающий порядок вставки. move_to_end() перемещает ключ в начало или конец.",
        "en": "A dictionary that remembers the insertion order. move_to_end() moves a key to the front or to the back."
      },
      "body": {
        "ru": "С Python 3.7 обычный dict и так гарантирует порядок вставки, поэтому OrderedDict нужен уже не ради порядка, а ради трёх вещей: move_to_end(), popitem(last=False) для снятия элемента с начала и равенства, которое учитывает порядок — два OrderedDict с одинаковыми парами, но в разной последовательности, не равны, тогда как обычные dict равны. Типичный сценарий сегодня — LRU-кеш: обращение к ключу двигаем в конец, вытесняем с начала.",
        "en": "Since Python 3.7 an ordinary dict already preserves insertion order, so OrderedDict is no longer about ordering as such but about three things: move_to_end(), popitem(last=False) to pop from the front, and order-sensitive equality — two OrderedDicts with the same pairs in a different sequence are unequal, while plain dicts would compare equal. The typical use today is an LRU cache: move a key to the end on access, evict from the front."
      },
      "syntax": "collections.OrderedDict(items=...)\nodict.move_to_end(key, last=True)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.OrderedDict",
      "version": "3.1",
      "section": "Модуль collections",
      "subcat": "ordereddict",
      "color_group": "module",
      "aliases": [
        "словарь с порядком вставки",
        "переместить ключ в конец словаря"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from collections import OrderedDict",
        "od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])",
        "od.move_to_end('a')  # → 'a' в конец",
        "print(list(od.keys()))  # → ['b', 'c', 'a']",
        "od.move_to_end('c', last=False)  # → 'c' в начало",
        "print(list(od.keys()))  # → ['c', 'b', 'a']",
        "print(od.popitem(last=True))  # → ('a', 1)"
      ],
      "related": [
        "collections.defaultdict",
        "collections.counter",
        "dict.popitem"
      ],
      "related_errors": []
    },
    {
      "id": "Future-cf",
      "title": "Future",
      "kind": "term",
      "summary": {
        "ru": "Объект, представляющий результат асинхронно выполняемой операции. Возвращается методом submit(). Позволяет проверить статус и получить результат.",
        "en": "An object representing the result of an operation carried out asynchronously. Returned by submit(). It lets you check the status and collect the result."
      },
      "body": {
        "ru": "Исключение внутри задачи не всплывает само: оно хранится в Future и выбрасывается только тогда, когда вы вызовете result() (или exception()). Если результат никто не спросил — ошибка пропадает бесследно, это классическая причина \"поток ничего не сделал и молчит\". И не вызывайте result() сразу в цикле подачи задач: это блокирует и превращает пул в последовательное выполнение — собирайте futures в список, а потом обходите их через as_completed().",
        "en": "An exception inside the task does not surface on its own: it is stored in the Future and only re-raised when you call result() (or exception()). If nobody asks for the result, the error vanishes without a trace — the classic reason a worker \"did nothing and said nothing\". Also avoid calling result() inside the submit loop: it blocks and turns the pool back into sequential execution — collect the futures first, then walk them with as_completed()."
      },
      "syntax": "f = executor.submit(fn, *args)\nf.result()   # блокирует до готовности\nf.done()     # True если завершён\nf.cancel()   # отменить, если ещё не запущен",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Future",
      "version": "",
      "section": "Модуль concurrent.futures",
      "subcat": "асинхронный результат",
      "color_group": "module",
      "aliases": [
        "результат асинхронной задачи",
        "объект отложенного результата",
        "проверить, готова ли задача"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from concurrent.futures import ThreadPoolExecutor",
        "with ThreadPoolExecutor() as ex:",
        "    f = ex.submit(sum, range(1000))",
        "    print(f.done())     # False или True",
        "    print(f.result())   # 499500 — блокирует"
      ],
      "related": [
        "executor-submit",
        "as-completed",
        "ThreadPoolExecutor"
      ],
      "related_errors": []
    },
    {
      "id": "ProcessPoolExecutor",
      "title": "ProcessPoolExecutor",
      "kind": "term",
      "summary": {
        "ru": "Пул процессов из concurrent.futures. Обходит GIL — каждый процесс имеет свой интерпретатор. Используй для CPU-интенсивных задач.",
        "en": "A pool of processes from concurrent.futures. It gets around the GIL — every process has its own interpreter. Use it for CPU-intensive work."
      },
      "body": {
        "ru": "Аргументы и возвращаемые значения ездят между процессами через pickle, поэтому лямбды, вложенные функции и незапикливаемые объекты сюда не передать — функция должна быть определена на верхнем уровне модуля. На Windows и macOS процессы стартуют через spawn, то есть дочерний процесс заново импортирует ваш модуль: без охраны if __name__ == '__main__' программа начнёт бесконечно плодить процессы. И запуск процесса плюс сериализация данных стоят дорого, так что на мелких или I/O-задачах ThreadPoolExecutor окажется быстрее.",
        "en": "Arguments and return values travel between processes through pickle, so lambdas, nested functions and unpicklable objects cannot be passed — the target function must live at module top level. On Windows and macOS processes start via spawn, meaning the child re-imports your module: without an if __name__ == '__main__' guard the program spawns processes forever. Process startup plus serialization is expensive too, so for small or I/O-bound work ThreadPoolExecutor will come out ahead."
      },
      "syntax": "with ProcessPoolExecutor(max_workers=N) as ex:\n    future = ex.submit(fn, *args)\n    results = ex.map(fn, iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor",
      "version": "",
      "section": "Модуль concurrent.futures",
      "subcat": "пул процессов",
      "color_group": "module",
      "aliases": [
        "пул процессов",
        "параллельные вычисления на нескольких ядрах"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from concurrent.futures import ProcessPoolExecutor",
        "def heavy(n):",
        "    return sum(range(n))",
        "if __name__ == '__main__':",
        "    with ProcessPoolExecutor() as ex:",
        "        results = list(ex.map(heavy, [10**6] * 4))",
        "        print(results)"
      ],
      "related": [
        "ThreadPoolExecutor",
        "GIL",
        "executor-submit"
      ],
      "related_errors": []
    },
    {
      "id": "ThreadPoolExecutor",
      "title": "ThreadPoolExecutor",
      "kind": "term",
      "summary": {
        "ru": "Пул потоков из модуля concurrent.futures. Управляет созданием и переиспользованием потоков. Удобен для параллельных I/O-задач.",
        "en": "A pool of threads from the concurrent.futures module. It handles creating and reusing the threads. Convenient for parallel I/O work."
      },
      "body": {
        "ru": "Потоки в CPython делят один GIL, поэтому пул ускоряет только ожидание — сеть, диск, время; на чистых вычислениях выигрыша не будет, там нужен ProcessPoolExecutor. Выход из with вызывает shutdown(wait=True) и блокирует программу до конца всех задач, а исключение внутри задачи не всплывает само — оно ждёт в Future и выстрелит только при обращении к result(). Если max_workers не задан, число потоков считается от числа ядер и сверху ограничено 32.",
        "en": "CPython threads share one GIL, so a thread pool only speeds up waiting — network, disk, sleep; for pure computation it buys nothing and you want ProcessPoolExecutor instead. Leaving the with block calls shutdown(wait=True) and blocks until every task is done, and an exception raised inside a task stays parked in its Future until you touch result(). With max_workers left out, the thread count is derived from the CPU count and capped at 32."
      },
      "syntax": "with ThreadPoolExecutor(max_workers=N) as ex:\n    future = ex.submit(fn, *args)\n    results = ex.map(fn, iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor",
      "version": "",
      "section": "Модуль concurrent.futures",
      "subcat": "пул потоков",
      "color_group": "module",
      "aliases": [
        "пул потоков",
        "параллельные задачи ввода-вывода"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from concurrent.futures import ThreadPoolExecutor",
        "import urllib.request",
        "urls = ['https://python.org', 'https://pypi.org']",
        "def fetch(url):",
        "    return len(urllib.request.urlopen(url).read())",
        "with ThreadPoolExecutor(max_workers=4) as ex:",
        "    sizes = list(ex.map(fetch, urls))",
        "    print(sizes)"
      ],
      "related": [
        "ProcessPoolExecutor",
        "executor-submit",
        "GIL",
        "threading-thread"
      ],
      "related_errors": []
    },
    {
      "id": "as-completed",
      "title": "as_completed()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает итератор, который отдаёт Future по мере их завершения — не в порядке создания, а в порядке готовности.",
        "en": "Returns an iterator that yields the Futures as they finish — in the order they become ready, not the order they were created."
      },
      "body": {
        "ru": "Итератор отдаёт сами объекты Future, а не результаты, и порядок теряется — чтобы понять, к какому входу относится ответ, обычно заводят словарь future -> аргумент. Берите as_completed, когда хотите обрабатывать ответы по мере готовности; если нужен порядок исходных данных, проще executor.map(). Необязательный timeout отсчитывается от вызова as_completed, а не от каждой задачи по отдельности.",
        "en": "The iterator yields Future objects, not results, and the original order is gone — the usual trick is a dict mapping each future back to its input. Reach for as_completed when you want to handle answers the moment they arrive; if you need results in the order of the inputs, executor.map() is simpler. The optional timeout is measured from the as_completed call itself, not per future."
      },
      "syntax": "from concurrent.futures import as_completed\nfor future in as_completed(fs): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed",
      "version": "",
      "section": "Модуль concurrent.futures",
      "subcat": "пул задач",
      "color_group": "module",
      "aliases": [
        "обработать задачи по мере готовности",
        "результаты в порядке завершения"
      ],
      "keywords": [
        "as_completed"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from concurrent.futures import ThreadPoolExecutor, as_completed",
        "import time, random",
        "def task(n):",
        "    time.sleep(random.random())",
        "    return n * 2",
        "with ThreadPoolExecutor() as ex:",
        "    futures = [ex.submit(task, i) for i in range(5)]",
        "    for f in as_completed(futures):",
        "        print(f.result())  # выводит по мере готовности"
      ],
      "related": [
        "Future-cf",
        "executor-submit",
        "asyncio.gather"
      ],
      "related_errors": []
    },
    {
      "id": "executor-submit",
      "title": "executor.submit()",
      "kind": "function",
      "summary": {
        "ru": "Отправляет вызов функции на выполнение в пул и немедленно возвращает объект Future, не дожидаясь результата.",
        "en": "Sends a call to the pool for execution and immediately returns a Future object, without waiting for the result."
      },
      "body": {
        "ru": "Классическая ошибка — написать ex.submit(fn(x)): функция при этом выполняется прямо здесь, в пул уходит уже готовое значение. Аргументы передаются отдельно: ex.submit(fn, x). Второе — submit никогда не бросает исключение задачи: оно сохраняется в Future и поднимется при вызове result(), поэтому пул, у которого результаты не забирают, глотает ошибки молча.",
        "en": "The classic slip is ex.submit(fn(x)): that calls the function right there and hands the pool a finished value. Arguments go separately: ex.submit(fn, x). Also, submit never raises the task's exception — it is stored in the Future and re-raised by result(), so a pool whose results nobody collects swallows failures silently."
      },
      "syntax": "future = executor.submit(fn, *args, **kwargs)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Executor.submit",
      "version": "",
      "section": "Модуль concurrent.futures",
      "subcat": "пул задач",
      "color_group": "module",
      "aliases": [
        "отправить задачу в пул",
        "запустить функцию асинхронно в пуле"
      ],
      "keywords": [
        "submit",
        "executor.submit"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from concurrent.futures import ThreadPoolExecutor",
        "def greet(name):",
        "    return f'Привет, {name}!'",
        "with ThreadPoolExecutor() as ex:",
        "    f1 = ex.submit(greet, 'Артём')",
        "    f2 = ex.submit(greet, 'Python')",
        "    print(f1.result())  # Привет, Артём!",
        "    print(f2.result())"
      ],
      "related": [
        "Future-cf",
        "ThreadPoolExecutor",
        "as-completed"
      ],
      "related_errors": []
    },
    {
      "id": "concurrent.interpreters.InterpreterError",
      "title": "concurrent.interpreters.InterpreterError",
      "kind": "exception",
      "summary": {
        "ru": "Базовое исключение модуля concurrent.interpreters (Python 3.14+): любая ошибка при работе с субинтерпретатором; подкласс Exception, предок InterpreterNotFoundError и ExecutionFailed.",
        "en": "Base exception of the concurrent.interpreters module (Python 3.14+), raised when an interpreter-related error happens; a subclass of Exception."
      },
      "body": {
        "ru": "Ловить его стоит как «что-то не так с субинтерпретатором вообще»: под него попадают и мёртвый интерпретатор (InterpreterNotFoundError), и упавший внутри код (ExecutionFailed), а вот NotShareableError — нет, тот растёт от TypeError. Важно, что исключение из субинтерпретатора не прилетает к вам «как есть»: объекты не пересекают границу интерпретаторов, наружу выходит ExecutionFailed со снимком исходной ошибки. Сам модуль появился только в Python 3.14 — на более ранних версиях падает уже импорт.",
        "en": "Catch it when you mean \"anything went wrong with a subinterpreter\": it covers both a dead interpreter (InterpreterNotFoundError) and code that blew up inside one (ExecutionFailed) — but not NotShareableError, which hangs off TypeError instead. Note that an exception raised inside the subinterpreter does not reach you unchanged: objects do not cross the interpreter boundary, so what surfaces is ExecutionFailed carrying a snapshot of the original error. The module itself is new in Python 3.14; on older versions the import already fails."
      },
      "syntax": "raise concurrent.interpreters.InterpreterError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/concurrent.interpreters.html#concurrent.interpreters.InterpreterError",
      "version": "3.14",
      "section": "Модуль concurrent.interpreters",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "ошибка субинтерпретатора",
        "ошибка при работе с интерпретатором",
        "параллельные интерпретаторы"
      ],
      "keywords": [
        "concurrent.interpreters.InterpreterError",
        "InterpreterError"
      ],
      "tags": [
        "concurrent"
      ],
      "examples": [
        "from concurrent import interpreters  # Python 3.14+",
        "print(interpreters.InterpreterError.__name__)  # → InterpreterError",
        "print(issubclass(interpreters.InterpreterError, Exception))  # → True",
        "print(issubclass(interpreters.InterpreterNotFoundError, interpreters.InterpreterError))  # → True",
        "print(issubclass(interpreters.ExecutionFailed, interpreters.InterpreterError))  # → True",
        "try:",
        "    raise interpreters.InterpreterError('интерпретатор недоступен')",
        "except interpreters.InterpreterError as e:",
        "    print(type(e).__name__, '|', e)  # → InterpreterError | интерпретатор недоступен"
      ],
      "related": [
        "exception",
        "GIL",
        "threading-thread",
        "ThreadPoolExecutor"
      ],
      "related_errors": []
    },
    {
      "id": "concurrent.interpreters.InterpreterNotFoundError",
      "title": "concurrent.interpreters.InterpreterNotFoundError",
      "kind": "exception",
      "summary": {
        "ru": "Целевой субинтерпретатор больше не существует — закрыт через close() или указан неизвестный id (Python 3.14+); подкласс InterpreterError.",
        "en": "The targeted interpreter no longer exists — closed or an unknown id (Python 3.14+); a subclass of InterpreterError."
      },
      "body": {
        "ru": "Обычная причина — вы держите объект Interpreter после close() или обращаетесь по id, за которым уже ничего нет; любой exec() или call() по такому хендлу упадёт именно этим исключением. Повторять попытку бессмысленно: интерпретатор не воскреснет, нужно создавать новый через create(). Если ловите его вместе с прочими ошибками, ставьте его выше InterpreterError в цепочке except — иначе базовый класс перехватит первым.",
        "en": "The usual cause is holding on to an Interpreter object after close(), or addressing an id that nothing owns any more; any exec() or call() through such a handle fails with this exception. Retrying is pointless — the interpreter is gone for good, so create a fresh one. If you handle it alongside other errors, put this except clause above InterpreterError, or the base class will swallow it first."
      },
      "syntax": "raise concurrent.interpreters.InterpreterNotFoundError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/concurrent.interpreters.html#concurrent.interpreters.InterpreterNotFoundError",
      "version": "3.14",
      "section": "Модуль concurrent.interpreters",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "интерпретатор не найден",
        "субинтерпретатор уничтожен"
      ],
      "keywords": [
        "concurrent.interpreters.InterpreterNotFoundError",
        "InterpreterNotFoundError"
      ],
      "tags": [
        "concurrent"
      ],
      "examples": [
        "from concurrent import interpreters  # Python 3.14+",
        "print(issubclass(interpreters.InterpreterNotFoundError, interpreters.InterpreterError))  # → True",
        "interp = interpreters.create()",
        "interp.exec('x = 1')",
        "interp.close()",
        "try:",
        "    interp.exec('x = 2')  # интерпретатор уже уничтожен",
        "except interpreters.InterpreterNotFoundError as e:",
        "    print(type(e).__name__)  # → InterpreterNotFoundError",
        "try:",
        "    interpreters.Interpreter(999999)  # такого id не существует",
        "except interpreters.InterpreterNotFoundError:",
        "    print('нет такого интерпретатора')  # → нет такого интерпретатора"
      ],
      "related": [
        "exception",
        "иерархия-исключений",
        "GIL",
        "threading-thread"
      ],
      "related_errors": []
    },
    {
      "id": "concurrent.interpreters.NotShareableError",
      "title": "concurrent.interpreters.NotShareableError",
      "kind": "exception",
      "summary": {
        "ru": "Объект нельзя отправить в другой интерпретатор: он не поддерживает cross-interpreter data (Python 3.14+); подкласс TypeError.",
        "en": "The object cannot be sent to another interpreter (Python 3.14+); a subclass of TypeError."
      },
      "body": {
        "ru": "Главная ловушка — класс наследуется от TypeError, поэтому общий except TypeError вокруг передачи данных проглотит его молча, и вы будете искать ошибку в аргументах. Круг разделяемых объектов узкий: простые неизменяемые значения проходят, а списки, словари и свои классы — нет; проверяйте is_shareable() заранее или передавайте данные в сериализованном виде, например JSON-строкой.",
        "en": "The trap is the inheritance: it is a TypeError subclass, so a broad except TypeError around your data passing will swallow it silently and send you hunting for a bug in the arguments. The shareable set is narrow — simple immutable values pass, lists, dicts and your own classes do not — so check is_shareable() up front, or hand the data over serialized, e.g. as a JSON string."
      },
      "syntax": "raise concurrent.interpreters.NotShareableError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/concurrent.interpreters.html#concurrent.interpreters.NotShareableError",
      "version": "3.14",
      "section": "Модуль concurrent.interpreters",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "объект нельзя передать между интерпретаторами",
        "объект не разделяемый",
        "несовместимый объект для интерпретаторов"
      ],
      "keywords": [
        "concurrent.interpreters.NotShareableError",
        "NotShareableError"
      ],
      "tags": [
        "concurrent"
      ],
      "examples": [
        "from concurrent import interpreters  # Python 3.14+",
        "print(issubclass(interpreters.NotShareableError, TypeError))  # → True",
        "print(interpreters.is_shareable(42))  # → True",
        "print(interpreters.is_shareable([1, 2, 3]))  # → False",
        "interp = interpreters.create()",
        "try:",
        "    interp.prepare_main(data=object())  # object() не переносится между интерпретаторами",
        "except interpreters.NotShareableError as e:",
        "    print(type(e).__name__)  # → NotShareableError",
        "interp.prepare_main(n=42)  # число — переносится",
        "interp.close()"
      ],
      "related": [
        "typeerror",
        "pickle",
        "GIL",
        "ThreadPoolExecutor"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.AbstractAsyncContextManager",
      "title": "contextlib.AbstractAsyncContextManager",
      "kind": "term",
      "summary": {
        "ru": "Абстрактный базовый класс асинхронного контекстного менеджера (протокол __aenter__/__aexit__, для async with).",
        "en": "The ABC for async context managers (the __aenter__/__aexit__ protocol)."
      },
      "body": {
        "ru": "Наследоваться не обязательно: проверка подкласса идёт по наличию __aenter__ и __aexit__, так что любой подходящий класс уже считается подклассом — сам ABC нужен в основном как готовый базовый класс (даёт __aenter__, возвращающий self) и как аннотация типа. Проверка смотрит только на имена методов, а не на то, что они корутины, и не отличает асинхронный менеджер от синхронного по смыслу. Появился в Python 3.7; с 3.9 класс поддерживает параметризацию квадратными скобками.",
        "en": "You rarely need to inherit from it: the subclass check is structural, based on __aenter__ and __aexit__ being present, so a suitable class already counts — the ABC mostly serves as a ready base (it supplies an __aenter__ returning self) and as a type annotation. That check only looks at method names, not at whether they are actually coroutines. Added in Python 3.7, and subscriptable for generics since 3.9."
      },
      "syntax": "class C(contextlib.AbstractAsyncContextManager): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.AbstractAsyncContextManager",
      "version": "3.7",
      "section": "Модуль contextlib",
      "subcat": "протоколы",
      "color_group": "module",
      "aliases": [
        "протокол асинхронного менеджера контекста",
        "свой класс для асинхронного контекста"
      ],
      "keywords": [],
      "tags": [
        "contextlib"
      ],
      "examples": [
        "import contextlib",
        "class ACM:",
        "    async def __aenter__(self): return self",
        "    async def __aexit__(self, *a): return False",
        "print(issubclass(ACM, contextlib.AbstractAsyncContextManager))   # → True"
      ],
      "related": [
        "contextlib.AbstractContextManager",
        "contextlib.asynccontextmanager",
        "async-for-async-with"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.AbstractContextManager",
      "title": "contextlib.AbstractContextManager",
      "kind": "term",
      "summary": {
        "ru": "Абстрактный базовый класс контекстного менеджера (протокол __enter__/__exit__); любой класс с этими методами — его виртуальный подкласс.",
        "en": "The ABC for context managers (the __enter__/__exit__ protocol)."
      },
      "body": {
        "ru": "Это не обязанность, а удобство: issubclass проходит для любого класса с __enter__ и __exit__, даже если он ничего не наследовал, — поэтому свой менеджер писать через наследование не нужно. Смысл наследования в том, что базовый класс уже даёт __enter__, возвращающий self, и остаётся описать только __exit__; ещё его удобно указывать в аннотациях типов. Асинхронные менеджеры сюда не попадают — у них отдельный AbstractAsyncContextManager.",
        "en": "Inheriting is a convenience, not a requirement: issubclass succeeds for any class defining __enter__ and __exit__, so your own manager needs no base class at all. What subclassing buys you is a ready __enter__ that returns self, leaving only __exit__ to write, plus a clean name to use in type hints. Async managers are not covered here — they have their own AbstractAsyncContextManager."
      },
      "syntax": "class C(contextlib.AbstractContextManager): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.AbstractContextManager",
      "version": "3.6",
      "section": "Модуль contextlib",
      "subcat": "протоколы",
      "color_group": "module",
      "aliases": [
        "протокол контекстного менеджера",
        "базовый класс для своего менеджера контекста"
      ],
      "keywords": [],
      "tags": [
        "contextlib"
      ],
      "examples": [
        "import contextlib",
        "class CM:",
        "    def __enter__(self): return self",
        "    def __exit__(self, *a): return False",
        "print(issubclass(CM, contextlib.AbstractContextManager))   # → True"
      ],
      "related": [
        "__enter__-__exit__",
        "contextlib.AbstractAsyncContextManager",
        "contextlib.contextmanager",
        "abc.ABC"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.AsyncExitStack",
      "title": "contextlib.AsyncExitStack",
      "kind": "term",
      "summary": {
        "ru": "Асинхронный аналог ExitStack: динамически регистрирует и корректно закрывает несколько (async) контекстных менеджеров (Python 3.7+).",
        "en": "An async ExitStack: dynamically manage several (async) context managers (3.7+)."
      },
      "body": {
        "ru": "Нужен там, где число ресурсов известно только в рантайме: открыть N соединений в цикле нельзя одним async with, а стек примет их по одному и закроет в обратном порядке при выходе. Асинхронные менеджеры регистрируются через enter_async_context() и push_async_exit(), обычные синхронные — через те же enter_context()/callback(), что и у ExitStack; перепутать методы — самая частая ошибка. Ещё приём: pop_all() переносит уже накопленные ресурсы в новый стек, чтобы не закрывать их, если инициализация дошла до конца успешно.",
        "en": "Reach for it when the number of resources is only known at runtime: you cannot write one async with for N connections opened in a loop, but a stack accepts them one by one and unwinds them in reverse order on exit. Async managers go in through enter_async_context() and push_async_exit(), while plain sync ones use the same enter_context()/callback() as ExitStack — mixing the two up is the usual mistake. A handy trick is pop_all(), which hands the collected resources to a fresh stack so they survive once setup has fully succeeded."
      },
      "syntax": "async with contextlib.AsyncExitStack() as stack: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.AsyncExitStack",
      "version": "",
      "section": "Модуль contextlib",
      "subcat": "менеджеры",
      "color_group": "module",
      "aliases": [
        "асинхронный стек контекстных менеджеров",
        "динамическое закрытие нескольких асинхронных менеджеров"
      ],
      "keywords": [],
      "tags": [
        "contextlib"
      ],
      "examples": [
        "import asyncio",
        "import contextlib",
        "print(issubclass(contextlib.AsyncExitStack, contextlib.AbstractAsyncContextManager))   # → True",
        "stack = contextlib.AsyncExitStack()",
        "print(hasattr(stack, 'enter_async_context'), hasattr(stack, 'push_async_callback'))   # → True True",
        "print(hasattr(stack, 'enter_context'), hasattr(stack, 'callback'))   # → True True",
        "print(hasattr(stack, '__aenter__'), hasattr(stack, '__enter__'))   # → True False",
        "print(issubclass(contextlib.AsyncExitStack, contextlib.ExitStack))   # → False",
        "stack.callback(print, 'закрыто')",
        "asyncio.run(stack.aclose())   # → закрыто"
      ],
      "related": [
        "contextlib.exitstack",
        "contextlib.asynccontextmanager",
        "async-for-async-with"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.ContextDecorator",
      "title": "contextlib.ContextDecorator",
      "kind": "term",
      "summary": {
        "ru": "Базовый класс, позволяющий использовать контекстный менеджер ещё и как декоратор функции (@cm).",
        "en": "A base class letting a context manager also be used as a function decorator."
      },
      "body": {
        "ru": "Смысл в том, чтобы не оборачивать всё тело функции в with ради одного и того же менеджера — но у формы @cm нет способа получить значение, которое менеджер отдаёт через as, поэтому она годится только для менеджеров, работающих ради побочного эффекта. Базовый _recreate_cm() возвращает тот же самый объект, так что одноразовый менеджер после первого вызова функции сломается: его нужно переопределить (у @contextmanager это уже сделано — генератор создаётся заново на каждый вызов).",
        "en": "It exists so you do not have to wrap a whole function body in with for the same manager every time — but the @cm form gives you no way to reach the value the manager yields via as, so it only fits managers used for their side effect. The default _recreate_cm() returns the very same object, so a single-use manager breaks on the second call to the decorated function unless you override it; @contextmanager already does this and rebuilds its generator per call."
      },
      "syntax": "class C(contextlib.ContextDecorator): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.ContextDecorator",
      "version": "3.2",
      "section": "Модуль contextlib",
      "subcat": "менеджеры",
      "color_group": "module",
      "aliases": [
        "менеджер контекста как декоратор",
        "декоратор из контекстного менеджера"
      ],
      "keywords": [],
      "tags": [
        "contextlib"
      ],
      "examples": [
        "import contextlib",
        "print(hasattr(contextlib.ContextDecorator, '_recreate_cm'))   # → True",
        "print('__call__' in vars(contextlib.ContextDecorator))   # → True",
        "print(callable(contextlib.ContextDecorator()))   # → True",
        "print(hasattr(contextlib.ContextDecorator, '__enter__'), hasattr(contextlib.ContextDecorator, '__exit__'))   # → False False",
        "gen_cm = contextlib.contextmanager(lambda: iter([None]))",
        "print(isinstance(gen_cm(), contextlib.ContextDecorator))   # → True"
      ],
      "related": [
        "contextlib.contextmanager",
        "декораторы",
        "__enter__-__exit__"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.aclosing",
      "title": "contextlib.aclosing",
      "kind": "term",
      "summary": {
        "ru": "Асинхронный контекстный менеджер, гарантирующий вызов aclose() у асинхронного ресурса на выходе (Python 3.10+).",
        "en": "An async context manager ensuring aclose() is called on exit (3.10+)."
      },
      "body": {
        "ru": "Главный случай — асинхронные генераторы с try/finally: если выйти из async for через break или исключение, финализация генератора не произойдёт сразу, её отложат до shutdown_asyncgens() у цикла событий, то есть в непредсказуемый момент. aclosing делает закрытие детерминированным ровно в точке выхода из блока. Работает только с объектами, у которых есть aclose(); для синхронного close() — обычный closing().",
        "en": "The main case is async generators with try/finally: if you leave an async for via break or an exception, the generator is not finalized right there — cleanup is deferred to the event loop's shutdown_asyncgens(), at an unpredictable moment. aclosing makes the teardown happen deterministically where the block ends. It only works with objects exposing aclose(); for a plain sync close() use closing() instead."
      },
      "syntax": "async with contextlib.aclosing(thing): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.aclosing",
      "version": "3.10",
      "section": "Модуль contextlib",
      "subcat": "менеджеры",
      "color_group": "module",
      "aliases": [
        "закрыть асинхронный генератор",
        "автозакрытие асинхронного ресурса"
      ],
      "keywords": [],
      "tags": [
        "contextlib"
      ],
      "examples": [
        "import contextlib",
        "import asyncio",
        "print(hasattr(contextlib.aclosing, '__call__'))   # → True",
        "print(issubclass(contextlib.aclosing, contextlib.AbstractAsyncContextManager))   # → True",
        "cm = contextlib.aclosing('resource')",
        "print(hasattr(cm, '__aenter__') and hasattr(cm, '__aexit__'))   # → True",
        "print(asyncio.run(cm.__aenter__()))   # → resource",
        "with contextlib.aclosing(None): pass   # → TypeError"
      ],
      "related": [
        "contextlib.closing",
        "contextlib.asynccontextmanager",
        "async-for-async-with"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.asynccontextmanager",
      "title": "contextlib.asynccontextmanager",
      "kind": "function",
      "summary": {
        "ru": "Декоратор: превращает асинхронную генераторную функцию (один yield) в асинхронный контекстный менеджер (Python 3.7+).",
        "en": "A decorator turning an async generator (with one yield) into an async context manager (3.7+)."
      },
      "body": {
        "ru": "Yield должен сработать ровно один раз: исключение из тела async with пробрасывается внутрь генератора в точку yield, поэтому освобождение ресурса кладут в finally — иначе при ошибке оно просто не выполнится. Полученный менеджер одноразовый: объект, возвращённый вызовом декорированной функции, нельзя использовать в двух async with, нужно вызывать функцию заново. С Python 3.10 результат ещё и сам является ContextDecorator для асинхронных функций, то есть его можно вешать декоратором.",
        "en": "The yield must happen exactly once: an exception raised inside the async with body is thrown back into the generator at the yield, so release code belongs in a finally block or it silently never runs on failure. The manager it produces is single-use — the object returned by one call cannot serve two async with statements, so call the function again. Since Python 3.10 the result also works as a decorator on async functions."
      },
      "syntax": "@contextlib.asynccontextmanager",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager",
      "version": "3.7",
      "section": "Модуль contextlib",
      "subcat": "менеджеры",
      "color_group": "module",
      "aliases": [
        "создать асинхронный контекстный менеджер",
        "свой менеджер контекста для асинхронного кода"
      ],
      "keywords": [],
      "tags": [
        "contextlib"
      ],
      "examples": [
        "import contextlib, inspect",
        "@contextlib.asynccontextmanager",
        "async def cm():",
        "    yield 1",
        "print(hasattr(cm(), '__aenter__'))   # → True"
      ],
      "related": [
        "contextlib.contextmanager",
        "async-for-async-with",
        "contextlib.AbstractAsyncContextManager"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.chdir",
      "title": "contextlib.chdir",
      "kind": "term",
      "summary": {
        "ru": "Контекстный менеджер: временно меняет текущий рабочий каталог, восстанавливая его на выходе (Python 3.11+).",
        "en": "A context manager that temporarily changes the current working directory (3.11+)."
      },
      "body": {
        "ru": "Меняется рабочий каталог всего процесса, а не отдельного потока, поэтому в многопоточном или асинхронном коде соседние потоки и задачи внезапно увидят чужой каталог — документация прямо отмечает, что менеджер не потокобезопасен. Вложенность при этом безопасна: он реентерабельный и запоминает предыдущий каталог на каждом входе. В обычном коде надёжнее просто складывать абсолютные пути, а chdir беречь для случаев, когда чужая библиотека умеет работать только с текущим каталогом.",
        "en": "It changes the working directory of the whole process, not of one thread, so in threaded or async code other threads and tasks suddenly see someone else's directory — the docs call it out as not thread-safe. Nesting is fine, though: it is reentrant and remembers the previous directory on each entry. In everyday code building absolute paths is safer; keep chdir for third-party code that insists on operating relative to the current directory."
      },
      "syntax": "with contextlib.chdir(path): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.chdir",
      "version": "3.11",
      "section": "Модуль contextlib",
      "subcat": "менеджеры",
      "color_group": "module",
      "aliases": [
        "временно сменить рабочий каталог",
        "временно перейти в другую директорию"
      ],
      "keywords": [],
      "tags": [
        "contextlib"
      ],
      "examples": [
        "import contextlib",
        "import os",
        "cm = contextlib.chdir('.')",
        "print(hasattr(cm, '__enter__') and hasattr(cm, '__exit__'))   # → True",
        "with contextlib.chdir('.') as v: print(v)   # → None",
        "before = os.getcwd()",
        "with contextlib.chdir('..'): print(os.getcwd() == os.path.dirname(before))   # → True",
        "print(os.getcwd() == before)   # → True",
        "with contextlib.chdir('no_such_dir'): pass   # → FileNotFoundError"
      ],
      "related": [
        "os.chdir",
        "os.getcwd",
        "path.cwd",
        "contextlib.contextmanager"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.closing",
      "title": "contextlib.closing",
      "kind": "term",
      "summary": {
        "ru": "Оборачивает объект, чтобы его метод close() вызывался автоматически при выходе из блока with.",
        "en": "Wraps an object so that its close() method is called automatically when the with block is left."
      },
      "body": {
        "ru": "Нужен только тем объектам, у которых есть close(), но нет протокола with. Файлы, сокеты и ответы urlopen в современном Python уже сами контекстные менеджеры, так что оборачивать их в closing бессмысленно — это приём для чужих или старых API. close() будет вызван и при нормальном выходе, и при исключении, но ошибки внутри самого close() не подавляются.",
        "en": "It exists only for objects that have a close() method but do not implement the with protocol. Files, sockets and urlopen responses are already context managers in modern Python, so wrapping them adds nothing — closing is for third-party or legacy APIs. The close() call happens on both normal exit and exception, and an error raised by close() itself is not swallowed."
      },
      "syntax": "contextlib.closing(thing)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.closing",
      "version": "",
      "section": "Модуль contextlib",
      "subcat": "закрытие",
      "color_group": "module",
      "aliases": [
        "автоматически закрыть объект после with",
        "закрытие ресурса без своего менеджера"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from contextlib import closing",
        "from urllib.request import urlopen",
        "with closing(urlopen('http://example.com')) as page:",
        "data = page.read()  # page.close() вызовется автоматически",
        "class Res:",
        "def close(self): print('closed')",
        "with closing(Res()) as r:",
        "pass # → closed"
      ],
      "related": [
        "contextlib.aclosing",
        "contextlib.exitstack",
        "__enter__-__exit__",
        "open"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.contextmanager",
      "title": "contextlib.contextmanager",
      "kind": "term",
      "summary": {
        "ru": "Декоратор для создания контекстного менеджера из генераторной функции с yield.",
        "en": "Decorator that turns a generator function with a yield into a context manager."
      },
      "body": {
        "ru": "Если внутри блока with вылетит исключение, оно будет возбуждено прямо в точке yield — и код после yield просто не выполнится. Поэтому уборку почти всегда оборачивают в try/finally вокруг yield, иначе ресурс останется незакрытым. Полученный объект одноразовый: генератор исчерпывается после первого with, для второго блока функцию нужно вызвать заново.",
        "en": "If the with block raises, the exception is thrown back in at the yield point, so anything after yield is simply skipped. That is why cleanup almost always belongs in a try/finally around the yield — otherwise the resource leaks on errors. The object you get is single-use: the generator is exhausted after one with, so call the function again for a second block."
      },
      "syntax": "@contextlib.contextmanager\ndef my_cm(): ...; yield value; ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager",
      "version": "",
      "section": "Модуль contextlib",
      "subcat": "декоратор",
      "color_group": "module",
      "aliases": [
        "создать свой контекстный менеджер",
        "контекстный менеджер из генератора"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from contextlib import contextmanager",
        "@contextmanager",
        "def greet(name):",
        "    print('enter')",
        "    yield f'hi {name}'",
        "    print('exit')",
        "    with greet('Bob') as msg:",
        "        print(msg) # → hi Bob",
        "        # enter/exit печатаются автоматически",
        "@contextmanager",
        "def tmp_dir(path):",
        "    import os; os.makedirs(path, exist_ok=True)",
        "    yield path  # cleanup можно в finally"
      ],
      "related": [
        "__enter__-__exit__",
        "contextlib.asynccontextmanager",
        "generator-function-yield",
        "contextlib.ContextDecorator"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.exitstack",
      "title": "contextlib.ExitStack",
      "kind": "term",
      "summary": {
        "ru": "Позволяет динамически регистрировать произвольное число контекстных менеджеров и колбэков.",
        "en": "Lets you register any number of context managers and callbacks dynamically."
      },
      "body": {
        "ru": "Берите его, когда число менеджеров известно только в рантайме или часть из них подключается по условию; для фиксированного набора обычный with с перечислением через запятую читается лучше. Выход происходит в обратном порядке регистрации, как у вложенных with. Метод pop_all() снимает всё со стека, не закрывая, — так передают владение ресурсами наружу, когда инициализация прошла успешно.",
        "en": "Reach for it when the number of managers is known only at runtime, or when some are entered conditionally; for a fixed set, a plain with listing them comma-separated reads better. Unwinding happens in reverse registration order, exactly like nested with blocks. Its pop_all() detaches everything without closing it, which is how you hand ownership of successfully initialised resources to the caller."
      },
      "syntax": "with contextlib.ExitStack() as stack:\n    cm = stack.enter_context(some_cm())",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack",
      "version": "3.3",
      "section": "Модуль contextlib",
      "subcat": "динамический",
      "color_group": "module",
      "aliases": [
        "несколько контекстных менеджеров сразу",
        "динамическое число блоков with"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from contextlib import ExitStack",
        "files = ['a.txt', 'b.txt']",
        "with ExitStack() as stack:",
        "    fds = [stack.enter_context(open(f, 'w')) for f in files]",
        "    # все файлы закроются при выходе",
        "    stack2 = ExitStack()",
        "    stack2.callback(print, 'done')",
        "    stack2.close() # → done"
      ],
      "related": [
        "contextlib.AsyncExitStack",
        "contextlib.closing",
        "contextlib.contextmanager",
        "__enter__-__exit__"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.nullcontext",
      "title": "contextlib.nullcontext",
      "kind": "term",
      "summary": {
        "ru": "Контекстный менеджер-заглушка, ничего не делающий. Полезен для условного использования КМ (Python 3.7+).",
        "en": "A do-nothing placeholder context manager. Useful when a context manager is only sometimes needed (Python 3.7+)."
      },
      "body": {
        "ru": "Смысл в том, чтобы не писать один и тот же блок кода дважды в ветках if/else, когда менеджер нужен лишь иногда. Частая ловушка: по умолчанию as-переменная получает None, поэтому если внутри блока ждут объект (например, файл), подставляйте его через enter_result. С Python 3.10 работает и как асинхронный контекстный менеджер.",
        "en": "The point is to avoid duplicating the same block in both branches of an if/else when the manager is only sometimes needed. Common trap: by default the as-variable is None, so if the body expects a real object (a file, say) pass it via enter_result. Since Python 3.10 it also works as an async context manager."
      },
      "syntax": "contextlib.nullcontext(enter_result=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext",
      "version": "3.7",
      "section": "Модуль contextlib",
      "subcat": "заглушка",
      "color_group": "module",
      "aliases": [
        "пустой контекстный менеджер",
        "заглушка вместо контекстного менеджера"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from contextlib import nullcontext",
        "with nullcontext('value') as v:",
        "    print(v) # → 'value'",
        "def process(cm=None):",
        "    with cm or nullcontext():",
        "        return 42",
        "process() # → 42",
        "process(nullcontext()) # → 42"
      ],
      "related": [
        "contextlib.exitstack",
        "contextlib.contextmanager",
        "__enter__-__exit__"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.redirect_stderr",
      "title": "contextlib.redirect_stderr",
      "kind": "term",
      "summary": {
        "ru": "Контекстный менеджер: временно перенаправляет sys.stderr в указанный файловый объект.",
        "en": "A context manager that temporarily redirects sys.stderr to a file object."
      },
      "body": {
        "ru": "Подменяется только sys.stderr на уровне Python, поэтому вывод из C-расширений и из дочерних процессов, пишущих в дескриптор 2 напрямую, вы не поймаете. Изменение глобальное, а значит не потокобезопасное: пока блок активен, перенаправление видят все потоки — для библиотечного кода это плохой выбор.",
        "en": "Only Python-level sys.stderr is swapped, so output from C extensions or child processes writing straight to file descriptor 2 slips past it. The change is global and therefore not thread-safe: while the block is active every thread sees the redirect, which makes it a poor fit for library code."
      },
      "syntax": "with contextlib.redirect_stderr(target): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stderr",
      "version": "3.5",
      "section": "Модуль contextlib",
      "subcat": "перенаправление",
      "color_group": "module",
      "aliases": [
        "перенаправить поток ошибок",
        "перехватить сообщения об ошибках"
      ],
      "keywords": [],
      "tags": [
        "contextlib"
      ],
      "examples": [
        "import contextlib, io, sys",
        "buf = io.StringIO()",
        "with contextlib.redirect_stderr(buf):",
        "    print('err', file=sys.stderr)",
        "print(buf.getvalue().strip())   # → err"
      ],
      "related": [
        "contextlib.redirect_stdout",
        "sys.stdin-sys.stdout-sys.stderr",
        "io.stringio"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.redirect_stdout",
      "title": "contextlib.redirect_stdout",
      "kind": "term",
      "summary": {
        "ru": "Контекстный менеджер: временно перенаправляет sys.stdout в указанный файловый объект.",
        "en": "A context manager that temporarily redirects sys.stdout to a file object."
      },
      "body": {
        "ru": "Подмена глобальная: пока блок with активен, перенаправлен sys.stdout у всей программы, включая другие потоки — поэтому в библиотечном коде и многопоточных приложениях так делать не стоит. На вывод дочерних процессов и кода на C, пишущего прямо в дескриптор 1, это не влияет: перехватывается только то, что идёт через sys.stdout. Для ошибок есть парный redirect_stderr, а типичное применение — тесты и захват вывода чужой функции, которая печатает вместо того, чтобы возвращать значение.",
        "en": "The swap is global: while the with block is active, sys.stdout is redirected for the whole program, other threads included — which is why this is a poor fit for library code and threaded applications. Output from subprocesses or from C code writing straight to file descriptor 1 is unaffected; only writes going through sys.stdout are captured. There is a matching redirect_stderr, and the usual use case is tests or capturing output from someone else's function that prints instead of returning."
      },
      "syntax": "with contextlib.redirect_stdout(target): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout",
      "version": "3.4",
      "section": "Модуль contextlib",
      "subcat": "перенаправление",
      "color_group": "module",
      "aliases": [
        "перенаправить вывод в файл",
        "перехватить вывод программы"
      ],
      "keywords": [],
      "tags": [
        "contextlib"
      ],
      "examples": [
        "import contextlib, io",
        "buf = io.StringIO()",
        "with contextlib.redirect_stdout(buf):",
        "    print('hi')",
        "print(buf.getvalue().strip())   # → hi"
      ],
      "related": [
        "contextlib.redirect_stderr",
        "sys.stdin-sys.stdout-sys.stderr",
        "io.stringio",
        "print"
      ],
      "related_errors": []
    },
    {
      "id": "contextlib.suppress",
      "title": "contextlib.suppress",
      "kind": "term",
      "summary": {
        "ru": "Подавляет указанные исключения. Аналог try/except pass. Читаемее и лаконичнее.",
        "en": "Suppresses the given exceptions. The equivalent of try/except pass, but shorter and easier to read."
      },
      "body": {
        "ru": "Главная ловушка: исключение гасит остаток блока with целиком, а не одну строку — выполнение продолжится уже после with, поэтому не кладите в один блок несколько шагов, если следующие обязаны выполниться. Подавляются и подклассы указанных исключений, так что suppress(Exception) проглотит вообще всё, включая опечатку в имени переменной. Годится только там, где вы точно знаете, что молча продолжить — правильно.",
        "en": "The main trap: the exception skips the rest of the with block, not just one line — execution resumes after the block, so do not stack several steps inside if the later ones must run. Subclasses of the listed exceptions are suppressed too, so suppress(Exception) will swallow everything, including a typo in a variable name. Use it only where silently continuing is provably the right behaviour."
      },
      "syntax": "contextlib.suppress(*exceptions)\nwith suppress(FileNotFoundError):\n    os.remove('file')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/contextlib.html#contextlib.suppress",
      "version": "3.4",
      "section": "Модуль contextlib",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "подавить исключение",
        "проигнорировать ошибку"
      ],
      "keywords": [],
      "tags": [
        "contextlib"
      ],
      "examples": [
        "from contextlib import suppress",
        "with suppress(FileNotFoundError):",
        "    import os",
        "    os.remove('/nonexistent_file')  # → тихо игнорируется",
        "    print('Continued normally')  # → Continued normally",
        "    with suppress(KeyError):",
        "        d = {'a': 1}",
        "        print(d['missing'])  # → подавлено",
        "        print('ok')  # → ok",
        "    with suppress(ZeroDivisionError, ValueError):",
        "        x = 1/0  # → подавлено",
        "# Сравни с try/except pass",
        "import os",
        "try:",
        "    os.remove('no_file')",
        "except FileNotFoundError:",
        "    pass  # эквивалент suppress"
      ],
      "related": [
        "try-except",
        "filenotfounderror",
        "contextlib.contextmanager"
      ],
      "related_errors": []
    },
    {
      "id": "__copy__-__deepcopy__",
      "title": "__copy__() / __deepcopy__()",
      "kind": "function",
      "summary": {
        "ru": "Методы для настройки поведения copy.copy() и copy.deepcopy() в пользовательских классах.",
        "en": "The methods that customize how copy.copy() and copy.deepcopy() behave for your own classes."
      },
      "body": {
        "ru": "Определять их нужно редко: по умолчанию copy восстанавливает объект из его __dict__, и этого хватает почти всегда — руками пишут, когда объект держит несопируемый ресурс (сокет, дескриптор файла, соединение) или кэш, который в копии должен быть пустым. В __deepcopy__ обязательно сначала положите новый объект в memo под ключом id(self), а вложенные поля копируйте как copy.deepcopy(x, memo) — иначе циклические ссылки уйдут в бесконечную рекурсию, а один и тот же общий подобъект скопируется дважды.",
        "en": "You rarely need these: by default copy rebuilds the object from its __dict__, which is enough almost always — hand-written versions appear when the object holds an uncopyable resource (socket, file handle, connection) or a cache that should start empty in the copy. Inside __deepcopy__, register the new object in memo under id(self) before copying anything, and copy nested fields via copy.deepcopy(x, memo) — otherwise cyclic references recurse forever and a shared sub-object gets duplicated instead of staying shared."
      },
      "syntax": "def __copy__(self): ...\ndef __deepcopy__(self, memo): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/copy.html#object.__copy__",
      "version": "",
      "section": "Модуль copy",
      "subcat": "кастомизация",
      "color_group": "module",
      "aliases": [
        "своё копирование объекта",
        "настроить копирование класса"
      ],
      "keywords": [
        "__copy__",
        "__deepcopy__"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import copy",
        "class MyObj:",
        "    def __init__(self, v): self.v = v",
        "    def __copy__(self):",
        "        return MyObj(self.v)",
        "    def __deepcopy__(self, memo):",
        "        return MyObj(copy.deepcopy(self.v, memo))",
        "obj = MyObj([1,2])",
        "c = copy.copy(obj)",
        "c.v is obj.v # → True  (shallow)",
        "d = copy.deepcopy(obj)",
        "d.v is obj.v # → False"
      ],
      "related": [
        "copy.deepcopy",
        "copy.copy",
        "ловушки-копирования"
      ],
      "related_errors": []
    },
    {
      "id": "copy.copy",
      "title": "copy.copy()",
      "kind": "function",
      "summary": {
        "ru": "Создаёт поверхностную копию объекта: новый объект, но вложенные объекты остаются общими.",
        "en": "Creates a shallow copy of an object: the object itself is new, but the nested objects stay shared."
      },
      "body": {
        "ru": "Для встроенных контейнеров это ровно то же, что list(a), a[:], dict(a) или a.copy() — отдельная функция нужна, чтобы копировать что угодно, включая экземпляры своих классов, не зная их типа. Для неизменяемых объектов (int, str, кортеж без изменяемых элементов) copy может вернуть тот же самый объект: копировать нечего. И помните, что копия экземпляра класса создаётся без вызова __init__ — просто переносится содержимое __dict__.",
        "en": "For built-in containers this is exactly list(a), a[:], dict(a) or a.copy() — the function exists so you can copy anything, including instances of your own classes, without knowing the type. For immutables (int, str, a tuple of immutables) copy may hand back the very same object, since there is nothing to copy. Note also that copying a class instance does not call __init__; the contents of __dict__ are transferred directly."
      },
      "syntax": "copy.copy(obj)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/copy.html#copy.copy",
      "version": "",
      "section": "Модуль copy",
      "subcat": "поверхностная",
      "color_group": "module",
      "aliases": [
        "поверхностная копия",
        "копия верхнего уровня объекта"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import copy",
        "a = [1, [2, 3]]",
        "b = copy.copy(a)",
        "b.append(4)",
        "print(a) # → [1, [2, 3]]",
        "b[1].append(99)",
        "print(a) # → [1, [2, 3, 99]]  вложенный общий!",
        "d = {'x': [1, 2]}",
        "d2 = copy.copy(d)",
        "d2['x'] is d['x'] # → True"
      ],
      "related": [
        "copy.deepcopy",
        "list.copy",
        "dict.copy",
        "ловушки-копирования"
      ],
      "related_errors": []
    },
    {
      "id": "copy.deepcopy",
      "title": "copy.deepcopy()",
      "kind": "function",
      "summary": {
        "ru": "Создаёт полностью независимую глубокую копию объекта: все вложенные объекты тоже копируются. В отличие от list.copy() (поверхностная копия), изменение вложенных элементов не влияет на оригинал.",
        "en": "Creates a fully independent deep copy of an object: every nested object is copied too. Unlike list.copy() (a shallow copy), changing a nested item does not affect the original."
      },
      "body": {
        "ru": "Внутри ведётся словарь memo, поэтому циклические ссылки не зацикливаются, а объект, на который в структуре два разных пути, останется одним общим объектом и в копии. Копируется не всё: модули, функции, классы, файлы, сокеты и потоки возвращаются как есть — глубокая копия объекта с открытым соединением даст копию, смотрящую в то же соединение. Рекурсивный обход дорог, и если структура состоит из простых данных, собрать её заново (или прогнать через json) часто быстрее.",
        "en": "A memo dictionary is kept internally, so cycles do not loop forever and an object reachable by two paths stays a single shared object in the copy as well. Not everything is duplicated: modules, functions, classes, files, sockets and threads come back unchanged — deep-copying an object that holds an open connection gives you a copy pointing at the same connection. The recursive walk is expensive, so for plain data structures rebuilding them (or a json round-trip) is often faster."
      },
      "syntax": "import copy\ncopy.deepcopy(obj, memo=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/copy.html#copy.deepcopy",
      "version": "",
      "section": "Модуль copy",
      "subcat": "глубокая",
      "color_group": "module",
      "aliases": [
        "глубокая копия объекта",
        "скопировать вложенные списки",
        "независимая копия"
      ],
      "keywords": [
        "copy.deepcopy"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import copy",
        "a = [[1, 2], [3, 4]]",
        "b = a.copy(); b[0][0] = 99",
        "print(a)  # → [[99, 2], [3, 4]]  (поверхностная: оригинал изменился)",
        "a = [[1, 2], [3, 4]]",
        "c = copy.deepcopy(a); c[0][0] = 99",
        "print(a)  # → [[1, 2], [3, 4]]   (глубокая: оригинал не тронут)",
        "print(c)  # → [[99, 2], [3, 4]]",
        "print(a is c)  # → False",
        "d = {'x': {'y': 1}}",
        "d2 = copy.deepcopy(d)",
        "print(d2['x'] is d['x'])  # → False (вложенный dict тоже скопирован)"
      ],
      "related": [
        "copy.copy",
        "__copy__-__deepcopy__",
        "ловушки-копирования",
        "copy-deepcopy-словаря"
      ],
      "related_errors": []
    },
    {
      "id": "ловушки-копирования",
      "title": "Ловушки копирования",
      "kind": "term",
      "summary": {
        "ru": "Типичные ошибки: mutable default arguments, циклические ссылки, shared state при shallow copy.",
        "en": "The usual mistakes: mutable default arguments, circular references, and shared state left behind by a shallow copy."
      },
      "body": {
        "ru": "Главная развилка: срез lst[:], dict.copy() и copy.copy() копируют только верхний уровень — вложенные списки и словари остаются общими, так что правка внутри копии видна в оригинале. copy.deepcopy() спускается вглубь и не зацикливается на циклических ссылках (он помнит уже скопированные объекты в memo), но платит за это скоростью и памятью, поэтому в горячем цикле от него лучше отказаться. Отдельная ловушка — значение по умолчанию def f(lst=[]): оно вычисляется один раз при определении функции, а не при каждом вызове; лечится через None и создание списка внутри тела.",
        "en": "The key split: a slice lst[:], dict.copy() and copy.copy() duplicate only the top level — nested lists and dicts stay shared, so editing inside the copy shows up in the original. copy.deepcopy() recurses all the way down and survives circular references (it remembers already-copied objects in a memo), but pays in speed and memory, so keep it out of hot loops. The mutable default def f(lst=[]) is a separate trap: it is evaluated once when the function is defined, not per call — use None and build the list inside the body."
      },
      "syntax": "# Используй copy/deepcopy для безопасного копирования мутируемых объектов",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/copy.html",
      "version": "",
      "section": "Модуль copy",
      "subcat": "ловушки",
      "color_group": "module",
      "aliases": [
        "изменение копии меняет оригинал",
        "общие вложенные списки после копирования",
        "изменяемое значение по умолчанию"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import copy",
        "# Mutable default — антипаттерн:",
        "def bad(lst=[]):",
        "    lst.append(1); return lst",
        "bad(); bad() # → [1, 1]  один объект!",
        "# Циклическая ссылка:",
        "a = []",
        "a.append(a)",
        "b = copy.deepcopy(a)  # deepcopy справляется",
        "b is a # → False",
        "# Shallow: одинаковые вложенные объекты",
        "x = [[0]*3]*3",
        "y = copy.copy(x)",
        "y[0][0] = 9",
        "print(x) # → [[9,0,0],[9,0,0],[9,0,0]]"
      ],
      "related": [
        "copy.copy",
        "copy.deepcopy",
        "параметры-по-умолчанию",
        "list.copy"
      ],
      "related_errors": []
    },
    {
      "id": "ctypes.ArgumentError",
      "title": "ctypes.ArgumentError",
      "kind": "exception",
      "summary": {
        "ru": "Исключение ctypes: вызов внешней функции не смог преобразовать один из переданных аргументов; наследуется прямо от Exception, а не от TypeError.",
        "en": "A ctypes exception raised when a foreign function call cannot convert one of the passed arguments; it inherits directly from Exception, not from TypeError."
      },
      "body": {
        "ru": "Ключевая неожиданность — класс наследуется прямо от Exception, поэтому except TypeError вокруг вызова C-функции его не поймает; перехватывайте ctypes.ArgumentError по имени. Возникает он на преобразовании конкретного аргумента, и его номер есть в тексте сообщения — читайте сообщение, а не гадайте. И наоборот: если у функции не заданы argtypes, проверять нечего, и вместо аккуратного ArgumentError вы рискуете получить порчу памяти или падение процесса.",
        "en": "The surprise is that it inherits straight from Exception, so an except TypeError around your foreign call will not catch it — name ctypes.ArgumentError explicitly. It fires while converting one specific argument, and the message tells you which position failed, so read it instead of guessing. The flip side: with no argtypes declared there is nothing to check, and instead of a clean ArgumentError you risk memory corruption or a hard crash of the process."
      },
      "syntax": "ctypes.ArgumentError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/ctypes.html#ctypes.ArgumentError",
      "version": "",
      "section": "Модуль ctypes",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "ошибка преобразования аргумента",
        "ошибка аргумента внешней функции"
      ],
      "keywords": [
        "ctypes.ArgumentError",
        "ArgumentError"
      ],
      "tags": [
        "ctypes"
      ],
      "examples": [
        "import ctypes",
        "print(issubclass(ctypes.ArgumentError, Exception))  # → True",
        "try:",
        "    raise ctypes.ArgumentError('argument 1: TypeError')",
        "except ctypes.ArgumentError as e:",
        "    print(e)  # → argument 1: TypeError"
      ],
      "related": [
        "typeerror",
        "valueerror",
        "exception",
        "struct.error"
      ],
      "related_errors": []
    },
    {
      "id": "__post_init__",
      "title": "__post_init__",
      "kind": "term",
      "summary": {
        "ru": "Метод __post_init__ вызывается автоматически после __init__ для дополнительной валидации или обработки.",
        "en": "The __post_init__ method is called automatically after __init__, for extra validation or processing."
      },
      "body": {
        "ru": "Вызывается только тем __init__, который сгенерировал сам dataclass: с init=False или собственноручным __init__ его никто не позовёт. Под frozen=True обычное присваивание self.x = ... там падает с FrozenInstanceError, и приходится писать object.__setattr__(self, \"x\", ...). Сюда же приходят поля-InitVar — они не становятся атрибутами, а передаются в __post_init__ как аргументы, что и есть штатный способ протащить данные, нужные только для инициализации.",
        "en": "It is called only by the __init__ that the dataclass itself generated: with init=False, or with a hand-written __init__, nothing invokes it. Under frozen=True a plain self.x = ... raises FrozenInstanceError there, so you have to write object.__setattr__(self, \"x\", ...). InitVar fields land here too — they never become attributes, they are passed to __post_init__ as arguments, which is the sanctioned way to feed in data needed only during construction."
      },
      "syntax": "def __post_init__(self): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.__post_init__",
      "version": "",
      "section": "Модуль dataclasses",
      "subcat": "инициализация",
      "color_group": "module",
      "aliases": [
        "валидация полей датакласса",
        "действия после создания объекта",
        "вычисляемое поле датакласса"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from dataclasses import dataclass",
        "@dataclass",
        "class Circle:",
        "radius: float",
        "def __post_init__(self):",
        "if self.radius < 0:",
        "raise ValueError('radius < 0')",
        "self.area = 3.14 * self.radius ** 2",
        "c = Circle(3.0)",
        "c.area # → 28.26",
        "# Circle(-1) # → ValueError"
      ],
      "related": [
        "dataclass",
        "dataclasses.InitVar",
        "__init__",
        "field"
      ],
      "related_errors": []
    },
    {
      "id": "dataclass",
      "title": "@dataclass",
      "kind": "term",
      "summary": {
        "ru": "@dataclass автоматически генерирует __init__, __repr__, __eq__. field() настраивает отдельные поля.",
        "en": "@dataclass generates __init__, __repr__ and __eq__ automatically. field() tunes individual fields."
      },
      "body": {
        "ru": "Поля со значением по умолчанию обязаны идти после полей без него — иначе TypeError ещё на этапе создания класса. Изменяемый дефолт вроде x: list = [] отвергается сразу с ValueError: нужен field(default_factory=list), иначе список был бы общим для всех экземпляров. Аннотации типов здесь лишь разметка, во время выполнения их никто не проверяет: Point(\"a\", \"b\") создастся спокойно.",
        "en": "Fields with defaults must come after fields without them, otherwise the class raises TypeError at definition time. A mutable default like x: list = [] is rejected outright with ValueError — you need field(default_factory=list), or the list would be shared by every instance. The type annotations are only markup: nothing checks them at runtime, so Point(\"a\", \"b\") is built without complaint."
      },
      "syntax": "from dataclasses import dataclass\n@dataclass\nclass Point:\n    x: float\n    y: float",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass",
      "version": "3.10",
      "section": "Модуль dataclasses",
      "subcat": "базовый",
      "color_group": "module",
      "aliases": [
        "класс для хранения данных",
        "класс с автоматическим конструктором"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from dataclasses import dataclass",
        "@dataclass",
        "class Point:",
        "x: float",
        "y: float",
        "p = Point(1.0, 2.0)",
        "print(p)  # → Point(x=1.0, y=2.0)",
        "print(p.x, p.y)  # → 1.0 2.0",
        "# __eq__ автоматически",
        "print(Point(1,2) == Point(1,2))  # → True",
        "print(Point(1,2) == Point(1,3))  # → False",
        "from dataclasses import dataclass, field",
        "@dataclass",
        "class Inventory:",
        "name: str",
        "items: list = field(default_factory=list)",
        "count: int = field(default=0, repr=False)",
        "inv = Inventory('store')",
        "inv.items.append('apple')",
        "print(inv)  # → Inventory(name='store', items=['apple'])",
        "# frozen=True — неизменяемый",
        "@dataclass(frozen=True)",
        "class ImmutablePoint:",
        "x: float",
        "y: float",
        "ip = ImmutablePoint(1,2)",
        "try:",
        "ip.x = 5  # → FrozenInstanceError",
        "except Exception as e:",
        "print(e)",
        "# __post_init__",
        "@dataclass",
        "class Circle:",
        "radius: float",
        "area: float = field(init=False)",
        "def __post_init__(self):",
        "import math",
        "self.area = math.pi * self.radius**2",
        "c = Circle(5)",
        "print(round(c.area, 2))  # → 78.54",
        "# order=True — для сравнения",
        "@dataclass(order=True)",
        "class Student:",
        "grade: int",
        "name: str",
        "students = [Student(85,'Bob'), Student(90,'Alice')]",
        "print(sorted(students))  # → сортировка по grade",
        "# Наследование dataclass",
        "@dataclass",
        "class Animal:",
        "name: str",
        "sound: str",
        "@dataclass",
        "class Dog(Animal):",
        "breed: str",
        "d = Dog('Rex', 'Woof', 'Lab')",
        "print(d)  # → Dog(name='Rex', sound='Woof', breed='Lab')"
      ],
      "related": [
        "field",
        "dataclass-frozen-true",
        "__post_init__",
        "namedtuple"
      ],
      "related_errors": []
    },
    {
      "id": "dataclass-frozen-true",
      "title": "@dataclass(frozen=True)",
      "kind": "function",
      "summary": {
        "ru": "frozen=True делает экземпляры неизменяемыми (аналог namedtuple) и хэшируемыми.",
        "en": "frozen=True makes the instances immutable (like a namedtuple) and hashable."
      },
      "body": {
        "ru": "Заморозка поверхностная: сам атрибут переприсвоить нельзя (FrozenInstanceError), но если внутри лежит список, его по-прежнему можно пополнять — и хэш такого объекта сломается. Хэшируемость появляется именно из-за связки frozen=True и eq=True (значение по умолчанию); при обычном eq=True без заморозки dataclass выставляет __hash__ = None, и объект перестаёт годиться в ключи словаря. Менять значения удобно не присваиванием, а dataclasses.replace(), который возвращает новый экземпляр.",
        "en": "The freeze is shallow: you cannot rebind an attribute (FrozenInstanceError), but a list stored inside can still be appended to — and the object's hash breaks with it. Hashability comes specifically from frozen=True together with eq=True (the default); with plain eq=True and no freeze, dataclass sets __hash__ = None and the object stops working as a dict key. To change a value, use dataclasses.replace(), which returns a fresh instance, rather than assignment."
      },
      "syntax": "@dataclass(frozen=True)\nclass Pt:\n    x: int; y: int",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#frozen-instances",
      "version": "",
      "section": "Модуль dataclasses",
      "subcat": "базовый",
      "color_group": "module",
      "aliases": [
        "неизменяемый датакласс",
        "запретить менять поля объекта",
        "хэшируемый датакласс"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from dataclasses import dataclass",
        "@dataclass(frozen=True)",
        "class Pt:",
        "    x: int; y: int",
        "    p = Pt(1, 2)",
        "    p.x # → 1",
        "    hash(p) # → работает (объект хэшируем)",
        "    # p.x = 5  # → FrozenInstanceError",
        "    d = {p: 'origin'}",
        "    d[Pt(1, 2)] # → 'origin'"
      ],
      "related": [
        "dataclass",
        "dataclasses.replace",
        "dataclass-order-true",
        "namedtuple"
      ],
      "related_errors": [
        "AttributeError"
      ]
    },
    {
      "id": "dataclass-order-true",
      "title": "@dataclass(order=True)",
      "kind": "function",
      "summary": {
        "ru": "order=True добавляет методы __lt__, __le__, __gt__, __ge__ на основе кортежа полей.",
        "en": "order=True adds the __lt__, __le__, __gt__ and __ge__ methods, comparing the fields as a tuple."
      },
      "body": {
        "ru": "Сравнение идёт по всем полям в порядке объявления, как по кортежу, — выбрать ключ на лету нельзя, поэтому порядок полей в классе фактически задаёт порядок сортировки; лишние поля исключаются через field(compare=False). Сочетание order=True с eq=False запрещено и падает с ValueError. Сравнение с объектом другого класса возвращает NotImplemented, то есть на выходе получается TypeError, а не False.",
        "en": "Comparison walks all fields in declaration order, as if they were a tuple — you cannot pick a key on the fly, so the field order in the class effectively defines the sort order; exclude unwanted fields with field(compare=False). Combining order=True with eq=False is forbidden and raises ValueError. Comparing against a different class returns NotImplemented, which surfaces as a TypeError rather than a quiet False."
      },
      "syntax": "@dataclass(order=True)\nclass X:\n    a: int; b: int",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass",
      "version": "3.10",
      "section": "Модуль dataclasses",
      "subcat": "базовый",
      "color_group": "module",
      "aliases": [
        "сортировка объектов датакласса",
        "сравнивать датаклассы по полям"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from dataclasses import dataclass",
        "@dataclass(order=True)",
        "class Version:",
        "major: int",
        "minor: int",
        "v1 = Version(1, 0)",
        "v2 = Version(2, 0)",
        "v1 < v2 # → True",
        "sorted([Version(2,0), Version(1,5)]) # → [Version(1,5), Version(2,0)]"
      ],
      "related": [
        "dataclass",
        "dataclass-frozen-true",
        "functools.total_ordering"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "dataclasses.InitVar",
      "title": "dataclasses.InitVar",
      "kind": "term",
      "summary": {
        "ru": "Псевдо-тип поля: значение передаётся в __init__/__post_init__, но НЕ становится атрибутом экземпляра.",
        "en": "A pseudo-type for a field passed to __init__/__post_init__ but not stored as an attribute."
      },
      "body": {
        "ru": "Это способ принять в конструктор данные, которые нужны только для вычисления настоящих полей и не должны оставаться в объекте: после __init__ такого атрибута у экземпляра просто нет. Отсюда типичный сюрприз — InitVar-поле не видно ни в repr, ни в сравнении, ни в fields(), ни в asdict(). И если забыть написать __post_init__, значение будет принято и молча выброшено; в __post_init__ такие параметры приходят позиционно, в порядке объявления в классе.",
        "en": "InitVar exists for input that is only needed to compute the real fields and should not live on the instance: once __init__ finishes, no such attribute exists. Hence the usual surprise — an InitVar never shows up in repr, equality, fields() or asdict(). Forget to define __post_init__ and the value is accepted and silently discarded; inside __post_init__ these parameters arrive positionally, in declaration order."
      },
      "syntax": "field: dataclasses.InitVar[T]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.InitVar",
      "version": "",
      "section": "Модуль dataclasses",
      "subcat": "поля",
      "color_group": "module",
      "aliases": [
        "поле датакласса только для инициализации",
        "параметр конструктора без атрибута"
      ],
      "keywords": [],
      "tags": [
        "dataclasses"
      ],
      "examples": [
        "import dataclasses",
        "@dataclasses.dataclass",
        "class C:",
        "    x: int",
        "    add: dataclasses.InitVar[int]",
        "    def __post_init__(self, add):",
        "        self.x += add",
        "print(C(10, 5).x)   # → 15"
      ],
      "related": [
        "__post_init__",
        "field",
        "dataclass"
      ],
      "related_errors": []
    },
    {
      "id": "dataclasses.asdict",
      "title": "dataclasses.asdict",
      "kind": "function",
      "summary": {
        "ru": "Рекурсивно превращает экземпляр dataclass (и вложенные) в словарь.",
        "en": "Recursively convert a dataclass instance into a dict."
      },
      "body": {
        "ru": "Это не дешёвый снимок __dict__: asdict рекурсивно обходит вложенные датаклассы, списки, кортежи и словари, а всё остальное копирует через copy.deepcopy — на больших структурах это заметно дороже, чем кажется. Результат ещё не готов к json.dumps: datetime, Decimal, set и прочие объекты остаются собой, а поля ClassVar и InitVar в словарь не попадают. Если нужна просто плоская копия атрибутов, дешевле взять vars(obj).",
        "en": "This is not a cheap view of __dict__: asdict walks nested dataclasses, lists, tuples and dicts recursively and deep-copies everything else, which costs real time on large structures. The result is not automatically JSON-ready either — datetime, Decimal, set and friends come back as themselves, and ClassVar/InitVar fields are excluded. When a flat copy of the attributes is enough, vars(obj) is far cheaper."
      },
      "syntax": "dataclasses.asdict(instance)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.asdict",
      "version": "",
      "section": "Модуль dataclasses",
      "subcat": "конвертация",
      "color_group": "module",
      "aliases": [
        "датакласс в словарь",
        "преобразовать объект в словарь",
        "сериализовать датакласс"
      ],
      "keywords": [],
      "tags": [
        "dataclasses"
      ],
      "examples": [
        "import dataclasses",
        "@dataclasses.dataclass",
        "class P:",
        "    x: int",
        "    y: int",
        "print(dataclasses.asdict(P(1, 2)))   # → {'x': 1, 'y': 2}"
      ],
      "related": [
        "dataclasses.astuple",
        "dataclass",
        "json.dumps",
        "dataclasses.fields"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "dataclasses.astuple",
      "title": "dataclasses.astuple",
      "kind": "function",
      "summary": {
        "ru": "Рекурсивно превращает экземпляр dataclass в кортеж значений полей.",
        "en": "Recursively convert a dataclass instance into a tuple of field values."
      },
      "body": {
        "ru": "Кортеж теряет имена, поэтому код, который его распаковывает, ломается молча, стоит переставить или добавить поле в классе — для сериализации и хранения надёжнее asdict. Как и asdict, функция рекурсивная и копирует значения через deepcopy, так что вложенный датакласс станет вложенным кортежем, а не ссылкой на исходный объект. Уместно там, где нужен именно короткий позиционный набор: распаковка, ключ сортировки, передача в функцию через *.",
        "en": "A tuple drops the field names, so any code unpacking it breaks silently the moment fields are reordered or inserted — for serialization and storage asdict is the safer choice. Like asdict it recurses and deep-copies, so a nested dataclass becomes a nested tuple rather than a reference to the original object. Reach for it when you genuinely want a short positional bundle: unpacking, a sort key, or splatting into a call."
      },
      "syntax": "dataclasses.astuple(instance)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.astuple",
      "version": "",
      "section": "Модуль dataclasses",
      "subcat": "конвертация",
      "color_group": "module",
      "aliases": [
        "датакласс в кортеж",
        "значения полей датакласса кортежем"
      ],
      "keywords": [],
      "tags": [
        "dataclasses"
      ],
      "examples": [
        "import dataclasses",
        "@dataclasses.dataclass",
        "class P:",
        "    x: int",
        "    y: int",
        "print(dataclasses.astuple(P(1, 2)))   # → (1, 2)"
      ],
      "related": [
        "dataclasses.asdict",
        "dataclass",
        "collections.namedtuple"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "dataclasses.fields",
      "title": "dataclasses.fields()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает кортеж объектов Field с метаданными каждого поля датакласса.",
        "en": "Returns a tuple of Field objects holding the metadata of every field of the dataclass."
      },
      "body": {
        "ru": "Это единственный поддерживаемый способ пройтись по полям датакласса — именно на нём строят валидацию, сериализацию и сравнение «по полям» в своём коде. Возвращаются только настоящие поля: псевдополя InitVar и аннотации ClassVar в кортеж не попадают. Два подвоха: f.type — это аннотация как она записана, то есть строка, если в модуле есть from __future__ import annotations; а отсутствие значения по умолчанию обозначается сентинелом dataclasses.MISSING, а не None.",
        "en": "This is the supported way to iterate over a dataclass's fields, and it is what custom validation, serialization and field-wise comparison are usually built on. Only real fields are returned: InitVar pseudo-fields and ClassVar annotations are left out. Two catches: f.type is the annotation exactly as written, so it is a string when the module uses from __future__ import annotations, and a missing default is reported by the dataclasses.MISSING sentinel rather than None."
      },
      "syntax": "dataclasses.fields(class_or_instance)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.fields",
      "version": "",
      "section": "Модуль dataclasses",
      "subcat": "field",
      "color_group": "module",
      "aliases": [
        "список полей датакласса",
        "перебрать поля датакласса"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from dataclasses import dataclass, fields",
        "@dataclass",
        "class Pt:",
        "x: int; y: int = 0",
        "for f in fields(Pt):",
        "print(f.name, f.type) # x int, y int",
        "fields(Pt)[0].name # → 'x'",
        "fields(Pt)[1].default # → 0"
      ],
      "related": [
        "field",
        "dataclass",
        "dataclasses.asdict",
        "dataclasses.is_dataclass"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "dataclasses.is_dataclass",
      "title": "dataclasses.is_dataclass",
      "kind": "function",
      "summary": {
        "ru": "True, если объект (класс или экземпляр) является dataclass.",
        "en": "True if the object (class or instance) is a dataclass."
      },
      "body": {
        "ru": "Главная ловушка — True возвращается и для класса, и для его экземпляра, поэтому для проверки «передали именно объект» нужно дополнительно убедиться, что это не тип (isinstance(obj, type)). Проверка нужна перед asdict/astuple/fields: на не-датаклассе они бросают TypeError, а не возвращают пустой результат. Наследники датакласса тоже считаются датаклассами, даже если сами декоратором не помечены.",
        "en": "The main trap: it returns True both for a dataclass and for its instances, so to test \"this is an actual object\" you must additionally check that it is not a type (isinstance(obj, type)). Use it as a guard before asdict/astuple/fields, which raise TypeError on non-dataclasses instead of returning something empty. Subclasses of a dataclass also count as dataclasses even when they carry no decorator of their own."
      },
      "syntax": "dataclasses.is_dataclass(obj)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.is_dataclass",
      "version": "",
      "section": "Модуль dataclasses",
      "subcat": "интроспекция",
      "color_group": "module",
      "aliases": [
        "проверить, датакласс ли объект",
        "определить датакласс во время выполнения"
      ],
      "keywords": [],
      "tags": [
        "dataclasses"
      ],
      "examples": [
        "import dataclasses",
        "@dataclasses.dataclass",
        "class P:",
        "    x: int",
        "print(dataclasses.is_dataclass(P))   # → True",
        "print(dataclasses.is_dataclass(42))  # → False"
      ],
      "related": [
        "dataclass",
        "dataclasses.fields",
        "dataclasses.asdict"
      ],
      "related_errors": []
    },
    {
      "id": "dataclasses.make_dataclass",
      "title": "dataclasses.make_dataclass",
      "kind": "function",
      "summary": {
        "ru": "Создаёт новый класс-dataclass динамически из имени и списка полей.",
        "en": "Create a new dataclass dynamically from a name and a list of fields."
      },
      "body": {
        "ru": "Нужен редко: если поля известны заранее, обычный декоратор @dataclass читается лучше и виден статическим анализаторам, а make_dataclass строит класс во время выполнения, поэтому IDE о его полях ничего не знает. Элементом списка полей может быть не только имя, но и пара (имя, тип) или тройка с field(); у голого имени тип считается typing.Any. Чтобы экземпляры такого класса пиклились, присвойте результат переменной уровня модуля с тем же именем, что передали первым аргументом.",
        "en": "Rarely the right tool: when the fields are known up front, a plain @dataclass decorator reads better and is visible to type checkers, while make_dataclass builds the class at runtime so your IDE knows nothing about its fields. A field entry can be a bare name, a (name, type) pair, or a triple with field(); a bare name gets typing.Any. For instances to be picklable, bind the result to a module-level variable whose name matches the first argument."
      },
      "syntax": "dataclasses.make_dataclass(name, fields)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.make_dataclass",
      "version": "3.14",
      "section": "Модуль dataclasses",
      "subcat": "фабрика",
      "color_group": "module",
      "aliases": [
        "создать датакласс динамически",
        "датакласс из списка полей"
      ],
      "keywords": [],
      "tags": [
        "dataclasses"
      ],
      "examples": [
        "import dataclasses",
        "Pt = dataclasses.make_dataclass('Pt', ['x', 'y'])",
        "print(dataclasses.asdict(Pt(1, 2)))   # → {'x': 1, 'y': 2}",
        "print(Pt(1, 2))   # → Pt(x=1, y=2)",
        "print(Pt(1, 2) == Pt(1, 2))   # → True",
        "P2 = dataclasses.make_dataclass('P2', [('x', int), ('y', int, dataclasses.field(default=0))])",
        "print(P2(5))   # → P2(x=5, y=0)",
        "print([f.name for f in dataclasses.fields(P2)])   # → ['x', 'y']",
        "Fz = dataclasses.make_dataclass('Fz', ['v'], frozen=True)",
        "Fz(1).v = 2   # → FrozenInstanceError"
      ],
      "related": [
        "dataclass",
        "collections.namedtuple",
        "dataclasses.is_dataclass"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "dataclasses.replace",
      "title": "dataclasses.replace()",
      "kind": "function",
      "summary": {
        "ru": "Создаёт новый экземпляр датакласса на основе существующего, заменяя указанные поля.",
        "en": "Creates a new dataclass instance from an existing one, replacing the fields given."
      },
      "body": {
        "ru": "replace() заново вызывает __init__ вместе с __post_init__, а не копирует объект побайтно: поля, объявленные с init=False, передать в него нельзя, и их значения не переносятся, а вычисляются заново. Копия поверхностная — вложенные списки и словари остаются общими с исходным объектом. Для frozen-датаклассов это штатный способ «изменить» поле, поэтому такие классы обычно и не нуждаются в сеттерах.",
        "en": "replace() re-runs __init__ (and __post_init__) rather than copying the object field by field: fields declared with init=False cannot be passed and are recomputed instead of carried over. The copy is shallow, so nested lists and dicts stay shared with the original. For frozen dataclasses this is the sanctioned way to \"change\" a field, which is why such classes rarely need setters."
      },
      "syntax": "dataclasses.replace(obj, **changes)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.replace",
      "version": "",
      "section": "Модуль dataclasses",
      "subcat": "копирование",
      "color_group": "module",
      "aliases": [
        "копия датакласса с изменённым полем",
        "изменить поле замороженного датакласса"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from dataclasses import dataclass, replace",
        "@dataclass",
        "class Point:",
        "x: int; y: int",
        "p = Point(1, 2)",
        "p2 = replace(p, x=10)",
        "p2 # → Point(x=10, y=2)",
        "p # → Point(x=1, y=2)",
        "replace(p, x=0, y=0) # → Point(x=0, y=0)"
      ],
      "related": [
        "dataclass-frozen-true",
        "dataclass",
        "copy.copy"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "field",
      "title": "field()",
      "kind": "function",
      "summary": {
        "ru": "Функция field() задаёт дополнительные параметры поля: значение по умолчанию, фабрику, участие в repr/compare/init.",
        "en": "The field() function sets extra properties of a field: its default value, its factory, and whether it takes part in repr/compare/init."
      },
      "body": {
        "ru": "Главная причина существования field() — изменяемые значения по умолчанию: одно и то же значение из тела класса разделили бы все экземпляры, поэтому dataclass падает с ValueError на нехешируемом default и требует default_factory. Ловушка в том, что проверка ловит именно нехешируемое: собственный изменяемый класс с обычным __hash__ проскочит через default= и молча станет общим для всех объектов. Флаги compare=False и repr=False удобны для служебных полей вроде кеша или id, которые не должны влиять на сравнение и вывод.",
        "en": "field() exists mainly because of mutable defaults: a value written in the class body would be shared by every instance, so dataclass raises ValueError on an unhashable default and demands default_factory. The catch is that the check keys on hashability — your own mutable class with a normal __hash__ slips through as default= and is silently shared by all instances. compare=False and repr=False are handy for bookkeeping fields such as caches or ids that should not affect equality or output."
      },
      "syntax": "dataclasses.field(*, default=MISSING, default_factory=MISSING, repr=True, hash=None, compare=True, init=True)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/dataclasses.html#dataclasses.field",
      "version": "3.10",
      "section": "Модуль dataclasses",
      "subcat": "field",
      "color_group": "module",
      "aliases": [
        "поле датакласса",
        "значение по умолчанию поля датакласса",
        "фабрика значений по умолчанию"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from dataclasses import dataclass, field",
        "@dataclass",
        "class Bag:",
        "items: list = field(default_factory=list)",
        "_id: int = field(default=0, repr=False, compare=False)",
        "b = Bag()",
        "b.items.append(1)",
        "repr(b) # → 'Bag(items=[1])'",
        "Bag() == Bag() # → True"
      ],
      "related": [
        "dataclass",
        "dataclasses.fields",
        "__post_init__",
        "параметры-по-умолчанию"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "date-арифметика",
      "title": "date арифметика",
      "kind": "term",
      "summary": {
        "ru": "Вычитание дат даёт timedelta. Прибавление timedelta даёт новую дату.",
        "en": "Subtracting one date from another gives a timedelta. Adding a timedelta gives a new date."
      },
      "body": {
        "ru": "У timedelta нет месяцев и лет — только дни, секунды и микросекунды, поэтому «прибавить месяц» так не выражается: считайте через calendar.monthrange() и replace() или берите стороннюю библиотеку. К date прибавляется только timedelta, но не число: date + 1 даёт TypeError. Разность двух дат — всегда целое число суток в diff.days, без сюрпризов с часовыми поясами и переводом времени, которые бывают у datetime.",
        "en": "timedelta has no months or years — only days, seconds and microseconds — so \"add one month\" simply cannot be written this way; compute it with calendar.monthrange() and replace(), or reach for a third-party library. A date accepts only a timedelta, never a plain number: date + 1 raises TypeError. The difference between two dates is always a whole number of days in diff.days, with none of the timezone or DST surprises that datetime arithmetic can bring."
      },
      "syntax": "d1 - d2\nd + timedelta(days=n)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#date-objects",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "date",
      "color_group": "module",
      "aliases": [
        "разница между датами",
        "прибавить дни к дате",
        "вычитание дат"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import date, timedelta",
        "d1 = date(2024, 12, 31)",
        "d2 = date(2024, 1, 1)",
        "diff = d1 - d2",
        "print(diff.days)  # → 365",
        "d3 = date.today() + timedelta(days=7)",
        "print(d3)  # → через неделю",
        "print(date(2024,3,1) > date(2024,1,1))  # → True",
        "print(date.fromisoformat('2024-12-25') - date.today())"
      ],
      "related": [
        "timedelta",
        "дни-между-датами",
        "datetime.date"
      ],
      "related_errors": []
    },
    {
      "id": "date.fromisoformat",
      "title": "date.fromisoformat()",
      "kind": "function",
      "summary": {
        "ru": "Разбирает строку формата ISO 8601 ('ГГГГ-ММ-ДД') в объект date. Обратная операция к .isoformat(); при неверном формате — ValueError.",
        "en": "Parse an ISO 8601 string ('YYYY-MM-DD') into a date object — the inverse of .isoformat(); raises ValueError on a malformed string."
      },
      "body": {
        "ru": "Это не универсальный парсер дат: строку вида '15.03.2024' или '03/15/2024' он не поймёт и бросит ValueError — для произвольных форматов нужен datetime.strptime с шаблоном. До Python 3.11 метод принимал только точный вид ГГГГ-ММ-ДД, с 3.11 понимает большинство ISO-форматов, включая '20240315' и недельные даты вроде '2024-W11-5'.",
        "en": "It is not a general-purpose date parser: '15.03.2024' or '03/15/2024' raise ValueError — for arbitrary layouts use datetime.strptime with a format string. Before Python 3.11 it accepted only the exact YYYY-MM-DD spelling; since 3.11 it handles most ISO forms, including '20240315' and week dates such as '2024-W11-5'."
      },
      "syntax": "from datetime import date\ndate.fromisoformat('2024-01-01')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.date.fromisoformat",
      "version": "3.7",
      "section": "Модуль datetime",
      "subcat": "date",
      "color_group": "module",
      "aliases": [
        "дата из строки в формате ИСО",
        "разбор даты ГГГГ-ММ-ДД",
        "получить дату из текста"
      ],
      "keywords": [
        "date.fromisoformat",
        "fromisoformat"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import date",
        "d = date.fromisoformat('2024-03-15')",
        "print(d)  # → 2024-03-15",
        "print(d.isoformat())  # → 2024-03-15 (обратно в строку)"
      ],
      "related": [
        "date.today"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "date.today",
      "title": "date.today()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает сегодняшнюю дату как объект date (без времени). Классовый метод — вызывается на самом классе date.",
        "en": "Return today's date as a date object (no time part); a classmethod called on the date class itself."
      },
      "body": {
        "ru": "Дата берётся из локальных часов машины, а не из UTC — на сервере в другом поясе около полуночи вы получите вчерашний или завтрашний день; если нужна дата по UTC, берите datetime.now(timezone.utc).date(). Полученный date нельзя сравнивать с datetime: равенство всегда даёт False, а < и > бросают TypeError, так что сначала приводите второй объект через .date().",
        "en": "The value comes from the machine's local clock, not UTC, so near midnight a server in another timezone reports yesterday or tomorrow; use datetime.now(timezone.utc).date() when you need the UTC day. A date never compares equal to a datetime — equality is always False and ordering raises TypeError — so convert the other object with .date() first."
      },
      "syntax": "from datetime import date\ndate.today()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.date.today",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "date",
      "color_group": "module",
      "aliases": [
        "сегодняшняя дата",
        "какое сегодня число",
        "получить дату сегодня"
      ],
      "keywords": [
        "date.today",
        "today"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import date",
        "today = date.today()",
        "print(today)  # → 2024-xx-xx",
        "print(today.year, today.month, today.day)"
      ],
      "related": [
        "date.fromisoformat",
        "datetime.now"
      ],
      "related_errors": []
    },
    {
      "id": "datetime.date",
      "title": "datetime.date",
      "kind": "term",
      "summary": {
        "ru": "Календарная дата (год, месяц, день) без времени. date.today() — сегодня, weekday() — день недели (0=пн).",
        "en": "A calendar date (year, month, day) without time."
      },
      "body": {
        "ru": "Вечный источник путаницы — нумерация дней недели: weekday() считает с нуля от понедельника, а isoweekday() — с единицы. Несуществующая дата вроде date(2024, 2, 31) роняет ValueError прямо в конструкторе, так что валидировать вручную не нужно, достаточно поймать исключение. Объект неизменяем и «плюс месяц» не умеет: складывать можно только с timedelta, то есть в днях и неделях.",
        "en": "Weekday numbering trips everyone up: weekday() counts from 0 for Monday, while isoweekday() counts from 1. An impossible date such as date(2024, 2, 31) raises ValueError right in the constructor, so there is no need to validate by hand — just catch it. The object is immutable and has no notion of \"plus one month\": you can only add a timedelta, that is, days and weeks."
      },
      "syntax": "datetime.date(year, month, day)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.date",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "дата",
      "color_group": "module",
      "aliases": [
        "календарная дата",
        "год месяц день",
        "дата без времени"
      ],
      "keywords": [],
      "tags": [
        "datetime"
      ],
      "examples": [
        "import datetime",
        "d = datetime.date(2024, 1, 15)",
        "print(d)              # → 2024-01-15",
        "print(d.weekday())    # → 0",
        "print(d.year, d.month)  # → 2024 1"
      ],
      "related": [
        "datetime.datetime",
        "date.today",
        "date-арифметика",
        "dt-weekday"
      ],
      "related_errors": []
    },
    {
      "id": "datetime.datetime",
      "title": "datetime.datetime",
      "kind": "term",
      "summary": {
        "ru": "Момент времени: дата + время. datetime.now() — текущий; поддерживает арифметику с timedelta и strftime().",
        "en": "A moment in time: date plus time; supports arithmetic with timedelta and strftime()."
      },
      "body": {
        "ru": "datetime.now() возвращает naive-объект без часового пояса, а вычитать или сравнивать naive и aware Python отказывается — отсюда неожиданный TypeError. Для UTC пишут datetime.now(timezone.utc): парный utcnow() с версии 3.12 объявлен устаревшим. Объект неизменяемый, поэтому поправить одно поле можно только через replace(), который отдаёт новую копию.",
        "en": "datetime.now() hands back a naive object with no tzinfo, and Python refuses to subtract or compare naive and aware values — that is the surprise TypeError. For UTC use datetime.now(timezone.utc); the old utcnow() has been deprecated since Python 3.12. Instances are immutable, so changing one field means building a copy with replace()."
      },
      "syntax": "datetime.datetime(year, month, day, hour=0, minute=0, second=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "дата-время",
      "color_group": "module",
      "aliases": [
        "дата и время вместе",
        "момент времени"
      ],
      "keywords": [],
      "tags": [
        "datetime"
      ],
      "examples": [
        "import datetime",
        "dt = datetime.datetime(2024, 1, 15, 10, 30)",
        "print(dt)                     # → 2024-01-15 10:30:00",
        "print(dt.strftime('%d.%m.%Y'))  # → 15.01.2024"
      ],
      "related": [
        "datetime.date",
        "datetime.now",
        "timedelta",
        "datetime.strftime"
      ],
      "related_errors": []
    },
    {
      "id": "datetime.now",
      "title": "datetime.now()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает текущие дату и время. Без аргумента — в локальном поясе и без tzinfo (naive); с datetime.now(timezone.utc) — осознанный объект в UTC.",
        "en": "Return the current date and time: naive local time by default, or an aware UTC object when called as datetime.now(timezone.utc)."
      },
      "body": {
        "ru": "Naive-объект (без пояса) нельзя вычесть из aware или сравнить с ним — Python бросит TypeError: can't subtract offset-naive and offset-aware datetimes, поэтому в пределах программы держитесь чего-то одного. И для измерения длительности now() не годится: системные часы подкручивает NTP и переводы времени, интервал считайте по time.perf_counter().",
        "en": "A naive result cannot be subtracted from or compared with an aware one — Python raises TypeError: can't subtract offset-naive and offset-aware datetimes — so pick one style and stick to it across the program. Also do not use now() to measure elapsed time: the system clock is adjusted by NTP and DST shifts, so use time.perf_counter() for durations."
      },
      "syntax": "from datetime import datetime\ndatetime.now(tz=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime.now",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "datetime",
      "color_group": "module",
      "aliases": [
        "текущее время",
        "текущие дата и время",
        "время сейчас"
      ],
      "keywords": [
        "datetime.now",
        "now"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime, timezone",
        "now = datetime.now()",
        "print(now)  # → 2024-xx-xx xx:xx:xx.xxxxxx",
        "print(now.hour, now.minute, now.second)",
        "utc = datetime.now(timezone.utc)",
        "print(utc.tzinfo)  # → UTC (осознанный объект)"
      ],
      "related": [
        "datetime.utcnow",
        "date.today"
      ],
      "related_errors": []
    },
    {
      "id": "datetime.strftime",
      "title": "datetime.strftime",
      "kind": "term",
      "summary": {
        "ru": "Форматирует объект datetime в строку по шаблону формата (коды %Y, %m, %d, %H и т.д.).",
        "en": "Formats a datetime object into a string following a format template (the codes %Y, %m, %d, %H and so on)."
      },
      "body": {
        "ru": "Строку формата в конце концов разбирает C-библиотека системы, поэтому переносим только документированный набор кодов: трюки вроде %-d (день без ведущего нуля) работают в Linux и падают на Windows. Названия дня недели и месяца (%A, %B) и AM/PM зависят от текущей локали, так что strftime — для показа человеку; для обмена данными надёжнее isoformat(). Тот же язык шаблонов понимает и спецификатор формата внутри f-строки, отдельный вызов не обязателен.",
        "en": "The format string is ultimately handed to the platform's C library, so only the documented codes are portable: tricks like %-d (day without a leading zero) work on Linux and raise on Windows. Weekday and month names (%A, %B) and AM/PM follow the current locale, which makes strftime a display tool — for data interchange isoformat() is the safer choice. The same template language also works as a format spec inside an f-string, so a separate call is not always needed."
      },
      "syntax": "dt.strftime(format)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime.strftime",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "форматирование",
      "color_group": "module",
      "aliases": [
        "форматирование даты",
        "дата в строку",
        "вывести дату в нужном формате"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime",
        "dt = datetime(2024, 3, 15, 14, 30, 45)",
        "print(dt.strftime('%Y-%m-%d'))  # → 2024-03-15",
        "print(dt.strftime('%d/%m/%Y %H:%M'))  # → 15/03/2024 14:30",
        "print(dt.strftime('%A, %B %d, %Y'))  # → Friday, March 15, 2024",
        "print(dt.strftime('%I:%M %p'))  # → 02:30 PM",
        "print(dt.strftime('%Y%m%d_%H%M%S'))  # → 20240315_143045"
      ],
      "related": [
        "datetime.strptime",
        "коды-формата-datetime",
        "date.fromisoformat"
      ],
      "related_errors": []
    },
    {
      "id": "datetime.strptime",
      "title": "datetime.strptime",
      "kind": "term",
      "summary": {
        "ru": "Парсит строку в объект datetime по шаблону формата (коды %Y, %m, %d и т.д.). Обратная операция для strftime().",
        "en": "Parses a string into a datetime object following a format template (the codes %Y, %m, %d and so on). The inverse of strftime()."
      },
      "body": {
        "ru": "Шаблон должен покрыть строку целиком: лишний хвост или разделитель, не совпавший с шаблоном, дают ValueError, поэтому разбор пользовательского ввода всегда оборачивают в try. Результат получается naive, пока в шаблоне нет %z, а сравнивать naive-дату с aware Python откажется — это TypeError. Для ISO-строк шаблон вообще не нужен: fromisoformat() и проще, и заметно быстрее.",
        "en": "The template must consume the whole string: trailing text or a separator that does not match raises ValueError, so parsing user input belongs inside a try block. Without %z in the template the result is naive, and comparing a naive datetime with an aware one is a TypeError, not a silent surprise. For ISO-formatted strings skip the template entirely — fromisoformat() is simpler and noticeably faster."
      },
      "syntax": "datetime.strptime(date_string, format)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime.strptime",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "форматирование",
      "color_group": "module",
      "aliases": [
        "строка в дату",
        "разобрать дату из строки",
        "распарсить дату по шаблону"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime",
        "d = datetime.strptime('2024-03-15', '%Y-%m-%d')",
        "print(d)  # → 2024-03-15 00:00:00",
        "d2 = datetime.strptime('15/03/2024 14:30', '%d/%m/%Y %H:%M')",
        "print(d2)  # → 2024-03-15 14:30:00",
        "d3 = datetime.strptime('March 15, 2024', '%B %d, %Y')",
        "print(d3.month)  # → 3",
        "print(datetime.strptime('2024-01-01 12:00:00', '%Y-%m-%d %H:%M:%S'))"
      ],
      "related": [
        "datetime.strftime",
        "коды-формата-datetime",
        "date.fromisoformat"
      ],
      "related_errors": []
    },
    {
      "id": "datetime.time",
      "title": "datetime.time",
      "kind": "term",
      "summary": {
        "ru": "Время суток (часы, минуты, секунды, микросекунды) без даты.",
        "en": "A time of day (hour, minute, second, microsecond) without a date."
      },
      "body": {
        "ru": "Арифметики у time нет: вычесть одно время из другого или прибавить timedelta нельзя — сначала соедините время с датой (datetime.combine), а считайте уже на datetime. Сравнение работает, но на интервалах через полночь подводит: 23:30 «меньше» 00:10, хотя идёт раньше в тех же сутках. Объект неизменяем, новое значение получают через replace().",
        "en": "time has no arithmetic: you cannot subtract two times or add a timedelta to one — combine it with a date first (datetime.combine) and do the maths on the datetime. Comparison works but misleads across midnight: 23:30 sorts below 00:10 even though it comes earlier in the same day. The object is immutable, so build a changed copy with replace()."
      },
      "syntax": "datetime.time(hour=0, minute=0, second=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.time",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "время",
      "color_group": "module",
      "aliases": [
        "время суток",
        "хранение только времени"
      ],
      "keywords": [],
      "tags": [
        "datetime"
      ],
      "examples": [
        "import datetime",
        "t = datetime.time(10, 30, 45)",
        "print(t)         # → 10:30:45",
        "print(t.hour, t.minute)  # → 10 30"
      ],
      "related": [
        "time-объект",
        "dt-combine",
        "datetime.datetime"
      ],
      "related_errors": []
    },
    {
      "id": "datetime.timezone",
      "title": "datetime.timezone",
      "kind": "term",
      "summary": {
        "ru": "Часовой пояс с фиксированным смещением от UTC (подкласс tzinfo). datetime.timezone.utc — сам UTC.",
        "en": "A time zone with a fixed offset from UTC (a subclass of tzinfo)."
      },
      "body": {
        "ru": "Это фиксированное смещение и ничего больше: перехода на летнее время здесь нет, поэтому для настоящих зон вроде Europe/Moscow нужен ZoneInfo из модуля zoneinfo (Python 3.9+). Смещение обязано быть строго меньше суток по модулю, иначе ValueError. Не путайте replace(tzinfo=...), который просто вешает ярлык, не трогая часы и минуты, с astimezone(), который пересчитывает момент времени в другой пояс.",
        "en": "This is a fixed offset and nothing more — no daylight saving transitions — so real zones like Europe/Moscow need ZoneInfo from the zoneinfo module (Python 3.9+). The offset must be strictly less than one day in magnitude, otherwise you get a ValueError. Keep replace(tzinfo=...), which only attaches a label and leaves the clock reading untouched, distinct from astimezone(), which actually converts the instant to another zone."
      },
      "syntax": "datetime.timezone(offset)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.timezone",
      "version": "3.2",
      "section": "Модуль datetime",
      "subcat": "часовой пояс",
      "color_group": "module",
      "aliases": [
        "часовой пояс со смещением",
        "задать смещение от Гринвича"
      ],
      "keywords": [],
      "tags": [
        "datetime"
      ],
      "examples": [
        "import datetime",
        "tz = datetime.timezone(datetime.timedelta(hours=3))",
        "print(tz)   # → UTC+03:00",
        "print(datetime.timezone.utc)   # → UTC"
      ],
      "related": [
        "timezone-aware-datetime",
        "datetime.tzinfo",
        "dt-astimezone",
        "timedelta"
      ],
      "related_errors": []
    },
    {
      "id": "datetime.tzinfo",
      "title": "datetime.tzinfo",
      "kind": "term",
      "summary": {
        "ru": "Абстрактный базовый класс сведений о часовом поясе; конкретная реализация — timezone.",
        "en": "An abstract base class for time-zone information; the concrete implementation is timezone."
      },
      "body": {
        "ru": "Класс абстрактный: сам экземпляр бесполезен, а наследник обязан реализовать utcoffset(), dst() и tzname(). Писать свой подкласс почти никогда не нужно — фиксированное смещение закрывает timezone, а зоны с переходом на летнее время — ZoneInfo из zoneinfo. На практике tzinfo встречается в проверке dt.tzinfo is None, которая отличает naive-дату от aware.",
        "en": "The class is abstract: an instance on its own does nothing, and a subclass must supply utcoffset(), dst() and tzname(). Writing your own subclass is almost never necessary — timezone covers fixed offsets and ZoneInfo from zoneinfo covers real zones with DST rules. In everyday code tzinfo mostly shows up in the dt.tzinfo is None check that tells a naive datetime from an aware one."
      },
      "syntax": "class MyTz(datetime.tzinfo): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.tzinfo",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "часовой пояс",
      "color_group": "module",
      "aliases": [
        "базовый класс часового пояса",
        "своя реализация часового пояса",
        "сведения о часовом поясе"
      ],
      "keywords": [],
      "tags": [
        "datetime"
      ],
      "examples": [
        "import datetime",
        "print(issubclass(datetime.timezone, datetime.tzinfo))   # → True",
        "print(datetime.timezone.utc.tzname(None))   # → UTC",
        "print(datetime.timezone(datetime.timedelta(hours=3)).utcoffset(None))   # → 3:00:00",
        "d = datetime.datetime(2024, 5, 1, 12, 0, tzinfo=datetime.timezone.utc)",
        "print(d.astimezone(datetime.timezone(datetime.timedelta(hours=3))).hour)   # → 15",
        "print(datetime.datetime(2024, 5, 1).tzinfo)   # → None",
        "print(datetime.tzinfo().utcoffset(None))   # → NotImplementedError"
      ],
      "related": [
        "datetime.timezone",
        "timezone-aware-datetime",
        "dt-astimezone"
      ],
      "related_errors": []
    },
    {
      "id": "datetime.utcnow",
      "title": "datetime.utcnow()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает текущее время UTC, но БЕЗ tzinfo (naive) — из-за этого его легко спутать с локальным. Устарел с Python 3.12: используй datetime.now(timezone.utc).",
        "en": "Return the current UTC time as a naive object (no tzinfo), which is easy to confuse with local time. Deprecated since Python 3.12 — use datetime.now(timezone.utc)."
      },
      "body": {
        "ru": "Опаснее самого устаревания то, что naive-результат дальше молча считается локальным временем: utcnow().timestamp() или сравнение с datetime.now() уезжают ровно на величину вашего часового пояса. Правильная реакция при встрече в чужом коде — заменить на datetime.now(timezone.utc), помня, что у нового объекта появится tzinfo и в isoformat() допишется '+00:00'.",
        "en": "The real hazard is not the deprecation but that the naive result is silently treated as local time downstream: utcnow().timestamp(), or comparing it with datetime.now(), is off by exactly your UTC offset. When you meet it in existing code, replace it with datetime.now(timezone.utc) and expect the new object to carry tzinfo and to append '+00:00' in isoformat()."
      },
      "syntax": "from datetime import datetime\ndatetime.utcnow()  # deprecated, 3.12+",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "datetime",
      "color_group": "module",
      "aliases": [
        "время по Гринвичу",
        "мировое время"
      ],
      "keywords": [
        "datetime.utcnow",
        "utcnow"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime, timezone",
        "old = datetime.utcnow()  # naive, устарело",
        "print(old.tzinfo)  # → None (нет пояса!)",
        "new = datetime.now(timezone.utc)  # современный аналог",
        "print(new.tzinfo)  # → UTC"
      ],
      "related": [
        "datetime.now"
      ],
      "related_errors": []
    },
    {
      "id": "dt-astimezone",
      "title": "astimezone()",
      "kind": "function",
      "summary": {
        "ru": "Конвертирует aware-datetime в другой часовой пояс. Если объект naive, считает его локальным.",
        "en": "Converts an aware datetime into another time zone. A naive object is treated as local time."
      },
      "body": {
        "ru": "Метод не сдвигает сам момент времени, а лишь пересчитывает поля под другой пояс — точка на оси времени остаётся прежней, меняются только видимые часы и tzinfo. Главная ловушка — вызвать его на naive-объекте, который на деле хранит UTC: Python примет его за локальное время машины и сдвинет неверно. Чтобы просто пометить значение поясом без пересчёта, нужен replace(tzinfo=...), а astimezone() — уже для конвертации между поясами.",
        "en": "The call does not move the instant, it only re-renders it: the underlying point in time stays the same while the visible clock fields and tzinfo change. The trap is calling it on a naive value that actually holds UTC — Python assumes the machine's local zone and shifts by the wrong amount. Use replace(tzinfo=...) to label a value with a zone, and astimezone() only to convert between zones."
      },
      "syntax": "dt.astimezone(tz=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime.astimezone",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "методы datetime",
      "color_group": "module",
      "aliases": [
        "перевести время в другой часовой пояс",
        "смена часового пояса",
        "конвертация времени между поясами"
      ],
      "keywords": [
        "astimezone"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime, timezone, timedelta",
        "utc = datetime(2024, 6, 17, 10, 0, tzinfo=timezone.utc)",
        "msk = timezone(timedelta(hours=3))",
        "msk_dt = utc.astimezone(msk)",
        "print(msk_dt)  # 2024-06-17 13:00:00+03:00"
      ],
      "related": [
        "timezone-aware-datetime",
        "datetime.timezone",
        "dt-replace"
      ],
      "related_errors": []
    },
    {
      "id": "dt-combine",
      "title": "combine()",
      "kind": "function",
      "summary": {
        "ru": "Объединяет объекты date и time в один datetime. Полезно, когда дата и время хранятся раздельно.",
        "en": "Combines a date object and a time object into a single datetime. Useful when the date and the time are stored separately."
      },
      "body": {
        "ru": "tzinfo результата берётся у аргумента time (или у явно переданного параметра tzinfo) — у date его нет вовсе, так что aware-время делает aware и весь результат. Если первым аргументом передать datetime, его собственные часы и пояс молча отбрасываются, берётся только календарная дата. Частый приём — границы суток: дата вместе с time.min или time.max даёт начало и конец дня.",
        "en": "The result's tzinfo comes from the time argument, or from the explicit tzinfo parameter — date carries none — so an aware time yields an aware datetime. Pass a datetime as the first argument and its own clock and zone are silently dropped; only the calendar date survives. A common use is day boundaries: a date paired with time.min or time.max gives the start and the end of that day."
      },
      "syntax": "datetime.combine(date, time, tzinfo=self.tzinfo)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime.combine",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "методы datetime",
      "color_group": "module",
      "aliases": [
        "объединить дату и время",
        "склеить дату со временем"
      ],
      "keywords": [
        "combine"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime, date, time",
        "d = date(2024, 6, 17)",
        "t = time(9, 30)",
        "dt = datetime.combine(d, t)",
        "print(dt)  # 2024-06-17 09:30:00"
      ],
      "related": [
        "datetime.date",
        "datetime.time",
        "datetime.datetime"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "dt-fromtimestamp",
      "title": "fromtimestamp()",
      "kind": "function",
      "summary": {
        "ru": "Создаёт datetime из Unix-timestamp (секунд с 1970-01-01). По умолчанию в локальном часовом поясе; передай tz для UTC или другого.",
        "en": "Creates a datetime from a Unix timestamp (seconds since 1970-01-01). In the local time zone by default; pass tz for UTC or another one."
      },
      "body": {
        "ru": "Без tz результат получается naive и в поясе конкретной машины, поэтому один и тот же timestamp на ноутбуке и на сервере покажет разное время — для воспроизводимости передавайте timezone.utc. Парный utcfromtimestamp() с Python 3.12 объявлен устаревшим, и заменяет его именно этот вызов с tz. Обратное преобразование делает метод timestamp(), причём naive-объект он тоже трактует как локальное время.",
        "en": "Without tz the result is naive and expressed in whatever zone the machine happens to use, so the same timestamp reads differently on your laptop and on the server; pass timezone.utc when you need reproducibility. The companion utcfromtimestamp() is deprecated as of Python 3.12, and this call with tz is its replacement. The reverse direction is timestamp(), which likewise reads a naive object as local time."
      },
      "syntax": "datetime.fromtimestamp(timestamp, tz=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime.fromtimestamp",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "методы datetime",
      "color_group": "module",
      "aliases": [
        "метка времени в дату",
        "перевести секунды в дату",
        "число секунд в дату"
      ],
      "keywords": [
        "fromtimestamp"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime, timezone",
        "ts = 1704067200",
        "local = datetime.fromtimestamp(ts)              # локальное время",
        "utc   = datetime.fromtimestamp(ts, tz=timezone.utc)  # UTC",
        "print(utc)  # 2024-01-01 00:00:00+00:00"
      ],
      "related": [
        "dt-timestamp",
        "dt-utcfromtimestamp",
        "datetime.timezone"
      ],
      "related_errors": [
        "OverflowError",
        "OSError"
      ]
    },
    {
      "id": "dt-isoweekday",
      "title": "isoweekday()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает день недели по ISO: 1 — понедельник, 7 — воскресенье. В отличие от weekday(), нумерация начинается с 1.",
        "en": "Returns the ISO day of the week: 1 is Monday, 7 is Sunday. Unlike weekday(), the numbering starts at 1."
      },
      "body": {
        "ru": "Выбор между двумя нумерациями — вопрос того, куда число пойдёт дальше: weekday() удобен как индекс в списке названий дней, а isoweekday() совпадает с третьим элементом isocalendar() и с ISO-нумерацией недель. Отсюда и классическая ошибка на единицу: выходные — это 5 и 6 для weekday(), но 6 и 7 для isoweekday().",
        "en": "Pick by what the number feeds into: weekday() is the one that indexes a zero-based list of day names, while isoweekday() lines up with the third field of isocalendar() and with ISO week numbering. Hence the classic off-by-one — the weekend is 5 and 6 under weekday() but 6 and 7 under isoweekday()."
      },
      "syntax": "date.isoweekday() -> int  # 1=Mon … 7=Sun",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.date.isoweekday",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "методы datetime",
      "color_group": "module",
      "aliases": [
        "номер дня недели с единицы",
        "день недели по ИСО"
      ],
      "keywords": [
        "isoweekday"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import date",
        "d = date(2024, 6, 17)   # понедельник",
        "print(d.isoweekday())   # 1",
        "print(d.weekday())      # 0"
      ],
      "related": [
        "dt-weekday",
        "datetime.date"
      ],
      "related_errors": []
    },
    {
      "id": "dt-replace",
      "title": "replace()",
      "kind": "function",
      "summary": {
        "ru": "Создаёт новый объект datetime/date/time с заменёнными полями. Исходный объект не изменяется — datetime неизменяем.",
        "en": "Creates a new datetime/date/time object with some fields replaced. The original is not changed — datetime is immutable."
      },
      "body": {
        "ru": "Главная ловушка — replace(tzinfo=...): время не пересчитывается, к 10:00 просто приклеивается ярлык зоны, и 10:00 оно и остаётся. Для настоящего перевода в другую зону нужен astimezone(). Подстановка несуществующей даты (day=31 для февраля) не «подтягивается» к концу месяца, а падает с ValueError.",
        "en": "The classic trap is replace(tzinfo=...): it does not convert anything, it just relabels the same wall-clock time, so 10:00 stays 10:00. Use astimezone() when you actually want the time recomputed for another zone. Substituting a date that does not exist (day=31 in February) raises ValueError instead of clamping to the end of the month."
      },
      "syntax": "dt.replace(year=, month=, day=, hour=, minute=, second=, microsecond=, tzinfo=)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime.replace",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "методы datetime",
      "color_group": "module",
      "aliases": [
        "заменить поле в дате",
        "изменить год или месяц у даты",
        "обнулить часы и минуты"
      ],
      "keywords": [
        "replace"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime",
        "now = datetime(2024, 6, 17, 10, 30)",
        "new = now.replace(hour=0, minute=0, second=0)  # начало дня",
        "print(new)  # 2024-06-17 00:00:00",
        "# заменить только год",
        "next_year = now.replace(year=2025)"
      ],
      "related": [
        "dt-astimezone",
        "datetime.datetime",
        "timezone-aware-datetime"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "dt-timestamp",
      "title": "timestamp()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает Unix-timestamp (секунды с 1970-01-01 00:00 UTC) как float. Объект должен быть timezone-aware или local.",
        "en": "Returns the Unix timestamp (seconds since 1970-01-01 00:00 UTC) as a float. The object has to be timezone-aware or local."
      },
      "body": {
        "ru": "Если объект naive, Python считает его местным временем машины — тот же код на другом компьютере или в CI даст другое число. Хотите воспроизводимый результат — делайте объект aware, например с tzinfo=timezone.utc. Возвращается float, поэтому после обратного преобразования значение может разойтись в последних микросекундах: сравнивать timestamp'ы на строгое равенство рискованно.",
        "en": "For a naive object Python assumes local machine time, so the very same code gives a different number on another computer or in CI. If you need a reproducible value, make the object aware, e.g. with tzinfo=timezone.utc. The result is a float, so a round trip can drift by the last microseconds — comparing timestamps for exact equality is fragile."
      },
      "syntax": "datetime.timestamp() -> float",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime.timestamp",
      "version": "3.3",
      "section": "Модуль datetime",
      "subcat": "методы datetime",
      "color_group": "module",
      "aliases": [
        "дата в секунды",
        "получить метку времени из даты"
      ],
      "keywords": [
        "timestamp"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime, timezone",
        "dt = datetime(2024, 1, 1, tzinfo=timezone.utc)",
        "print(dt.timestamp())  # 1704067200.0",
        "# обратно: datetime из timestamp",
        "from datetime import datetime",
        "dt2 = datetime.fromtimestamp(1704067200, tz=timezone.utc)"
      ],
      "related": [
        "dt-fromtimestamp",
        "dt-utcfromtimestamp",
        "timezone-aware-datetime"
      ],
      "related_errors": []
    },
    {
      "id": "dt-timetuple",
      "title": "timetuple()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает time.struct_time — совместимую с time.mktime() структуру. Удобно для передачи в функции модуля time.",
        "en": "Returns a time.struct_time — the structure time.mktime() accepts. Convenient for passing values to functions of the time module."
      },
      "body": {
        "ru": "Метод существует ради совместимости со старым модулем time: struct_time не хранит ни микросекунды, ни таймзону, поэтому time.mktime(tt) истолкует значение как местное время. У naive-объекта поле tm_isdst равно -1 («неизвестно»), у aware — берётся из dst(). В новом коде почти всегда проще обойтись timestamp(), astimezone() и strftime().",
        "en": "This method exists for compatibility with the old time module: struct_time carries neither microseconds nor a time zone, so time.mktime(tt) will read the value as local time. For a naive object tm_isdst is -1 (\"unknown\"); for an aware one it comes from dst(). In new code timestamp(), astimezone() and strftime() usually cover the same ground more directly."
      },
      "syntax": "datetime.timetuple() -> time.struct_time",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime.timetuple",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "методы datetime",
      "color_group": "module",
      "aliases": [
        "дата в структуру времени",
        "кортеж из даты и времени"
      ],
      "keywords": [
        "timetuple"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime",
        "import time",
        "dt = datetime(2024, 6, 17, 10, 0)",
        "tt = dt.timetuple()",
        "print(tt.tm_year)  # 2024",
        "print(tt.tm_wday)  # 0 (Monday)",
        "ts = time.mktime(tt)  # unix timestamp"
      ],
      "related": [
        "dt-timestamp",
        "datetime.datetime"
      ],
      "related_errors": []
    },
    {
      "id": "dt-utcfromtimestamp",
      "title": "utcfromtimestamp()",
      "kind": "function",
      "summary": {
        "ru": "Создаёт naive datetime в UTC из Unix-timestamp. Устаревший метод — предпочтительнее fromtimestamp(ts, tz=timezone.utc).",
        "en": "Creates a naive datetime in UTC from a Unix timestamp. A deprecated method — prefer fromtimestamp(ts, tz=timezone.utc)."
      },
      "body": {
        "ru": "Начиная с Python 3.12 вызов помечен устаревшим и печатает DeprecationWarning, а убрать его планируют в будущих версиях. Беда не столько в предупреждении: результат naive, и если потом позвать у него timestamp() или astimezone(), Python примет его за местное время — round-trip сломается на любой машине не в UTC. Пишите fromtimestamp(ts, tz=timezone.utc) — тогда зона в объекте есть и путаницы нет.",
        "en": "Since Python 3.12 this call is deprecated and emits a DeprecationWarning; removal is planned for a future release. The warning is not the real problem: the result is naive, so a later timestamp() or astimezone() will treat it as local time and the round trip breaks on any machine that is not on UTC. Use fromtimestamp(ts, tz=timezone.utc) instead, so the zone travels with the object."
      },
      "syntax": "datetime.utcfromtimestamp(timestamp)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.datetime.utcfromtimestamp",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "методы datetime",
      "color_group": "module",
      "aliases": [
        "метка времени в utc-дату",
        "юникс-время в дату без часового пояса"
      ],
      "keywords": [
        "utcfromtimestamp"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime",
        "dt = datetime.utcfromtimestamp(1704067200)",
        "print(dt)  # 2024-01-01 00:00:00  (naive, UTC)",
        "# современная альтернатива:",
        "from datetime import timezone",
        "dt2 = datetime.fromtimestamp(1704067200, tz=timezone.utc)"
      ],
      "related": [
        "dt-fromtimestamp",
        "datetime.utcnow",
        "datetime.timezone"
      ],
      "related_errors": [
        "OverflowError",
        "OSError"
      ]
    },
    {
      "id": "dt-weekday",
      "title": "weekday()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает день недели как целое число: 0 — понедельник, 6 — воскресенье. Удобно для условий и фильтрации по дням.",
        "en": "Returns the day of the week as an integer: 0 is Monday, 6 is Sunday. Convenient in conditions and for filtering by day."
      },
      "body": {
        "ru": "Рядом живут две другие нумерации: isoweekday() возвращает 1–7 (воскресенье — 7), а strftime('%w') — 0–6, но нулём там считается воскресенье; перепутать их проще простого. Проверку на выходные надёжнее писать как d.weekday() >= 5. Сама нумерация от локали не зависит — локаль влияет только на текстовые %A и %a.",
        "en": "Two neighbouring numbering schemes invite mistakes: isoweekday() returns 1-7 with Sunday as 7, while strftime('%w') returns 0-6 but counts Sunday as zero. A weekend check is safest written as d.weekday() >= 5. The numbering itself is locale-independent — the locale only affects the textual %A and %a."
      },
      "syntax": "date.weekday() -> int  # 0=Mon … 6=Sun",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.date.weekday",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "методы datetime",
      "color_group": "module",
      "aliases": [
        "день недели числом",
        "определить день недели по дате",
        "проверить выходной день"
      ],
      "keywords": [
        "weekday"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import date",
        "d = date(2024, 6, 17)   # понедельник",
        "print(d.weekday())      # 0",
        "print(d.strftime('%A')) # Monday",
        "# фильтр рабочих дней",
        "if d.weekday() < 5:",
        "    print('будний день')"
      ],
      "related": [
        "dt-isoweekday",
        "datetime.date",
        "date.today"
      ],
      "related_errors": []
    },
    {
      "id": "time-объект",
      "title": "time объект",
      "kind": "term",
      "summary": {
        "ru": "Объект времени (без даты). Часы, минуты, секунды, микросекунды.",
        "en": "A time object (with no date). Hours, minutes, seconds and microseconds."
      },
      "body": {
        "ru": "Арифметики у time нет: прибавить timedelta к нему нельзя (TypeError) — чтобы сдвинуть момент на полчаса, соберите полноценный datetime через datetime.combine(дата, время) и считайте уже на нём. Все аргументы необязательны и по умолчанию нулевые, объект неизменяемый, а сравнения (<, ==) работают и идут по часам, минутам, секундам, микросекундам.",
        "en": "time supports no arithmetic: adding a timedelta raises TypeError — to shift a moment by half an hour, build a full datetime with datetime.combine(date, time) and do the math there. Every argument is optional and defaults to zero, the object is immutable, and comparisons work, ordering by hour, minute, second and microsecond."
      },
      "syntax": "from datetime import time\ntime(hour, minute, second, microsecond)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.time",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "time",
      "color_group": "module",
      "aliases": [
        "время без даты",
        "часы минуты секунды"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import time",
        "t = time(14, 30, 45)",
        "print(t)  # → 14:30:45",
        "print(t.hour, t.minute, t.second)  # → 14 30 45",
        "t2 = time(0, 0, 0)",
        "print(t2)  # → 00:00:00",
        "print(time.fromisoformat('12:30:00'))  # → 12:30:00",
        "print(time(23, 59, 59, 999999))  # → максимум дня"
      ],
      "related": [
        "datetime.time",
        "dt-combine",
        "datetime.datetime"
      ],
      "related_errors": []
    },
    {
      "id": "timedelta",
      "title": "timedelta",
      "kind": "term",
      "summary": {
        "ru": "Промежуток времени. Поддерживает операции с датами: сложение, вычитание, сравнение. Хранит дни, секунды, микросекунды.",
        "en": "A span of time. It supports operations with dates: addition, subtraction and comparison. It stores days, seconds and microseconds."
      },
      "body": {
        "ru": "Внутри хранятся только дни, секунды и микросекунды: weeks, hours, minutes пересчитываются при создании, а месяцев и лет нет вовсе — «прибавить месяц» через timedelta не выразить, нужен ручной расчёт или сторонний dateutil. Атрибут .seconds — это остаток внутри суток (0-86399), а не вся длительность; полную величину в секундах даёт total_seconds().",
        "en": "Only days, seconds and microseconds are stored: weeks, hours and minutes are folded into them at construction, and there are no months or years at all, so \"one month later\" cannot be expressed with a timedelta. Beware of .seconds — it is the leftover within a day (0-86399), not the whole span; use total_seconds() when you need the full length."
      },
      "syntax": "from datetime import timedelta\ntimedelta(days=0, seconds=0, ...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.timedelta",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "timedelta",
      "color_group": "module",
      "aliases": [
        "промежуток времени",
        "разница между датами",
        "прибавить дни к дате"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime, timedelta",
        "td = timedelta(days=7)",
        "print(td)  # → 7 days, 0:00:00",
        "next_week = datetime.now() + td",
        "print(next_week.date())",
        "td2 = timedelta(hours=2, minutes=30)",
        "print(td2.total_seconds())  # → 9000.0",
        "d1 = datetime(2024,12,31)",
        "d2 = datetime(2024,1,1)",
        "print((d1-d2).days)  # → 365",
        "print(timedelta(weeks=2))  # → 14 days"
      ],
      "related": [
        "date-арифметика",
        "дни-между-датами",
        "datetime.datetime"
      ],
      "related_errors": []
    },
    {
      "id": "timezone-aware-datetime",
      "title": "timezone aware datetime",
      "kind": "term",
      "summary": {
        "ru": "Datetime с учётом часового пояса. Используй timezone из datetime или pytz.",
        "en": "A datetime that carries a time zone. Use timezone from datetime, or pytz."
      },
      "body": {
        "ru": "Naive и aware смешивать нельзя: сравнение или вычитание datetime.now() и datetime.now(timezone.utc) даёт TypeError, поэтому выбирайте что-то одно на весь проект — обычно UTC. timezone(timedelta(hours=3)) — фиксированный сдвиг, он ничего не знает о переходах на летнее время; для настоящих зон берите zoneinfo.ZoneInfo(\"Europe/Berlin\") из стандартной библиотеки (Python 3.9+), pytz давно не нужен.",
        "en": "Naive and aware objects do not mix: comparing or subtracting datetime.now() and datetime.now(timezone.utc) raises TypeError, so pick one convention for the whole program — usually UTC. A timezone(timedelta(hours=3)) is a fixed offset that knows nothing about daylight saving; for real regions use zoneinfo.ZoneInfo(\"Europe/Berlin\") from the standard library (Python 3.9+) instead of pytz."
      },
      "syntax": "from datetime import timezone\ndatetime.now(timezone.utc)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#aware-and-naive-objects",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "timezone",
      "color_group": "module",
      "aliases": [
        "дата с часовым поясом",
        "время с учётом часового пояса",
        "наивное и осознанное время"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime, timezone, timedelta",
        "utc_now = datetime.now(timezone.utc)",
        "print(utc_now.tzinfo)  # → UTC",
        "print(utc_now.utcoffset())  # → 0:00:00",
        "moscow_tz = timezone(timedelta(hours=3))",
        "moscow_now = datetime.now(moscow_tz)",
        "print(moscow_now.utcoffset())  # → 3:00:00",
        "print(moscow_now.strftime('%z'))  # → +0300",
        "fixed = datetime(2024, 5, 17, 12, 30, tzinfo=timezone.utc)",
        "print(fixed)  # → 2024-05-17 12:30:00+00:00",
        "print(fixed.astimezone(moscow_tz))  # → 2024-05-17 15:30:00+03:00"
      ],
      "related": [
        "datetime.timezone",
        "dt-astimezone",
        "datetime.tzinfo",
        "datetime.now"
      ],
      "related_errors": []
    },
    {
      "id": "дни-между-датами",
      "title": "Дни между датами",
      "kind": "term",
      "summary": {
        "ru": "Вычисление количества дней между двумя датами через вычитание date-объектов — результат является timedelta.",
        "en": "The number of days between two dates, found by subtracting two date objects — the result is a timedelta."
      },
      "body": {
        "ru": "Разность двух date — всегда целое число суток, но при вычитании datetime атрибут .days отбрасывает часы и минуты, а остаток уходит в .seconds; полную разницу даёт total_seconds(). Отрицательный результат нормализуется вниз: у промежутка «минус пара часов» .days равен -1. И не считайте возраст в годах как .days // 365 — високосные годы накапливают ошибку.",
        "en": "Subtracting two date objects always gives whole days, but subtracting datetimes puts only complete days into .days and pushes the leftover hours into .seconds — use total_seconds() for the real difference. Negative spans are normalised downwards, so a gap of minus two hours reports .days == -1. Do not turn this into years with .days // 365 either: leap years make the estimate drift."
      },
      "syntax": "(d2 - d1).days",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#datetime.timedelta.days",
      "version": "",
      "section": "Модуль datetime",
      "subcat": "timedelta",
      "color_group": "module",
      "aliases": [
        "сколько дней прошло",
        "разница дат в днях",
        "посчитать количество дней"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import date",
        "birth = date(2000, 6, 15)",
        "today = date.today()",
        "age_days = (today - birth).days",
        "print(age_days)  # → ~8900",
        "new_year = date(date.today().year + 1, 1, 1)",
        "days_left = (new_year - today).days",
        "print(days_left, 'days until NY')",
        "print((date(2024,12,31) - date(2024,1,1)).days)  # → 365"
      ],
      "related": [
        "timedelta",
        "date-арифметика",
        "datetime.date"
      ],
      "related_errors": []
    },
    {
      "id": "коды-формата-datetime",
      "title": "Коды формата datetime",
      "kind": "term",
      "summary": {
        "ru": "Коды для strftime/strptime: %Y год, %m месяц, %d день, %H час (24ч), %M минута, %S секунда, %A день нед., %B месяц, %p AM/PM.",
        "en": "The codes for strftime/strptime: %Y year, %m month, %d day, %H hour (24h), %M minute, %S second, %A weekday, %B month, %p AM/PM."
      },
      "body": {
        "ru": "%H — час в 24-часовом формате, %I — в 12-часовом, и %p осмыслен только вместе с %I: приписанный к %H он ничего не изменит. При разборе strptime мягче, чем кажется, — для %m примет и 3, и 03, — но структуру строки требует точь-в-точь: шаблон с дефисами не разберёт дату с точками. Гарантированно переносим только документированный набор кодов, всё остальное зависит от системной библиотеки.",
        "en": "%H is the 24-hour field and %I the 12-hour one, and %p only means something alongside %I — bolted onto %H it changes nothing. When parsing, strptime is more forgiving about padding than it looks (%m accepts both 3 and 03), but the surrounding punctuation must match exactly, so a hyphen template will not read a dotted date. Only the documented codes are guaranteed portable; anything else is whatever the system C library happens to support."
      },
      "syntax": "%Y %m %d %H %M %S %A %B %p",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes",
      "version": "3.6",
      "section": "Модуль datetime",
      "subcat": "форматирование",
      "color_group": "module",
      "aliases": [
        "шаблоны формата даты",
        "процентные коды даты и времени",
        "обозначения года, месяца, дня"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from datetime import datetime",
        "dt = datetime(2024, 3, 15, 14, 5, 9)",
        "print(dt.strftime('%Y'))   # → 2024",
        "print(dt.strftime('%m'))   # → 03",
        "print(dt.strftime('%d'))   # → 15",
        "print(dt.strftime('%H:%M:%S'))  # → 14:05:09",
        "print(dt.strftime('%A'))  # → Friday",
        "print(dt.strftime('%B'))  # → March"
      ],
      "related": [
        "datetime.strftime",
        "datetime.strptime"
      ],
      "related_errors": []
    },
    {
      "id": ".name-.value",
      "title": ".name / .value",
      "kind": "term",
      "summary": {
        "ru": "Каждый элемент перечисления имеет атрибуты .name (строка) и .value (заданное значение).",
        "en": "Every member of an enumeration has a .name attribute (a string) and a .value attribute (the value given to it)."
      },
      "body": {
        "ru": "Не путайте два способа поиска: Color(1) ищет по значению и совпадает с .value, а Color['RED'] — по имени и совпадает с .name. Сам элемент не равен своему значению: у обычного Enum Color.RED == 1 даёт False, сравнивать нужно либо элементы между собой, либо .value со значением (либо наследоваться от IntEnum/StrEnum). Оба атрибута доступны только на чтение и реализованы так, что поле с именем name или value в перечислении конфликтует с ними.",
        "en": "Keep the two lookups apart: Color(1) looks up by value and matches .value, while Color['RED'] looks up by name and matches .name. A member is not equal to its value — with a plain Enum, Color.RED == 1 is False, so compare members to members or .value to the raw value (or inherit from IntEnum/StrEnum). Both attributes are read-only and implemented in a way that clashes with a member literally named name or value."
      },
      "syntax": "member.name  # → 'RED'\nmember.value # → 1",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.Enum.name",
      "version": "",
      "section": "Модуль enum",
      "subcat": "атрибуты",
      "color_group": "module",
      "aliases": [
        "имя элемента перечисления",
        "значение члена перечисления"
      ],
      "keywords": [
        "name",
        "value"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from enum import Enum",
        "class Color(Enum):",
        "RED = 1; GREEN = 2",
        "Color.RED.name # → 'RED'",
        "Color.RED.value # → 1",
        "Color(1).name # → 'RED'",
        "for c in Color:",
        "print(c.name, c.value) # RED 1, GREEN 2"
      ],
      "related": [
        "enum",
        "auto",
        "итерация-и-сравнение-enum"
      ],
      "related_errors": []
    },
    {
      "id": "_missing_",
      "title": "_missing_()",
      "kind": "function",
      "summary": {
        "ru": "Классовый метод _missing_() вызывается, когда значение не найдено в перечислении (через Enum(value)).",
        "en": "The _missing_() class method is called when a value is not found in the enumeration (through Enum(value))."
      },
      "body": {
        "ru": "Срабатывает только при поиске по значению — Enum(value); обращение по имени через Enum['NAME'] и доступ к атрибуту его не задействуют. Вернуть нужно элемент этого же перечисления или None (тогда как обычно поднимется ValueError): любой другой объект приведёт к TypeError. Типичное применение — мягкие псевдонимы: подобрать элемент по строке без учёта регистра или отдать значение по умолчанию вместо исключения.",
        "en": "It fires only on lookup by value — Enum(value); name lookup via Enum['NAME'] and plain attribute access never reach it. Return a member of the same enum or None (in which case the usual ValueError is raised); anything else turns into a TypeError. The typical use is lenient aliasing: match a string case-insensitively, or hand back a default member instead of an exception."
      },
      "syntax": "@classmethod\ndef _missing_(cls, value): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.Enum._missing_",
      "version": "3.6",
      "section": "Модуль enum",
      "subcat": "обработка",
      "color_group": "module",
      "aliases": [
        "значение не найдено в перечислении",
        "обработка неизвестного значения перечисления",
        "запасной член перечисления"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from enum import Enum",
        "class Color(Enum):",
        "    RED = 1; GREEN = 2",
        "    @classmethod",
        "    def _missing_(cls, value):",
        "        return cls.RED  # значение по умолчанию",
        "Color(99) # → <Color.RED: 1>",
        "Color(0) # → <Color.RED: 1>",
        "Color(1) # → <Color.RED: 1>"
      ],
      "related": [
        "enum",
        ".name-.value",
        "valueerror"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "auto",
      "title": "auto()",
      "kind": "function",
      "summary": {
        "ru": "Автоматически назначает значения элементам перечисления (по умолчанию — последовательные числа).",
        "en": "Assigns values to the members of an enumeration automatically (consecutive numbers by default)."
      },
      "body": {
        "ru": "Нумерация начинается с 1, а не с 0 — частая причина расхождения с внешним протоколом, где коды идут с нуля. Если смешивать явные значения и auto(), очередное auto() продолжит счёт от последнего числового значения, а во Flag вместо +1 подставляются степени двойки. Правило простое: auto() уместен, когда сами значения никого не волнуют и важны только имена; как только значение уходит в файл, БД или по сети — задавайте его руками.",
        "en": "Numbering starts at 1, not 0, which quietly breaks compatibility with external protocols that count from zero. Mixed with explicit values, the next auto() continues from the last numeric one, and inside a Flag it yields powers of two instead of +1. Use auto() only when the values themselves are irrelevant and names carry the meaning; once a value is stored or sent over the wire, spell it out."
      },
      "syntax": "from enum import auto\nclass X(Enum):\n    A = auto()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.auto",
      "version": "",
      "section": "Модуль enum",
      "subcat": "auto",
      "color_group": "module",
      "aliases": [
        "автоматические значения перечисления",
        "автонумерация членов перечисления"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from enum import Enum, auto",
        "class Dir(Enum):",
        "NORTH = auto()",
        "SOUTH = auto()",
        "EAST = auto()",
        "WEST = auto()",
        "Dir.NORTH.value # → 1",
        "Dir.SOUTH.value # → 2",
        "list(Dir) # → [<Dir.NORTH:1>, <Dir.SOUTH:2>, ...]"
      ],
      "related": [
        "enum",
        ".name-.value",
        "unique"
      ],
      "related_errors": []
    },
    {
      "id": "enum",
      "title": "Enum",
      "kind": "term",
      "summary": {
        "ru": "Базовый класс для создания именованных перечислений. Каждый элемент — уникальный символьный объект.",
        "en": "The base class for named enumerations. Every member is a unique symbolic object."
      },
      "body": {
        "ru": "Члены перечисления сравниваются по идентичности, поэтому Color.RED == 1 даёт False, даже если членом объявлено именно число — чтобы сравнивать со «сырыми» значениями, наследуйтесь от IntEnum или StrEnum. Два члена с одинаковым значением не ошибка: второй становится псевдонимом первого, при итерации не показывается и по имени всё равно доступен. Порядок обхода — порядок объявления в теле класса, а не порядок значений.",
        "en": "Members compare by identity, so Color.RED == 1 is False even when the member's value is that number; subclass IntEnum or StrEnum if you need comparison with raw values. Two members sharing a value are not an error — the second becomes an alias, hidden from iteration but still reachable by name. Iteration follows definition order, not value order."
      },
      "syntax": "from enum import Enum\nclass Color(Enum):\n    RED = 1",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.Enum",
      "version": "",
      "section": "Модуль enum",
      "subcat": "базовый",
      "color_group": "module",
      "aliases": [
        "перечисление",
        "именованные константы",
        "набор допустимых значений"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from enum import Enum",
        "class Color(Enum):",
        "RED = 1; GREEN = 2; BLUE = 3",
        "Color.RED # → <Color.RED: 1>",
        "Color(2) # → <Color.GREEN: 2>",
        "Color['BLUE'] # → <Color.BLUE: 3>",
        "type(Color.RED) # → <enum 'Color'>",
        "Color.RED == Color.RED # → True"
      ],
      "related": [
        ".name-.value",
        "auto",
        "intenum-strenum",
        "flag-intflag"
      ],
      "related_errors": []
    },
    {
      "id": "enum.EnumCheck",
      "title": "enum.EnumCheck",
      "kind": "term",
      "summary": {
        "ru": "Перечисление возможных проверок для verify(): UNIQUE, CONTINUOUS, NAMED_FLAGS (Python 3.11+).",
        "en": "An enumeration of checks for verify(): UNIQUE, CONTINUOUS, NAMED_FLAGS (3.11+)."
      },
      "body": {
        "ru": "Сам по себе EnumCheck ничего не делает — его члены передают декоратору verify(), например @verify(UNIQUE, CONTINUOUS) над классом. Проверка выполняется один раз, в момент создания класса, то есть ошибка вылетит при импорте модуля, а не при обращении к члену. UNIQUE запрещает псевдонимы, CONTINUOUS — дыры в числовых значениях, NAMED_FLAGS требует, чтобы составные значения флагов собирались только из уже именованных битов.",
        "en": "EnumCheck does nothing on its own: its members are arguments to the verify() decorator, as in @verify(UNIQUE, CONTINUOUS). The checks run once, while the class is being built, so a violation blows up at import time rather than at first use. UNIQUE bans aliases, CONTINUOUS bans gaps in numeric values, and NAMED_FLAGS demands that composite flag values be made only of bits that already have names."
      },
      "syntax": "enum.EnumCheck",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.EnumCheck",
      "version": "",
      "section": "Модуль enum",
      "subcat": "проверки",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "print(enum.EnumCheck.UNIQUE.name)   # → UNIQUE",
        "print(sorted(m.name for m in enum.EnumCheck))   # → ['CONTINUOUS', 'NAMED_FLAGS', 'UNIQUE']",
        "print(isinstance(enum.EnumCheck.UNIQUE, str))   # → True",
        "Nums = enum.Enum('Nums', {'A': 1, 'B': 2})",
        "print(enum.verify(enum.EnumCheck.CONTINUOUS)(Nums) is Nums)   # → True",
        "Gap = enum.Enum('Gap', {'A': 1, 'B': 3})",
        "enum.verify(enum.EnumCheck.CONTINUOUS)(Gap)   # → ValueError"
      ],
      "related": [
        "enum.verify",
        "unique",
        "enum.FlagBoundary"
      ],
      "related_errors": []
    },
    {
      "id": "enum.EnumDict",
      "title": "enum.EnumDict",
      "kind": "term",
      "summary": {
        "ru": "Специальный словарь-пространство имён, используемый метаклассом при создании перечисления: отслеживает порядок и запрещает дубли членов (публичный класс с Python 3.13).",
        "en": "The special namespace dict used while creating an enum; tracks order and forbids duplicate members (public in 3.13+)."
      },
      "body": {
        "ru": "В обычном коде этот класс не нужен ни разу — он существует ради того, чтобы повторное присваивание одного и того же имени в теле перечисления падало с TypeError сразу, а не давало молча переопределённый член. Пригодится он только если вы пишете собственный подкласс EnumType и вручную готовите пространство имён. До Python 3.13 он был тем же самым, но приватным (_EnumDict), поэтому код с оглядкой на старые версии должен проверять наличие атрибута.",
        "en": "Everyday code never touches this class; it exists so that assigning the same name twice inside an enum body fails with TypeError immediately instead of silently redefining a member. You only reach for it when writing your own EnumType subclass and preparing the namespace by hand. Before 3.13 the same object existed as the private _EnumDict, so version-tolerant code has to check that the public name is there."
      },
      "syntax": "ns = enum.EnumDict()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.EnumDict",
      "version": "",
      "section": "Модуль enum",
      "subcat": "метакласс",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "d = enum.EnumDict() if hasattr(enum, 'EnumDict') else {}",
        "d['A'] = 1",
        "print(d['A'])   # → 1"
      ],
      "related": [
        "enum.EnumType",
        "enum.EnumMeta",
        "unique"
      ],
      "related_errors": []
    },
    {
      "id": "enum.EnumMeta",
      "title": "enum.EnumMeta",
      "kind": "term",
      "summary": {
        "ru": "Прежнее имя метакласса перечислений — псевдоним enum.EnumType, оставленный для обратной совместимости.",
        "en": "The former name of the enum metaclass — an alias of enum.EnumType kept for compatibility."
      },
      "body": {
        "ru": "Практическая польза от этого имени одна: type(Color) — это и есть метакласс, поэтому isinstance(X, EnumType) отвечает на вопрос «X — перечисление целиком?», в отличие от isinstance(X, Enum), проверяющего отдельный член. Переименование произошло в Python 3.11; старое имя работает и удалять его не собираются, но в новом коде пишите EnumType.",
        "en": "The one practical use: type(Color) is the metaclass, so isinstance(X, EnumType) answers \"is X an enum class?\", whereas isinstance(X, Enum) tests a single member. The rename landed in 3.11; the old name still works and is not slated for removal, but new code should say EnumType."
      },
      "syntax": "enum.EnumMeta is enum.EnumType",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.EnumType",
      "version": "",
      "section": "Модуль enum",
      "subcat": "метакласс",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "print(enum.EnumMeta is enum.EnumType)   # → True",
        "print(enum.EnumMeta.__name__)   # → EnumType",
        "class Color(enum.Enum): RED = 1; GREEN = 2",
        "print(type(Color) is enum.EnumMeta)   # → True",
        "print(len(Color))   # → 2",
        "print(list(Color))   # → [<Color.RED: 1>, <Color.GREEN: 2>]"
      ],
      "related": [
        "enum.EnumType",
        "enum.EnumDict",
        "enum"
      ],
      "related_errors": []
    },
    {
      "id": "enum.EnumType",
      "title": "enum.EnumType",
      "kind": "term",
      "summary": {
        "ru": "Метакласс всех перечислений: именно он превращает класс с атрибутами-константами в Enum (проверяет уникальность, создаёт члены). Актуальное имя с Python 3.11.",
        "en": "The metaclass of all enumerations; turns a class of constants into an Enum (3.11+ name)."
      },
      "body": {
        "ru": "Напрямую его называют редко, но именно он объясняет, почему у класса-перечисления работают len(Color), обход в цикле, Color['RED'] и Color(1): эти операции определены на метаклассе, а не на самом Enum. До Python 3.11 он назывался EnumMeta, и старое имя оставлено как псевдоним, так что код с ним не сломался. Наследоваться от него имеет смысл только если пишешь свой вид перечисления с изменённым поиском членов.",
        "en": "You rarely name it directly, yet it is the reason len(Color), iterating a Color, Color['RED'] and Color(1) all work: those operations live on the metaclass, not on Enum itself. Before Python 3.11 it was called EnumMeta, and the old name still works as an alias, so existing code kept running. Subclassing it only pays off when you build your own flavour of enumeration with custom member lookup."
      },
      "syntax": "class E(enum.Enum): ...  # type(E) is enum.EnumType",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.EnumType",
      "version": "3.11",
      "section": "Модуль enum",
      "subcat": "метакласс",
      "color_group": "module",
      "aliases": [
        "метакласс перечислений",
        "как создаётся перечисление"
      ],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "class Color(enum.Enum):",
        "    RED = 1",
        "print(type(Color).__name__)   # → EnumType"
      ],
      "related": [
        "enum.EnumMeta",
        "enum.EnumDict",
        "enum"
      ],
      "related_errors": []
    },
    {
      "id": "enum.Flag",
      "title": "enum.Flag",
      "kind": "term",
      "summary": {
        "ru": "Перечисление битовых флагов: члены комбинируются операторами | & ^ ~.",
        "en": "A bit-flag enumeration whose members combine with | & ^ ~."
      },
      "body": {
        "ru": "Значения членов должны быть отдельными битами: auto() внутри Flag выдаёт 1, 2, 4 и так далее, а написанное руками 3 создаст не новый флаг, а псевдоним комбинации, который не появится при обходе класса. В отличие от IntFlag, член Flag не является числом, и это плюс: случайно сравнить или сложить его с посторонним int не выйдет. Пустая комбинация (значение 0) ложна в булевом контексте, так что обычная проверка на истинность отвечает на вопрос «взведён ли хоть один флаг».",
        "en": "Member values must be single bits: auto() inside a Flag hands out 1, 2, 4 and so on, while a hand-written 3 creates a composite alias rather than a new flag and won't show up when you iterate the class. Unlike IntFlag, a Flag member is not an int, which is the point: it cannot be silently compared with or added to unrelated numbers. The empty combination (value 0) is falsy, so a plain truth test answers \"is any flag set at all\"."
      },
      "syntax": "class C(enum.Flag): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.Flag",
      "version": "",
      "section": "Модуль enum",
      "subcat": "флаги",
      "color_group": "module",
      "aliases": [
        "перечисление битовых флагов",
        "набор флагов как класс"
      ],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "class Perm(enum.Flag):",
        "    R = 1",
        "    W = 2",
        "    X = 4",
        "print((Perm.R | Perm.W).value)      # → 3",
        "print(Perm.R in (Perm.R | Perm.W))  # → True"
      ],
      "related": [
        "enum.IntFlag",
        "flag-intflag",
        "enum.FlagBoundary",
        "побитовые-операторы"
      ],
      "related_errors": []
    },
    {
      "id": "enum.FlagBoundary",
      "title": "enum.FlagBoundary",
      "kind": "term",
      "summary": {
        "ru": "Стратегия обработки битов вне определённых флагов: STRICT/CONFORM/EJECT/KEEP (Python 3.11+).",
        "en": "The strategy for out-of-range flag bits: STRICT/CONFORM/EJECT/KEEP (3.11+)."
      },
      "body": {
        "ru": "Отвечает на вопрос, что делать, если в значении оказался бит, которого нет ни в одном члене. Умолчания разные, и об это чаще всего спотыкаются: у Flag это STRICT, поэтому неизвестный бит даёт ValueError, а у IntFlag это KEEP, и лишний бит молча сохраняется в члене. Стратегия задаётся ключевым словом boundary прямо в заголовке класса.",
        "en": "It answers what should happen when a value carries a bit that no member defines. The defaults differ, and that is the usual trap: Flag uses STRICT, so an undefined bit raises ValueError, while IntFlag uses KEEP and quietly carries the stray bit along. You choose a strategy with the boundary keyword in the class header."
      },
      "syntax": "enum.FlagBoundary",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.FlagBoundary",
      "version": "",
      "section": "Модуль enum",
      "subcat": "флаги",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "print(enum.FlagBoundary.STRICT.name)   # → STRICT",
        "print(list(enum.FlagBoundary.__members__))   # → ['STRICT', 'CONFORM', 'EJECT', 'KEEP']",
        "class Perm(enum.Flag): R = 1; W = 2",
        "print(Perm(5))   # → ValueError",
        "class Soft(enum.Flag, boundary=enum.CONFORM): R = 1; W = 2",
        "print(Soft(5).value)   # → 1"
      ],
      "related": [
        "enum.Flag",
        "enum.IntFlag",
        "enum.verify",
        "enum.EnumCheck"
      ],
      "related_errors": []
    },
    {
      "id": "enum.IntEnum",
      "title": "enum.IntEnum",
      "kind": "term",
      "summary": {
        "ru": "Перечисление, члены которого — ещё и int: их можно сравнивать с числами и использовать в арифметике.",
        "en": "An enumeration whose members are also ints (comparable to and usable as numbers)."
      },
      "body": {
        "ru": "Плата за удобство — потеря типовой строгости: член равен обычному числу и любому члену другого IntEnum с тем же значением, поэтому перепутанные перечисления сравниваются молча и без ошибки. С Python 3.11 str() и f-строки печатают у IntEnum голое число, а не Color.RED, — при переезде на новую версию логи и сообщения незаметно меняются. Брать IntEnum стоит ради совместимости с кодом и API, которые ждут int; для внутренней логики обычный Enum безопаснее.",
        "en": "The convenience costs you type safety: a member equals a plain number and equals any member of a different IntEnum with the same value, so two unrelated enums compare equal without a peep. Since Python 3.11 str() and f-strings render an IntEnum member as the bare number instead of Color.RED, which silently rewrites log lines when old code moves to a newer version. Reach for IntEnum to interoperate with code and APIs that expect ints; for logic of your own, plain Enum is safer."
      },
      "syntax": "class C(enum.IntEnum): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.IntEnum",
      "version": "",
      "section": "Модуль enum",
      "subcat": "виды enum",
      "color_group": "module",
      "aliases": [
        "числовое перечисление",
        "перечисление вместо числовых констант"
      ],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "class Color(enum.IntEnum):",
        "    RED = 1",
        "    GREEN = 2",
        "print(Color.RED == 1)        # → True",
        "print(int(Color.GREEN) + 1)  # → 3"
      ],
      "related": [
        "enum.StrEnum",
        "intenum-strenum",
        "enum.ReprEnum",
        "enum.IntFlag"
      ],
      "related_errors": []
    },
    {
      "id": "enum.IntFlag",
      "title": "enum.IntFlag",
      "kind": "term",
      "summary": {
        "ru": "Битовые флаги, которые ещё и int: комбинируются и сравниваются как числа.",
        "en": "Bit flags that are also ints (combine and compare as numbers)."
      },
      "body": {
        "ru": "У IntFlag по умолчанию boundary=KEEP: значение с битом, которого нет ни в одном члене, не бросит ошибку, а сохранится как есть — испорченную маску придётся ловить самому. Плюс член свободно сравнивается и участвует в арифметике с обычными int, так что смешать его с посторонним числом легко. Разумно брать IntFlag там, где значение уходит наружу — в системный вызов, флаги re, упакованное битовое поле; для внутренней логики чище Flag.",
        "en": "IntFlag defaults to boundary=KEEP: a value carrying a bit that no member defines is accepted and kept rather than rejected, so a corrupt mask stays silent until it bites. Members also compare and do arithmetic with ordinary ints, which makes accidental mixing with unrelated numbers easy. Use IntFlag where the value crosses out of your program (system calls, re flags, packed bit fields); for logic that stays inside, Flag is cleaner."
      },
      "syntax": "class C(enum.IntFlag): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.IntFlag",
      "version": "",
      "section": "Модуль enum",
      "subcat": "флаги",
      "color_group": "module",
      "aliases": [
        "флаги, которые ведут себя как числа",
        "битовая маска целым числом"
      ],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "class Mode(enum.IntFlag):",
        "    A = 1",
        "    B = 2",
        "print((Mode.A | Mode.B) == 3)   # → True"
      ],
      "related": [
        "enum.Flag",
        "flag-intflag",
        "enum.IntEnum",
        "битовые-операции"
      ],
      "related_errors": []
    },
    {
      "id": "enum.ReprEnum",
      "title": "enum.ReprEnum",
      "kind": "term",
      "summary": {
        "ru": "Базовый класс для IntEnum/StrEnum: сохраняет repr подмешанного типа (int/str), а не Enum (Python 3.11+).",
        "en": "The base for IntEnum/StrEnum: keeps the mixed-in type's repr (3.11+)."
      },
      "body": {
        "ru": "Вопреки названию, ReprEnum трогает не repr, а str() и format(): repr остаётся «энумовским» (<Size.S: 'small'>), зато f-строка и str() дают значение подмешанного типа. Он появился в 3.11 потому, что тогда обычные смешанные перечисления (class Foo(str, Enum)) начали печататься как Foo.A, и IntEnum/StrEnum нужно было спасти от этой ломки. Наследуйтесь от него, если делаете собственный миксин (например с float) и хотите, чтобы член в строке выглядел как его значение.",
        "en": "Despite the name, ReprEnum leaves repr alone (<Size.S: 'small'>) and instead borrows str() and format() from the mixed-in type. It exists because 3.11 made plain mixins like class Foo(str, Enum) print as Foo.A, and IntEnum/StrEnum had to be shielded from that change. Inherit from it when you build your own mixin enum and want members to render as their values in f-strings."
      },
      "syntax": "class C(int, enum.ReprEnum): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.ReprEnum",
      "version": "3.11",
      "section": "Модуль enum",
      "subcat": "виды enum",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "print(issubclass(enum.IntEnum, enum.ReprEnum))   # → True",
        "class Size(int, enum.ReprEnum): BIG = 10",
        "print(Size.BIG)   # → 10",
        "print(repr(Size.BIG))   # → <Size.BIG: 10>",
        "class Plain(enum.Enum): BIG = 10",
        "print(Plain.BIG)   # → Plain.BIG"
      ],
      "related": [
        "enum.IntEnum",
        "enum.StrEnum",
        "intenum-strenum"
      ],
      "related_errors": []
    },
    {
      "id": "enum.StrEnum",
      "title": "enum.StrEnum",
      "kind": "term",
      "summary": {
        "ru": "Перечисление, члены которого — ещё и str (Python 3.11+): ведут себя как строки.",
        "en": "An enumeration whose members are also str (3.11+)."
      },
      "body": {
        "ru": "Главная ловушка — при замене привычного class C(str, Enum) на StrEnum меняется вывод: в 3.11+ у обычного миксина str() и f-строка дают 'C.S', а у StrEnum — 'small'. Ещё одна неочевидность: auto() здесь возвращает не число, а имя члена в нижнем регистре, и все значения обязаны быть строками — int в теле класса вызовет ошибку.",
        "en": "The trap shows up when you swap a hand-rolled class C(str, Enum) for StrEnum: since 3.11 the plain mixin renders as 'C.S' in str() and f-strings, while StrEnum renders as 'small'. Also non-obvious: auto() yields the member name lowercased rather than a number, and every value must be a string — a bare int in the class body raises."
      },
      "syntax": "class C(enum.StrEnum): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.StrEnum",
      "version": "3.11",
      "section": "Модуль enum",
      "subcat": "виды enum",
      "color_group": "module",
      "aliases": [
        "строковое перечисление",
        "перечисление ведёт себя как строка"
      ],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "class Size(enum.StrEnum):",
        "    S = 'small'",
        "    L = 'large'",
        "print(Size.S == 'small')   # → True",
        "print(Size.S.upper())      # → SMALL"
      ],
      "related": [
        "enum.IntEnum",
        "intenum-strenum",
        "enum.ReprEnum"
      ],
      "related_errors": []
    },
    {
      "id": "enum.global_enum",
      "title": "enum.global_enum",
      "kind": "function",
      "summary": {
        "ru": "Декоратор класса-перечисления: переносит его члены в глобальное пространство имён модуля (доступ по имени без префикса) и настраивает их repr под «модульный» стиль.",
        "en": "A class decorator that injects an enum's members into the module globals and adjusts their repr."
      },
      "body": {
        "ru": "Декоратор придуман ради стандартной библиотеки, где константы исторически лежат прямо в модуле (re.IGNORECASE, socket.AF_INET), а перечисление добавили позже — он даёт новый класс, не ломая старый способ обращения. В своём коде применять его почти всегда не стоит: имена появляются в globals() в обход обычного присваивания, поэтому линтеры и автодополнение IDE их не видят, а случайное совпадение имён молча затрёт существующую переменную модуля.",
        "en": "The decorator exists for the standard library, where constants historically sat at module level (re.IGNORECASE, socket.AF_INET) and the enum class arrived later — it adds the class without breaking the old access path. In your own code it is rarely a good idea: the names land in globals() without an actual assignment, so linters and IDE completion miss them, and a name clash silently overwrites an existing module-level variable."
      },
      "syntax": "@enum.global_enum",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.global_enum",
      "version": "3.11",
      "section": "Модуль enum",
      "subcat": "глобализация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum, re",
        "print(callable(enum.global_enum))   # → True",
        "print(repr(re.IGNORECASE))   # → re.IGNORECASE",
        "@enum.global_enum",
        "class Color(enum.Enum): RED = 1; GREEN = 2",
        "print(RED is Color.RED)   # → True",
        "print(RED)   # → RED"
      ],
      "related": [
        "enum.global_enum_repr",
        "enum.global_str",
        "enum.global_flag_repr",
        "enum.pickle_by_global_name"
      ],
      "related_errors": []
    },
    {
      "id": "enum.global_enum_repr",
      "title": "enum.global_enum_repr",
      "kind": "function",
      "summary": {
        "ru": "Функция repr для членов перечисления, «глобализированного» через global_enum: показывает имя как модульную константу (module.MEMBER).",
        "en": "The repr function for members of a global_enum-decorated enum (shows module.MEMBER)."
      },
      "body": {
        "ru": "Эту функцию почти никогда не вызывают вручную — её ставит как __repr__ декоратор global_enum; напрямую она нужна, только если пишете свой похожий декоратор. Префикс модуля берётся из __module__ самого класса перечисления, так что при реэкспорте члена из другого модуля в repr всё равно окажется имя модуля, где enum объявлен.",
        "en": "You almost never call this yourself: global_enum installs it as the class's __repr__, and the function is only interesting if you write a similar decorator of your own. The module prefix comes from the enum class's own __module__, so re-exporting a member elsewhere still shows the module where the enum was defined."
      },
      "syntax": "enum.global_enum_repr(member)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#utilities-and-decorators",
      "version": "",
      "section": "Модуль enum",
      "subcat": "глобализация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum, re",
        "print(callable(enum.global_enum_repr))   # → True",
        "print(enum.global_enum_repr(enum.FlagBoundary.STRICT))   # → enum.STRICT",
        "print(enum.global_enum_repr(re.RegexFlag.IGNORECASE))   # → re.IGNORECASE",
        "print(repr(re.IGNORECASE))   # → re.IGNORECASE",
        "class Color(enum.Enum): RED = 1",
        "print(repr(Color.RED))   # → <Color.RED: 1>"
      ],
      "related": [
        "enum.global_enum",
        "enum.global_flag_repr",
        "enum.global_str"
      ],
      "related_errors": []
    },
    {
      "id": "enum.global_flag_repr",
      "title": "enum.global_flag_repr",
      "kind": "function",
      "summary": {
        "ru": "Функция repr для членов флагового перечисления (Flag), глобализированного через global_enum.",
        "en": "The repr function for members of a global_enum-decorated Flag."
      },
      "body": {
        "ru": "Отдельная функция для Flag нужна потому, что у комбинации битов нет одного имени: repr выводит каждый флаг с префиксом модуля через вертикальную черту (re.ASCII|re.IGNORECASE), а не одно имя, как у обычного enum. Если значение не раскладывается на именованные флаги, вывод падает обратно к виду module.Class(число).",
        "en": "Flags need their own function because a combined value has no single name: the repr prints each flag with the module prefix joined by a pipe (re.ASCII|re.IGNORECASE) instead of one name as a plain enum would. When a value has no named flags behind it, the output falls back to the module.Class(number) form."
      },
      "syntax": "enum.global_flag_repr(member)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#utilities-and-decorators",
      "version": "",
      "section": "Модуль enum",
      "subcat": "глобализация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "import re",
        "print(callable(enum.global_flag_repr))   # → True",
        "print(repr(re.IGNORECASE))   # → re.IGNORECASE",
        "print(repr(re.IGNORECASE | re.MULTILINE))   # → re.IGNORECASE|re.MULTILINE",
        "print(repr(re.RegexFlag.DOTALL))   # → re.DOTALL",
        "print(re.RegexFlag.__repr__ is enum.global_flag_repr)   # → True"
      ],
      "related": [
        "enum.global_enum_repr",
        "enum.global_enum",
        "enum.Flag"
      ],
      "related_errors": []
    },
    {
      "id": "enum.global_str",
      "title": "enum.global_str",
      "kind": "function",
      "summary": {
        "ru": "Функция __str__ для членов глобализированного перечисления: печатает голое имя члена без префикса класса.",
        "en": "The __str__ function for members of a globalized enum (bare member name)."
      },
      "body": {
        "ru": "Напрямую её почти никогда не вызывают: это деталь механики декоратора @global_enum, который дублирует члены перечисления в глобальном пространстве модуля (как re.IGNORECASE) и подменяет __str__ на эту функцию. Смысл в том, чтобы print(IGNORECASE) и f-строка печатали ровно то имя, под которым константа доступна в модуле, а не RegexFlag.IGNORECASE. Побочный эффект — str() и repr() у такого члена расходятся, и это нормально.",
        "en": "You essentially never call this yourself: it is plumbing for the @global_enum decorator, which copies members into the module's global namespace (think re.IGNORECASE) and swaps __str__ for this function. The point is that print() and f-strings show the exact name the constant is reachable by in the module, not the ClassName.MEMBER form. As a side effect str() and repr() of such a member no longer agree, which is intended."
      },
      "syntax": "enum.global_str(member)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#utilities-and-decorators",
      "version": "",
      "section": "Модуль enum",
      "subcat": "глобализация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "print(callable(enum.global_str))   # → True",
        "Color = enum.Enum('Color', 'RED GREEN')",
        "print(str(Color.RED))   # → Color.RED",
        "print(enum.global_str(Color.RED))   # → RED",
        "Color.__str__ = enum.global_str",
        "print(str(Color.GREEN))   # → GREEN",
        "print(repr(Color.GREEN))   # → <Color.GREEN: 2>"
      ],
      "related": [
        "enum.global_enum",
        "enum.global_enum_repr",
        "enum.global_flag_repr"
      ],
      "related_errors": []
    },
    {
      "id": "enum.member",
      "title": "enum.member",
      "kind": "term",
      "summary": {
        "ru": "Обёртка, принудительно делающая значение членом перечисления (Python 3.11+); полезно для функций/дескрипторов.",
        "en": "A wrapper forcing a value to become an enum member (3.11+)."
      },
      "body": {
        "ru": "Нужна ровно там, где обычные правила Enum срабатывают против вас: функция, lambda, дескриптор или вложенный класс в теле перечисления членом не становятся — Python считает их методами класса. Обёртка снимает это исключение и заставляет значение стать полноценным членом с .name и .value. Для обычных чисел и строк она избыточна, а до Python 3.11 приходилось хитрить, заворачивая функцию, например, в functools.partial.",
        "en": "It exists for the cases where the usual Enum rules work against you: a function, lambda, descriptor or nested class written in the enum body is treated as a method, not a member. Wrapping it makes it a real member with .name and .value. For plain numbers and strings the wrapper is pointless, and before Python 3.11 people faked it by hiding the callable inside something like functools.partial."
      },
      "syntax": "X = enum.member(value)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.member",
      "version": "3.11",
      "section": "Модуль enum",
      "subcat": "члены",
      "color_group": "module",
      "aliases": [
        "сделать значение членом перечисления",
        "функция как член перечисления",
        "принудительно добавить в перечисление"
      ],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "class C(enum.Enum):",
        "    X = enum.member(1)",
        "print(C.X.value)   # → 1"
      ],
      "related": [
        "enum.nonmember",
        "enum",
        "auto"
      ],
      "related_errors": []
    },
    {
      "id": "enum.nonmember",
      "title": "enum.nonmember",
      "kind": "term",
      "summary": {
        "ru": "Обёртка, не дающая значению стать членом перечисления — остаётся обычным атрибутом класса (Python 3.11+).",
        "en": "A wrapper preventing a value from becoming an enum member (3.11+)."
      },
      "body": {
        "ru": "Обратная задача: любое обычное значение в теле Enum молча становится членом, попадает в len(), в итерацию и в поиск по значению — nonmember оставляет его простым атрибутом класса. Обратите внимание, что C.HELPER после этого возвращает голое значение: у него нет .name и .value, и C(99) поднимет ValueError. Тот же эффект даёт имя с ведущим подчёркиванием, но nonmember нужен, когда константа должна остаться публичной.",
        "en": "This is the mirror problem: any ordinary value in an Enum body silently becomes a member, counted by len(), yielded by iteration and reachable by value lookup, while nonmember keeps it an ordinary class attribute. Note that C.HELPER then gives you the raw value, with no .name or .value, and C(99) raises ValueError. A leading underscore in the name achieves the same exclusion, so reach for nonmember when the constant must stay public."
      },
      "syntax": "H = enum.nonmember(value)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.nonmember",
      "version": "3.11",
      "section": "Модуль enum",
      "subcat": "члены",
      "color_group": "module",
      "aliases": [
        "исключить значение из перечисления",
        "обычный атрибут в перечислении",
        "константа не член перечисления"
      ],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "class C(enum.Enum):",
        "    X = 1",
        "    HELPER = enum.nonmember(99)",
        "print(C.HELPER)   # → 99",
        "print(len(C))     # → 1"
      ],
      "related": [
        "enum.member",
        "enum",
        ".name-.value"
      ],
      "related_errors": []
    },
    {
      "id": "enum.pickle_by_enum_name",
      "title": "enum.pickle_by_enum_name",
      "kind": "function",
      "summary": {
        "ru": "Функция-редьюсер для pickle: сериализует член перечисления по паре (класс, имя члена) вместо значения — надёжнее при изменении значений.",
        "en": "A pickle reducer that serializes an enum member by (class, member name) instead of by value."
      },
      "body": {
        "ru": "По умолчанию член перечисления пикуется по значению — в потоке фактически лежит вызов Colour(1). Если значения потом сдвинутся (особенно при auto()), старый pickle не упадёт, а тихо развернётся в другой член; хранение по имени такой ошибки не допускает. Присваивать редьюсер надо в теле класса перечисления: __reduce_ex__ = enum.pickle_by_enum_name.",
        "en": "By default an enum member is pickled by value, so the stream effectively holds a Colour(1) call. If the values later shift, typically because they came from auto(), an old pickle will not fail loudly but quietly resolve to a different member; storing the name instead removes that trap. Assign the reducer inside the enum class body as __reduce_ex__ = enum.pickle_by_enum_name."
      },
      "syntax": "member.__reduce_ex__ = enum.pickle_by_enum_name",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#utilities-and-decorators",
      "version": "",
      "section": "Модуль enum",
      "subcat": "pickle",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "print(callable(enum.pickle_by_enum_name))   # → True",
        "Color = enum.Enum('Color', 'RED GREEN')",
        "print(Color.RED.__reduce_ex__(2)[1])   # → (1,)",
        "func, args = enum.pickle_by_enum_name(Color.RED, 2)",
        "print(func is getattr)   # → True",
        "print(args[1])   # → RED",
        "print(func(*args) is Color.RED)   # → True"
      ],
      "related": [
        "enum.pickle_by_global_name",
        "pickle",
        ".name-.value"
      ],
      "related_errors": []
    },
    {
      "id": "enum.pickle_by_global_name",
      "title": "enum.pickle_by_global_name",
      "kind": "function",
      "summary": {
        "ru": "Функция-редьюсер для pickle: сериализует член глобализированного перечисления по его глобальному имени в модуле.",
        "en": "A pickle reducer that serializes a globalized enum member by its module-global name."
      },
      "body": {
        "ru": "Подходит только перечислениям под @global_enum, члены которых продублированы в globals() модуля: pickle тогда хранит обычную ссылку вида module.NAME. Если такое имя из модуля исчезнет или его переименуют, распаковка упадёт на поиске атрибута, поэтому для обычных, не глобализированных перечислений берите pickle_by_enum_name.",
        "en": "This one only fits enums decorated with @global_enum, whose members also live in the module globals: the pickle then stores a plain module.NAME reference. If that global name is later removed or renamed, unpickling fails during lookup, so for ordinary non-globalized enums use pickle_by_enum_name instead."
      },
      "syntax": "member.__reduce_ex__ = enum.pickle_by_global_name",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#utilities-and-decorators",
      "version": "",
      "section": "Модуль enum",
      "subcat": "pickle",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "print(callable(enum.pickle_by_global_name))   # → True",
        "Color = enum.Enum('Color', 'RED GREEN')",
        "print(enum.pickle_by_global_name(Color.GREEN, 2))   # → GREEN",
        "print(isinstance(enum.pickle_by_global_name(Color.GREEN, 2), str))   # → True",
        "print(Color.GREEN.__reduce_ex__(2)[1])   # → (2,)",
        "print(enum.pickle_by_enum_name(Color.GREEN, 2)[0] is getattr)   # → True"
      ],
      "related": [
        "enum.pickle_by_enum_name",
        "enum.global_enum",
        "pickle"
      ],
      "related_errors": []
    },
    {
      "id": "enum.verify",
      "title": "enum.verify",
      "kind": "term",
      "summary": {
        "ru": "Декоратор класса, проверяющий ограничения перечисления (UNIQUE/CONTINUOUS/NAMED_FLAGS) при определении (Python 3.11+).",
        "en": "A class decorator enforcing enum constraints (UNIQUE/CONTINUOUS/NAMED_FLAGS) at definition (3.11+)."
      },
      "body": {
        "ru": "Проверка срабатывает один раз, в момент создания класса, — это не валидация значений во время работы программы, и требуется Python 3.11+. Режим UNIQUE дословно повторяет декоратор @unique, поэтому реальный смысл verify в двух других: CONTINUOUS требует, чтобы значения шли подряд без пропусков от наименьшего к наибольшему, а NAMED_FLAGS — чтобы каждый бит составного значения флага был покрыт именованным членом.",
        "en": "The check runs once, when the class is created — it is not runtime validation of values, and it needs Python 3.11+. The UNIQUE mode is exactly what the @unique decorator already does, so verify earns its place through the other two: CONTINUOUS demands values with no gaps between the lowest and the highest, and NAMED_FLAGS demands that every bit of a composite flag value be covered by a named member."
      },
      "syntax": "@enum.verify(enum.UNIQUE)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.verify",
      "version": "3.11",
      "section": "Модуль enum",
      "subcat": "проверки",
      "color_group": "module",
      "aliases": [
        "проверить ограничения перечисления",
        "проверка непрерывности значений перечисления",
        "валидация перечисления при объявлении"
      ],
      "keywords": [],
      "tags": [
        "enum"
      ],
      "examples": [
        "import enum",
        "@enum.verify(enum.UNIQUE)",
        "class C(enum.Enum):",
        "    A = 1",
        "    B = 2",
        "print(len(C))   # → 2"
      ],
      "related": [
        "enum.EnumCheck",
        "unique",
        "enum.FlagBoundary"
      ],
      "related_errors": []
    },
    {
      "id": "flag-intflag",
      "title": "Flag / IntFlag",
      "kind": "term",
      "summary": {
        "ru": "Flag и IntFlag поддерживают битовые операции: объединение через |, проверку через &.",
        "en": "Flag and IntFlag support bitwise operations: members are combined with | and tested with &."
      },
      "body": {
        "ru": "Результат & — это снова флаг, а не bool; пустой флаг ложен, поэтому проверку принадлежности удобнее писать оператором in, чем сравнивать с нулём. IntFlag наследуется от int, и его члены незаметно утекают в арифметику, ключи словарей и сериализацию как обычные числа — если такая совместимость не нужна, берите Flag. Начиная с 3.11 составной флаг можно итерировать: он отдаёт входящие в него одиночные биты.",
        "en": "An & of two flags yields a flag, not a bool; the empty flag is falsy, so membership reads better with the in operator than with a comparison against zero. IntFlag subclasses int, so its members quietly leak into arithmetic, dict keys and serialization as plain numbers — pick Flag when you do not need that compatibility. Since 3.11 a composite flag is iterable and yields the single-bit members it is made of."
      },
      "syntax": "from enum import Flag, IntFlag\nclass Perm(Flag):\n    R = 1; W = 2; X = 4",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.Flag",
      "version": "",
      "section": "Модуль enum",
      "subcat": "флаги",
      "color_group": "module",
      "aliases": [
        "битовые операции с флагами",
        "объединить и проверить флаги",
        "права доступа флагами"
      ],
      "keywords": [
        "Flag",
        "IntFlag"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from enum import Flag, auto",
        "class Perm(Flag):",
        "READ = auto()",
        "WRITE = auto()",
        "EXEC = auto()",
        "rw = Perm.READ | Perm.WRITE",
        "Perm.READ in rw # → True",
        "Perm.EXEC in rw # → False",
        "rw & Perm.WRITE # → <Perm.WRITE: 2>"
      ],
      "related": [
        "enum.Flag",
        "enum.IntFlag",
        "битовые-операции",
        "intenum-strenum"
      ],
      "related_errors": []
    },
    {
      "id": "intenum-strenum",
      "title": "IntEnum / StrEnum",
      "kind": "term",
      "summary": {
        "ru": "IntEnum — перечисление, совместимое с int. StrEnum — совместимое со str (Python 3.11+).",
        "en": "IntEnum is an enumeration compatible with int. StrEnum is compatible with str (Python 3.11+)."
      },
      "body": {
        "ru": "Совместимость работает в обе стороны и без типобезопасности: член IntEnum равен обычному числу, а значит и члену совсем другого IntEnum с тем же значением — это цена за то, чтобы значение принимали старый код, БД и бинарные протоколы. Отличие от обычного Enum заметнее всего при выводе: str() и f-строка дают само число или строку, тогда как обычный Enum напечатает имя вида Color.RED. В StrEnum вызов auto() подставляет имя члена в нижнем регистре, а не порядковый номер.",
        "en": "The compatibility cuts both ways and costs you type safety: an IntEnum member equals a plain number, and therefore also equals a member of a completely unrelated IntEnum holding the same value — that is the price of being accepted by legacy code, databases and binary protocols. The difference from a plain Enum shows up in output: str() and f-strings give the bare number or string, while a plain Enum prints a name like Color.RED. In StrEnum, auto() fills in the member name lowercased rather than a counter."
      },
      "syntax": "class X(IntEnum): ...\nclass Y(StrEnum): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.IntEnum",
      "version": "3.11",
      "section": "Модуль enum",
      "subcat": "базовый",
      "color_group": "module",
      "aliases": [
        "смешанные перечисления с числом и строкой",
        "разница числового и строкового перечисления"
      ],
      "keywords": [
        "IntEnum",
        "StrEnum"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from enum import IntEnum",
        "class Prio(IntEnum):",
        "LOW = 1; MED = 2; HIGH = 3",
        "Prio.HIGH > Prio.LOW # → True",
        "Prio.MED + 1 # → 3",
        "from enum import StrEnum",
        "class Status(StrEnum):",
        "OK = 'ok'; ERR = 'error'",
        "Status.OK == 'ok' # → True",
        "f'status: {Status.ERR}' # → 'status: error'"
      ],
      "related": [
        "enum",
        "enum.IntEnum",
        "enum.StrEnum",
        "flag-intflag"
      ],
      "related_errors": []
    },
    {
      "id": "unique",
      "title": "@unique",
      "kind": "term",
      "summary": {
        "ru": "Декоратор @unique запрещает дублирование значений в перечислении — вызывает ValueError при нарушении.",
        "en": "The @unique decorator forbids duplicate values in an enumeration — it raises ValueError when there are any."
      },
      "body": {
        "ru": "Без этого декоратора повтор значения не ошибка: второе имя молча становится псевдонимом первого члена, оба имени указывают на один и тот же объект, при итерации виден только первый, а в __members__ присутствуют оба. Ставьте @unique там, где дубликат — это почти наверняка опечатка; если псевдонимы задуманы специально (старое и новое название одного состояния), декоратор придётся убрать.",
        "en": "Without the decorator a repeated value is not an error at all: the second name silently becomes an alias of the first member, both names point at one object, iteration shows only the first, and __members__ holds both. Reach for @unique where a duplicate almost certainly means a typo; if the aliases are deliberate (an old and a new name for the same state), the decorator has to go."
      },
      "syntax": "from enum import unique\n@unique\nclass X(Enum): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/enum.html#enum.unique",
      "version": "",
      "section": "Модуль enum",
      "subcat": "проверка",
      "color_group": "module",
      "aliases": [
        "запретить дубликаты значений перечисления",
        "проверка уникальности членов перечисления",
        "запретить псевдонимы членов"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from enum import Enum, unique",
        "@unique",
        "class Status(Enum):",
        "ACTIVE = 1; INACTIVE = 2",
        "# @unique",
        "# class Bad(Enum):",
        "#     A = 1; B = 1  # → ValueError!",
        "Status.ACTIVE.value # → 1",
        "Status.INACTIVE.value # → 2"
      ],
      "related": [
        "enum.verify",
        "enum.EnumCheck",
        "valueerror",
        "enum"
      ],
      "related_errors": []
    },
    {
      "id": "итерация-и-сравнение-enum",
      "title": "Итерация и сравнение Enum",
      "kind": "term",
      "summary": {
        "ru": "Перечисления поддерживают итерацию по list(MyEnum) и сравнение через is / ==. IntEnum поддерживает <, >.",
        "en": "Enumerations support iteration through list(MyEnum) and comparison with is / ==. IntEnum also supports < and >."
      },
      "body": {
        "ru": "Обычный Enum не равен своему значению: сравнение члена с числом или строкой даёт False — нужен либо .value, либо IntEnum/StrEnum. Итерация идёт в порядке определения, а не по возрастанию значений, и пропускает псевдонимы; чтобы увидеть их, смотрите __members__. Члены — синглтоны, поэтому для сравнения достаточно is, и это надёжнее ==, если в проект вдруг попадут два одинаковых по значению перечисления.",
        "en": "A plain Enum member is not equal to its own value: comparing it with a number or a string gives False, so use .value or switch to IntEnum/StrEnum. Iteration follows definition order, not value order, and skips aliases — look at __members__ to see those. Members are singletons, so is is enough for comparison and it stays correct even if two different enums end up sharing values."
      },
      "syntax": "list(MyEnum)  # все элементы\nfor m in MyEnum: ...\nmember is MyEnum.X",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/howto/enum.html#iteration",
      "version": "3.11",
      "section": "Модуль enum",
      "subcat": "базовый",
      "color_group": "module",
      "aliases": [
        "перебрать все элементы перечисления",
        "получить список членов перечисления",
        "сравнение членов перечисления"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from enum import Enum",
        "class Day(Enum):",
        "MON=1; TUE=2; WED=3",
        "list(Day) # → [<Day.MON:1>, <Day.TUE:2>, <Day.WED:3>]",
        "Day.MON is Day.MON # → True",
        "Day.MON == Day.MON # → True",
        "Day.MON == 1 # → False (Enum не совместим с int)",
        "Day.MON in Day # → True"
      ],
      "related": [
        "enum",
        ".name-.value",
        "is-is-not",
        "intenum-strenum"
      ],
      "related_errors": []
    },
    {
      "id": "functools.cache",
      "title": "functools.cache",
      "kind": "function",
      "summary": {
        "ru": "Декоратор: простой неограниченный кеш (как lru_cache(maxsize=None)), добавлен в Python 3.9.",
        "en": "Decorator for a simple unbounded cache (like lru_cache(maxsize=None)); added in 3.9."
      },
      "body": {
        "ru": "cache ничего не вытесняет: словарь растёт, пока жива функция, поэтому на данных от пользователя он рискованнее lru_cache с ограничением — а на методах кеш ещё и держит ссылку на self, и объект не умирает. Взамен он чуть быстрее lru_cache: не нужно отслеживать порядок обращений и вытеснять старое. Аргументы обязаны быть хешируемыми, список или словарь дадут TypeError.",
        "en": "cache never evicts anything: the dictionary keeps growing for as long as the function lives, so it is riskier than a bounded lru_cache on user-supplied input — and on methods it also keeps a reference to self alive. In exchange it is slightly faster, since there is no recency bookkeeping or eviction. Arguments must be hashable; a list or dict raises TypeError."
      },
      "syntax": "@functools.cache",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.cache",
      "version": "3.9",
      "section": "Модуль functools",
      "subcat": "functools",
      "color_group": "module",
      "aliases": [
        "мемоизация",
        "кеш результатов функции",
        "запоминание вычислений"
      ],
      "keywords": [],
      "tags": [
        "functools"
      ],
      "examples": [
        "import functools",
        "@functools.cache",
        "def f(n):",
        "    return n + 1",
        "print(f(10), f(10))   # → 11 11"
      ],
      "related": [
        "functools.lru_cache"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "functools.cached_property",
      "title": "functools.cached_property",
      "kind": "term",
      "summary": {
        "ru": "Вычисляет значение свойства один раз и сохраняет в __dict__ экземпляра. При повторном обращении возвращает кэш без вызова метода.",
        "en": "Computes the value of a property once and stores it in the instance's __dict__. Later accesses return the cached value without calling the method."
      },
      "body": {
        "ru": "Кеш сам не сбрасывается: если поля объекта изменились, свойство продолжит отдавать старое значение, пока его не удалить через del obj.area. Работает только у классов с обычным __dict__ — с __slots__ не заведётся, потому что значение просто некуда положить. С Python 3.12 общей блокировки больше нет, так что в многопоточном коде значение может посчитаться несколько раз.",
        "en": "The cache never invalidates itself: if the object's fields change, the property keeps returning the stale value until you delete it with del obj.area. It needs a normal instance __dict__, so a class with __slots__ will not work — there is nowhere to store the value. Since Python 3.12 there is no shared lock, so under threads the computation may run more than once."
      },
      "syntax": "@functools.cached_property",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.cached_property",
      "version": "3.8",
      "section": "Модуль functools",
      "subcat": "кэширование",
      "color_group": "module",
      "aliases": [
        "кешируемое свойство",
        "вычислить свойство один раз",
        "ленивое свойство"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from functools import cached_property",
        "class Circle:",
        "    def __init__(self, r): self.r = r",
        "    @cached_property",
        "    def area(self):",
        "        return 3.14159 * self.r ** 2",
        "c = Circle(5)",
        "print(c.area)  # → 78.53975",
        "print(c.area)  # → 78.53975  (без вычисления)",
        "print('area' in c.__dict__)  # → True",
        "print(type(c.__dict__['area']))  # → <class 'float'>"
      ],
      "related": [
        "property",
        "functools.cache",
        "functools.lru_cache",
        "__slots__"
      ],
      "related_errors": []
    },
    {
      "id": "functools.cmp_to_key",
      "title": "functools.cmp_to_key",
      "kind": "function",
      "summary": {
        "ru": "Превращает старую функцию сравнения cmp(a, b)→int в key-функцию для sorted()/min()/max().",
        "en": "Convert an old-style cmp(a, b) comparison function into a key function for sorted()."
      },
      "body": {
        "ru": "Нужен только там, где порядок нельзя выразить одним ключом, потому что решение зависит сразу от пары элементов — классика вроде «склеить числа так, чтобы получилось наибольшее». В остальных случаях обычный key= быстрее: ключ считается один раз на элемент, а функция сравнения дёргается порядка n log n раз, да ещё через объект-обёртку. И cmp должна возвращать отрицательное число, ноль или положительное, а не True/False.",
        "en": "Reach for it only when the ordering cannot be expressed as a single key because the decision depends on a pair of elements at once — the classic case is arranging numbers to form the largest possible number. Otherwise a plain key= wins: the key is computed once per element, while a comparison function is called about n log n times through a wrapper object. Also, cmp must return a negative number, zero or a positive number — not True/False."
      },
      "syntax": "functools.cmp_to_key(cmp_func)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.cmp_to_key",
      "version": "3.2",
      "section": "Модуль functools",
      "subcat": "functools",
      "color_group": "module",
      "aliases": [
        "компаратор для сортировки",
        "своя функция сравнения при сортировке",
        "сравнение двух элементов в сортировке"
      ],
      "keywords": [],
      "tags": [
        "functools"
      ],
      "examples": [
        "import functools",
        "def cmp(a, b):",
        "    return a - b",
        "print(sorted([3, 1, 2], key=functools.cmp_to_key(cmp)))   # → [1, 2, 3]"
      ],
      "related": [
        "sorted-с-key",
        "sorted",
        "operator.itemgetter",
        "list.sort"
      ],
      "related_errors": []
    },
    {
      "id": "functools.lru_cache",
      "title": "functools.lru_cache",
      "kind": "function",
      "summary": {
        "ru": "Декоратор: кеширует результаты функции по аргументам (LRU, ограничен maxsize). Ускоряет повторные и рекурсивные вызовы.",
        "en": "Decorator caching a function's results by arguments (LRU-bounded by maxsize)."
      },
      "body": {
        "ru": "Ключом кеша служат сами аргументы, поэтому f(2) и f(n=2) занимают разные ячейки, а список или словарь в аргументах немедленно дают TypeError: unhashable. Кеш один на функцию и живёт до конца программы: навешанный на метод, он удерживает self и мешает сборке мусора — чистить через f.cache_clear(). Для рекурсии дефолтного maxsize=128 часто мало, а maxsize=None (то же самое, что functools.cache) заодно убирает накладные расходы на вытеснение.",
        "en": "The cache key is built from the arguments themselves, so f(2) and f(n=2) occupy different slots, and passing a list or dict raises TypeError: unhashable straight away. There is one cache per function and it lives for the whole program: applied to a method it pins self and blocks garbage collection — clear it with f.cache_clear(). For recursion the default maxsize=128 is often too small, while maxsize=None (exactly what functools.cache does) also drops the eviction overhead."
      },
      "syntax": "@functools.lru_cache(maxsize=128)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.lru_cache",
      "version": "3.2",
      "section": "Модуль functools",
      "subcat": "functools",
      "color_group": "module",
      "aliases": [
        "кеширование результатов функции",
        "ускорить рекурсию кешем",
        "кеш последних вызовов"
      ],
      "keywords": [],
      "tags": [
        "functools"
      ],
      "examples": [
        "import functools",
        "@functools.lru_cache",
        "def sq(n):",
        "    return n * n",
        "print(sq(4))                        # → 16",
        "print(sq(4), sq.cache_info().hits)  # → 16 1"
      ],
      "related": [
        "functools.cache"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "functools.partial",
      "title": "functools.partial",
      "kind": "term",
      "summary": {
        "ru": "Создаёт новую функцию с частично заполненными аргументами (частичное применение).",
        "en": "Creates a new function with some of the arguments already filled in (partial application)."
      },
      "body": {
        "ru": "Аргументы фиксируются в момент создания partial, а не в момент вызова — этим он и отличается от lambda с поздним связыванием, из-за которого в цикле все замыкания видят последнее значение переменной. Позиционные аргументы приклеиваются слева, поэтому «пропустить» первый параметр и заполнить только второй можно лишь по имени. В отличие от лямбды, объект partial хранит func, args и keywords, читаемо печатается при отладке и сериализуется через pickle.",
        "en": "Arguments are frozen when the partial is created, not when it is called — that is the difference from a lambda, whose late binding makes every closure in a loop see the variable's final value. Positional arguments are glued on from the left, so you cannot skip the first parameter and pre-fill only the second except by keyword. Unlike a lambda, a partial object exposes func, args and keywords, prints readably while debugging, and can be pickled."
      },
      "syntax": "from functools import partial\nfunctools.partial(func, /, *args, **kwargs) -> partial",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.partial",
      "version": "",
      "section": "Модуль functools",
      "subcat": "частичное",
      "color_group": "module",
      "aliases": [
        "частичное применение функции",
        "зафиксировать часть аргументов",
        "функция с заранее заданными аргументами"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from functools import partial",
        "def power(base, exp):",
        "    return base ** exp",
        "square = partial(power, exp=2)",
        "print(square(4))  # → 16",
        "cube = partial(power, exp=3)",
        "print(cube(3))  # → 27",
        "add = lambda a, b: a + b",
        "add10 = partial(add, 10)",
        "print(add10(5))  # → 15",
        "print_sep = partial(print, sep=', ')",
        "print_sep(1, 2, 3)  # → 1, 2, 3",
        "# partial с методом",
        "from functools import partial",
        "int2 = partial(int, base=2)",
        "print(int2('1010'))  # → 10"
      ],
      "related": [
        "functools.partialmethod",
        "lambda",
        "замыкания"
      ],
      "related_errors": []
    },
    {
      "id": "functools.partialmethod",
      "title": "functools.partialmethod",
      "kind": "function",
      "summary": {
        "ru": "Дескриптор для частичного применения метода: как partial, но для методов класса (учитывает self).",
        "en": "A descriptor for partial application of a method (like partial, but for methods)."
      },
      "body": {
        "ru": "Обычный partial в теле класса не сработает: у объекта partial нет __get__, поэтому self не подставится и вызов через экземпляр уйдёт без первого аргумента — ровно ради этого случая и существует partialmethod. Зафиксированные позиционные аргументы встают сразу после self, так что «замораживать» ими удобно только первые параметры; всё остальное передавайте по имени.",
        "en": "A plain partial does not work inside a class body: partial objects are not descriptors, so self never gets bound and calling through an instance drops the first argument — partialmethod exists precisely to cover that case. The frozen positional arguments land right after self, so they only pin the leading parameters; anything further along has to be fixed by keyword."
      },
      "syntax": "attr = functools.partialmethod(method, *args)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.partialmethod",
      "version": "3.4",
      "section": "Модуль functools",
      "subcat": "частичное применение",
      "color_group": "module",
      "aliases": [
        "частичное применение метода",
        "метод с заранее заданными аргументами"
      ],
      "keywords": [],
      "tags": [
        "functools"
      ],
      "examples": [
        "from functools import partialmethod",
        "class Cell:",
        "    def set(self, v): self.value = v",
        "    set_on = partialmethod(set, True)",
        "c = Cell()",
        "c.set_on()",
        "print(c.value)   # → True"
      ],
      "related": [
        "functools.partial",
        "методы-экземпляра",
        "lambda"
      ],
      "related_errors": []
    },
    {
      "id": "functools.reduce",
      "title": "functools.reduce",
      "kind": "term",
      "summary": {
        "ru": "Сворачивает итерируемое применяя функцию накопительно слева направо. Необязательный initializer — начальное значение.",
        "en": "Folds an iterable by applying a function cumulatively from left to right. The optional initializer is the starting value."
      },
      "body": {
        "ru": "Без initializer reduce на пустом итерируемом падает с TypeError, а на одноэлементном возвращает этот элемент, ни разу не вызвав функцию — почти всегда стоит передавать начальное значение явно. В Python 3 reduce намеренно убрали из встроенных: обычный цикл или готовые sum(), math.prod(), str.join() читаются лучше, а накопление списков или строк через reduce ещё и квадратично по времени.",
        "en": "With no initializer, reduce raises TypeError on an empty iterable and, on a one-element one, returns that element without ever calling the function — pass a starting value explicitly unless you really want that behaviour. Python 3 moved reduce out of the builtins on purpose: a plain loop or sum(), math.prod(), str.join() usually reads better, and folding lists or strings this way is quadratic."
      },
      "syntax": "functools.reduce(function, iterable[, initializer])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.reduce",
      "version": "",
      "section": "Модуль functools",
      "subcat": "сворачивание",
      "color_group": "module",
      "aliases": [
        "свёртка последовательности",
        "накопление результата по списку",
        "сворачивание в одно значение"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from functools import reduce",
        "print(reduce(lambda a, b: a + b, [1, 2, 3, 4]))  # → 10",
        "print(reduce(lambda a, b: a * b, range(1, 6)))  # → 120",
        "print(reduce(lambda a, b: a + b, [], 0))  # → 0  (с initializer)",
        "print(reduce(max, [3, 1, 4, 1, 5]))  # → 5",
        "print(reduce(lambda a, b: a + [b], [1, 2, 3], []))  # → [1, 2, 3]"
      ],
      "related": [
        "reduce",
        "itertools.accumulate",
        "sum"
      ],
      "related_errors": []
    },
    {
      "id": "functools.singledispatch",
      "title": "functools.singledispatch",
      "kind": "term",
      "summary": {
        "ru": "Декоратор для создания обобщённых функций с диспетчеризацией по типу первого аргумента. Регистрация через @func.register.",
        "en": "Decorator for writing generic functions that dispatch on the type of the first argument. Implementations are registered with @func.register."
      },
      "body": {
        "ru": "Реализация выбирается только по типу первого аргумента и только если он передан позиционно — по имени его передать нельзя, а остальные аргументы на выбор не влияют. Тип ищется по MRO, поэтому подкласс подхватит реализацию, зарегистрированную для базового класса или ABC; с версии 3.7 тип можно не писать в register, а взять из аннотации, с 3.11 понимаются и union-аннотации вида int | float. Для методов класса это не работает — там нужен singledispatchmethod.",
        "en": "Dispatch looks only at the first argument, and only if it is passed positionally — pass it by keyword and you get a TypeError; the remaining arguments never affect the choice. Lookup follows the MRO, so a subclass picks up the implementation registered for its base class or ABC; since 3.7 register can read the type off the annotation, and since 3.11 union annotations such as int | float work too. It does not cover methods — use singledispatchmethod there."
      },
      "syntax": "@functools.singledispatch\n@func.register(type)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.singledispatch",
      "version": "3.4",
      "section": "Модуль functools",
      "subcat": "диспетчеризация",
      "color_group": "module",
      "aliases": [
        "перегрузка функции по типу аргумента",
        "обобщённая функция",
        "диспетчеризация по типу"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from functools import singledispatch",
        "@singledispatch",
        "def process(arg):",
        "    return f'unknown: {arg}'",
        "@process.register(int)",
        "def _(arg): return f'int: {arg * 2}'",
        "@process.register(str)",
        "def _(arg): return f'str: {arg.upper()}'",
        "print(process(5))  # → int: 10",
        "print(process('hi'))  # → str: HI",
        "print(process(3.14))  # → unknown: 3.14",
        "print(process([1, 2]))  # → unknown: [1, 2]"
      ],
      "related": [
        "functools.singledispatchmethod",
        "полиморфизм",
        "typing.overload",
        "декораторы"
      ],
      "related_errors": []
    },
    {
      "id": "functools.singledispatchmethod",
      "title": "functools.singledispatchmethod",
      "kind": "function",
      "summary": {
        "ru": "Декоратор метода с одиночной диспетчеризацией: выбирает реализацию по типу первого аргумента (после self).",
        "en": "A method decorator with single dispatch on the type of the first argument (after self)."
      },
      "body": {
        "ru": "Появился в Python 3.8; диспетчеризация идёт по типу первого аргумента после self, и тип обычно берут из его аннотации. Регистрируемые реализации принято называть _ — имя роли не играет, значение имеет только register; а если складывать декоратор с classmethod или staticmethod, singledispatchmethod должен быть самым внешним, иначе атрибут register до него не доберётся.",
        "en": "Added in Python 3.8; it dispatches on the type of the first argument after self, normally taken from that argument's annotation. Registered implementations are conventionally named _ because the name is irrelevant — only register matters — and when stacking with classmethod or staticmethod, singledispatchmethod has to be the outermost decorator or its register attribute is unreachable."
      },
      "syntax": "@functools.singledispatchmethod",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.singledispatchmethod",
      "version": "3.8",
      "section": "Модуль functools",
      "subcat": "обобщённые функции",
      "color_group": "module",
      "aliases": [
        "перегрузка метода по типу аргумента",
        "одиночная диспетчеризация в классе"
      ],
      "keywords": [],
      "tags": [
        "functools"
      ],
      "examples": [
        "from functools import singledispatchmethod",
        "class C:",
        "    @singledispatchmethod",
        "    def f(self, x): return 'other'",
        "    @f.register",
        "    def _(self, x: int): return 'int'",
        "print(C().f(5))   # → int"
      ],
      "related": [
        "functools.singledispatch",
        "полиморфизм",
        "методы-экземпляра"
      ],
      "related_errors": []
    },
    {
      "id": "functools.total_ordering",
      "title": "functools.total_ordering",
      "kind": "term",
      "summary": {
        "ru": "Декоратор класса. Дополняет недостающие операторы сравнения (<, <=, >, >=) при наличии __eq__ и хотя бы одного из них.",
        "en": "A class decorator. Given __eq__ and at least one ordering method, it fills in the missing comparison operators (<, <=, >, >=)."
      },
      "body": {
        "ru": "Как только вы объявили __eq__, класс теряет унаследованный __hash__ и экземпляры перестают быть хешируемыми — total_ordering это не чинит, __hash__ придётся вернуть вручную. Сгенерированные операторы просто дёргают ваши __eq__ и __lt__, поэтому работают медленнее написанных руками и удлиняют трейсбек; на горячем пути дешевле расписать все шесть. И пусть ваш __lt__ возвращает NotImplemented для чужих типов, иначе сравнение с посторонним объектом упадёт с AttributeError вместо внятного TypeError.",
        "en": "Defining __eq__ wipes out the inherited __hash__, so instances stop being hashable — total_ordering does not restore it, you have to set __hash__ yourself. The generated operators are thin wrappers that call your __eq__ and __lt__, so they run noticeably slower than hand-written ones and clutter tracebacks; on a hot path just write all six. Also make __lt__ return NotImplemented for unrelated types, otherwise comparing against a foreign object blows up with AttributeError instead of a clean TypeError."
      },
      "syntax": "@functools.total_ordering",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.total_ordering",
      "version": "3.2",
      "section": "Модуль functools",
      "subcat": "сравнение",
      "color_group": "module",
      "aliases": [
        "автоматические операторы сравнения",
        "сделать объекты класса сравнимыми",
        "дописать методы сравнения класса"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from functools import total_ordering",
        "@total_ordering",
        "class Version:",
        "def __init__(self, v): self.v = v",
        "def __eq__(self, o): return self.v == o.v",
        "def __lt__(self, o): return self.v < o.v",
        "v1, v2 = Version(1), Version(2)",
        "print(v1 < v2)  # → True",
        "print(v1 > v2)  # → False",
        "print(v1 <= v1)  # → True",
        "print(v2 >= v1)  # → True"
      ],
      "related": [
        "__add__-__mul__-__eq__-__lt__-и-оператор",
        "dataclass-order-true",
        "sorted"
      ],
      "related_errors": []
    },
    {
      "id": "functools.update_wrapper",
      "title": "functools.update_wrapper",
      "kind": "function",
      "summary": {
        "ru": "Копирует метаданные (__name__, __doc__, …) с обёрнутой функции на обёртку; основа декоратора @functools.wraps.",
        "en": "Copy metadata (__name__, __doc__, …) from a wrapped function onto the wrapper."
      },
      "body": {
        "ru": "Напрямую вызывать почти не приходится: @functools.wraps — это тот же update_wrapper, оформленный как декоратор. Прямой вызов нужен, когда обёртка родилась не из def внутри декоратора — например, это объект functools.partial или экземпляр класса с __call__. Функция правит wrapper на месте (и возвращает его же), а заодно кладёт в него ссылку __wrapped__, благодаря которой inspect.signature показывает параметры оригинала, а не (*args, **kwargs).",
        "en": "You rarely reach for it directly, since @functools.wraps is just this function packaged as a decorator. Call it by hand when the wrapper is not a plain def inside a decorator — a functools.partial object, say, or an instance of a class with __call__. It mutates the wrapper in place (returning it too) and stores a __wrapped__ back-reference, which is what lets inspect.signature report the original parameters instead of (*args, **kwargs)."
      },
      "syntax": "functools.update_wrapper(wrapper, wrapped)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.update_wrapper",
      "version": "",
      "section": "Модуль functools",
      "subcat": "functools",
      "color_group": "module",
      "aliases": [
        "копирование метаданных функции",
        "ручная настройка обёртки декоратора"
      ],
      "keywords": [],
      "tags": [
        "functools"
      ],
      "examples": [
        "import functools",
        "def orig():",
        "    'док'",
        "def wrap():",
        "    pass",
        "functools.update_wrapper(wrap, orig)",
        "print(wrap.__name__)   # → orig"
      ],
      "related": [
        "functools.wraps",
        "декораторы",
        "замыкания"
      ],
      "related_errors": []
    },
    {
      "id": "functools.wraps",
      "title": "functools.wraps",
      "kind": "term",
      "summary": {
        "ru": "Копирует метаданные декорируемой функции (__name__, __doc__, __annotations__) в функцию-обёртку. Обязателен при написании декораторов.",
        "en": "Copies the metadata of the wrapped function (__name__, __doc__, __annotations__) onto the wrapper. A must when writing decorators."
      },
      "body": {
        "ru": "Забудете — и обёртка начнёт представляться чужим именем: help() и __name__ покажут wrapper, сломаются doctest'ы, автодокументация и любые реестры, где ключом служит имя функции. При этом wraps чинит только интроспекцию: вызываться по-прежнему будет wrapper(*args, **kwargs), просто inspect.signature пройдёт по проставленному __wrapped__ и покажет сигнатуру оригинала.",
        "en": "Skip it and your decorated function starts lying about itself: help() and __name__ report wrapper, doctests stop being collected, and anything keyed by function name — docs generators, registries — quietly breaks. Note that wraps only repairs introspection: the callable is still wrapper(*args, **kwargs); inspect.signature merely follows the __wrapped__ link it sets to report the original signature."
      },
      "syntax": "@functools.wraps(wrapped)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.wraps",
      "version": "",
      "section": "Модуль functools",
      "subcat": "декораторы",
      "color_group": "module",
      "aliases": [
        "сохранить имя и документацию функции",
        "правильный декоратор",
        "обёртка не теряет имя функции"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from functools import wraps",
        "def my_decorator(func):",
        "@wraps(func)",
        "def wrapper(*args, **kwargs):",
        "return func(*args, **kwargs)",
        "return wrapper",
        "@my_decorator",
        "def greet(name):",
        "\"\"\"Say hello.\"\"\"",
        "return f'Hello, {name}'",
        "print(greet.__name__)  # → greet",
        "print(greet.__doc__)  # → Say hello.",
        "print(greet('Alice'))  # → Hello, Alice!",
        "print(greet.__wrapped__)  # → <function greet>"
      ],
      "related": [
        "functools.update_wrapper",
        "декораторы",
        "замыкания"
      ],
      "related_errors": []
    },
    {
      "id": "hashlib-md5",
      "title": "hashlib.md5()",
      "kind": "function",
      "summary": {
        "ru": "Создаёт объект MD5-хеша. Быстр, но не криптостоек — не используй для паролей. Подходит для контрольных сумм файлов.",
        "en": "Creates an MD5 hash object. Fast, but not cryptographically strong — do not use it for passwords. Fine for file checksums."
      },
      "body": {
        "ru": "Хеш-объект принимает только bytes: hashlib.md5('текст') сразу падает с TypeError, строку нужно сначала закодировать через .encode(). Данные накапливаются — update() можно звать сколько угодно раз подряд, результат тот же, что от одного вызова со склеенной строкой, а hexdigest() состояние не сбрасывает и читается повторно. Поэтому большой файл хешируют не целиком в память, а кусками в цикле.",
        "en": "The hash object takes bytes only: hashlib.md5('text') raises TypeError right away — encode the string first. Data accumulates, so calling update() several times gives the same result as one call on the concatenated input, and hexdigest() can be read repeatedly without resetting anything. That is why a large file is hashed chunk by chunk instead of being loaded whole."
      },
      "syntax": "hashlib.md5(data=b'', usedforsecurity=True)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/hashlib.html#hashlib.md5",
      "version": "",
      "section": "Модуль hashlib",
      "subcat": "хеширование",
      "color_group": "module",
      "aliases": [
        "контрольная сумма файла",
        "быстрый нестойкий хеш"
      ],
      "keywords": [
        "hashlib.md5"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import hashlib",
        "h = hashlib.md5(b'hello world')",
        "print(h.hexdigest())  # 5eb63bbbe01eeed093cb22bb8f5acdc3",
        "# хеш файла",
        "with open('file.txt', 'rb') as f:",
        "    digest = hashlib.md5(f.read()).hexdigest()"
      ],
      "related": [
        "hashlib-sha256",
        "str.encode",
        "hash"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "hashlib-sha256",
      "title": "hashlib.sha256()",
      "kind": "function",
      "summary": {
        "ru": "Создаёт объект SHA-256 хеша. Криптографически стойкий алгоритм. Используй для проверки целостности данных и цифровых подписей.",
        "en": "Creates a SHA-256 hash object. A cryptographically strong algorithm. Use it for data integrity checks and digital signatures."
      },
      "body": {
        "ru": "Криптостойкость не делает SHA-256 годным для паролей — он слишком быстр, и перебор по словарю идёт миллионами хешей в секунду; для паролей есть hashlib.pbkdf2_hmac() и hashlib.scrypt() с намеренным замедлением и солью. Сравнивая секретные хеши, бери hmac.compare_digest(), а не ==: обычное сравнение обрывается на первом различии и выдаёт время подбора.",
        "en": "Being collision-resistant does not make SHA-256 suitable for passwords: it is fast, so a dictionary attack runs millions of hashes per second — use hashlib.pbkdf2_hmac() or hashlib.scrypt(), which are deliberately slow and salted. When comparing secret digests, use hmac.compare_digest() rather than ==, since a plain comparison bails out at the first differing byte and leaks timing information."
      },
      "syntax": "hashlib.sha256(data=b'')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/hashlib.html#hashlib.sha256",
      "version": "",
      "section": "Модуль hashlib",
      "subcat": "хеширование",
      "color_group": "module",
      "aliases": [
        "криптостойкий хеш",
        "проверка целостности данных",
        "цифровая подпись данных"
      ],
      "keywords": [
        "hashlib.sha256"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import hashlib",
        "h = hashlib.sha256(b'secret data')",
        "print(h.hexdigest())",
        "# 2b...64 символа",
        "# постепенная загрузка больших файлов",
        "h = hashlib.sha256()",
        "with open('bigfile.bin', 'rb') as f:",
        "    for chunk in iter(lambda: f.read(65536), b''):",
        "        h.update(chunk)",
        "        print(h.hexdigest())"
      ],
      "related": [
        "hashlib-md5",
        "str.encode",
        "bytes.hex"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "heapq.heapify",
      "title": "heapq.heapify()",
      "kind": "function",
      "summary": {
        "ru": "Преобразует произвольный список в min-heap за O(n). Изменяет список на месте.",
        "en": "Turns an arbitrary list into a min-heap in O(n). It rearranges the list in place."
      },
      "body": {
        "ru": "Собрать кучу разом дешевле, чем набивать поэлементно: heapify — O(n), а n вызовов heappush — O(n log n). Главная ловушка — решить, что после heapify список отсортирован: он упорядочен лишь частично, гарантирован только минимум в x[0], и печать списка даёт на вид случайный порядок. Max-heap в heapq нет — обычный обходной приём: хранить значения с обратным знаком.",
        "en": "Building the heap in one shot is cheaper than filling it item by item: heapify is O(n), whereas n heappush calls cost O(n log n). The classic misread is assuming the list comes out sorted — it is only partially ordered, the guarantee is x[0] being the minimum, and printing the list looks shuffled. There is no max-heap in heapq; the usual workaround is storing negated values."
      },
      "syntax": "heapq.heapify(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/heapq.html#heapq.heapify",
      "version": "",
      "section": "Модуль heapq",
      "subcat": "операции",
      "color_group": "module",
      "aliases": [
        "превратить список в кучу",
        "построить кучу из готового списка"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import heapq",
        "data = [5, 2, 8, 1, 4]",
        "heapq.heapify(data)",
        "data[0] # → 1  (минимальный элемент)",
        "heapq.heappop(data) # → 1",
        "heapq.heappop(data) # → 2",
        "data # → [4, 5, 8]"
      ],
      "related": [
        "heapq.heappush",
        "heapq.heappop",
        "куча-как-приоритетная-очередь",
        "list.sort"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "heapq.heappop",
      "title": "heapq.heappop()",
      "kind": "function",
      "summary": {
        "ru": "Извлекает и возвращает наименьший элемент кучи, сохраняя её инвариант. На пустой куче поднимает IndexError. Сложность O(log n).",
        "en": "Pop and return the smallest item from the heap, preserving the invariant; raises IndexError on an empty heap. O(log n)."
      },
      "body": {
        "ru": "На пустой куче это IndexError, а не None — либо проверяйте if heap:, либо ловите исключение. Чтобы просто подсмотреть минимум, не вынимая его, читайте heap[0]: индексация ничего не перестраивает и стоит O(1). Вычерпать всё через heappop действительно даёт отсортированную последовательность, но для готового списка sorted() проще и быстрее — heapq выигрывает, когда элементы приходят и уходят вперемешку.",
        "en": "An empty heap raises IndexError rather than returning None, so guard with if heap: or catch it. To peek at the minimum without removing it, just read heap[0] — indexing rebuilds nothing and costs O(1). Draining everything with heappop does yield sorted order, but for a list you already have, sorted() is simpler and faster; heapq pays off when pushes and pops interleave."
      },
      "syntax": "heapq.heappop(heap)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/heapq.html#heapq.heappop",
      "version": "",
      "section": "Модуль heapq",
      "subcat": "операции",
      "color_group": "module",
      "aliases": [
        "извлечь минимум из кучи",
        "достать наименьший элемент кучи",
        "удалить вершину кучи"
      ],
      "keywords": [
        "heapq.heappop",
        "heappop"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import heapq",
        "h = [1, 2, 3]",
        "heapq.heappop(h) # → 1",
        "heapq.heappop(h) # → 2",
        "h # → [3]"
      ],
      "related": [
        "heapq.heappush",
        "heapq.heapreplace",
        "heapq.heappushpop"
      ],
      "related_errors": [
        "IndexError"
      ]
    },
    {
      "id": "heapq.heappush",
      "title": "heapq.heappush()",
      "kind": "function",
      "summary": {
        "ru": "Добавляет элемент в кучу, сохраняя её инвариант. Куча — обычный список, минимум всегда лежит в heap[0]. Сложность O(log n).",
        "en": "Push an item onto the heap, preserving the heap invariant; the heap is a plain list whose smallest item is always heap[0]. O(log n)."
      },
      "body": {
        "ru": "Куча — обычный список, поэтому инвариант легко сломать по неосторожности: append(), insert() или присваивание в середину мимо heapq делают все последующие heappop бессмысленными. В очереди с приоритетом кладут кортежи (приоритет, объект), но при равных приоритетах Python начнёт сравнивать вторые элементы и упадёт с TypeError на несравнимых объектах — спасает возрастающий счётчик вторым полем кортежа. Если сразу после вставки нужно извлечение, есть heappushpop: одна перестройка вместо двух.",
        "en": "Because the heap is an ordinary list, nothing stops you from corrupting it: an append(), insert() or direct assignment that bypasses heapq makes every later heappop meaningless. Priority queues usually push (priority, item) tuples, but on equal priorities Python falls through to comparing the items themselves and blows up with TypeError on non-comparable objects — inserting a monotonically increasing counter as the second field fixes it. When a pop follows immediately, heappushpop does both with a single reordering."
      },
      "syntax": "heapq.heappush(heap, item)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/heapq.html#heapq.heappush",
      "version": "",
      "section": "Модуль heapq",
      "subcat": "операции",
      "color_group": "module",
      "aliases": [
        "добавить элемент в кучу",
        "вставить элемент с сохранением свойства кучи"
      ],
      "keywords": [
        "heapq.heappush",
        "heappush"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import heapq",
        "h = []",
        "heapq.heappush(h, 3)",
        "heapq.heappush(h, 1)",
        "heapq.heappush(h, 2)",
        "h[0] # → 1 (минимум всегда первый)"
      ],
      "related": [
        "heapq.heappop",
        "heapq.heapreplace",
        "heapq.heappushpop"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "heapq.heappushpop",
      "title": "heapq.heappushpop()",
      "kind": "function",
      "summary": {
        "ru": "Вставляет элемент и сразу извлекает минимум — порядок обратный heapreplace(). Если новый элемент меньше всех, он же и вернётся, куча не изменится.",
        "en": "Push an item and immediately pop the smallest — the reverse order of heapreplace(); if the new item is the smallest, it is returned and the heap is unchanged."
      },
      "body": {
        "ru": "Один просев дерева вместо двух, поэтому пара heappush() плюс heappop() всегда проигрывает: обе операции O(log n), но здесь работы вдвое меньше. В отличие от heapreplace(), спокойно переживает пустую кучу — просто вернёт то, что вы передали. Отсюда идиома «топ-k»: держите кучу ровно из k элементов и лейте поток через heappushpop().",
        "en": "One sift through the tree instead of two, so heappush() followed by heappop() always loses: both are O(log n), but this does half the work. Unlike heapreplace(), it tolerates an empty heap — it simply hands the item back to you. That makes it the standard way to keep a running top-k: hold exactly k items and feed the stream through it."
      },
      "syntax": "heapq.heappushpop(heap, item)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/heapq.html#heapq.heappushpop",
      "version": "",
      "section": "Модуль heapq",
      "subcat": "операции",
      "color_group": "module",
      "aliases": [
        "вставить в кучу и сразу извлечь минимум",
        "добавить и забрать наименьший за один вызов"
      ],
      "keywords": [
        "heapq.heappushpop",
        "heappushpop"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import heapq",
        "h = [3, 5, 7]",
        "heapq.heappushpop(h, 1) # → 1  (1 < min, сразу вернули)",
        "heapq.heappushpop(h, 9) # → 3  (вставили 9, извлекли 3)"
      ],
      "related": [
        "heapq.heapreplace",
        "heapq.heappush",
        "heapq.heappop"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "heapq.heapreplace",
      "title": "heapq.heapreplace()",
      "kind": "function",
      "summary": {
        "ru": "Извлекает минимум и вставляет новый элемент одним проходом — эффективнее пары heappop()+heappush(). Сначала извлекает, потом вставляет.",
        "en": "Pop the smallest item and push a new one in a single pass — more efficient than heappop() followed by heappush(). Pops first, then pushes."
      },
      "body": {
        "ru": "На пустой куче падает с IndexError: извлекать нечего, а извлечение здесь идёт первым. Вернуться может элемент больше вставленного, поэтому в цикле «топ-k» heapreplace() безопасен только под проверкой item > heap[0]; без неё берите heappushpop(). Размер кучи не меняется никогда — ради этого функцию и держат в буферах фиксированной длины.",
        "en": "On an empty heap it raises IndexError: the pop comes first and there is nothing to pop. The value returned can be larger than the one you pushed, so in a top-k loop guard it with if item > heap[0]; without that guard use heappushpop() instead. The heap size never changes, which is exactly why this fits fixed-size buffers."
      },
      "syntax": "heapq.heapreplace(heap, item)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/heapq.html#heapq.heapreplace",
      "version": "",
      "section": "Модуль heapq",
      "subcat": "операции",
      "color_group": "module",
      "aliases": [
        "заменить минимум кучи новым элементом",
        "извлечь минимум и на его место вставить другой"
      ],
      "keywords": [
        "heapq.heapreplace",
        "heapreplace"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import heapq",
        "h = [1, 3, 5]",
        "heapq.heapreplace(h, 4) # → 1  (извлекли 1, вставили 4)",
        "h # → [3, 4, 5]"
      ],
      "related": [
        "heapq.heappushpop",
        "heapq.heappush",
        "heapq.heappop"
      ],
      "related_errors": [
        "IndexError",
        "TypeError"
      ]
    },
    {
      "id": "heapq.nlargest",
      "title": "heapq.nlargest()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает список из n наибольших элементов в порядке убывания. Принимает key=, как sorted(). При n≈len(iterable) дешевле обычный sorted().",
        "en": "Return a list of the n largest items in descending order; accepts key= like sorted(). For n close to len(iterable) a plain sorted() is cheaper."
      },
      "body": {
        "ru": "Проходит итерируемое один раз и держит в памяти только n элементов, так что генератор или огромный файл её не смущают; стоимость — порядка m·log n, где m — длина потока. Оба края диапазона дешевле закрывать иначе: n=1 — это max(), а n, сравнимое с длиной данных, — обычный sorted(). Равные по key элементы сохраняют исходный порядок, как у sorted().",
        "en": "It walks the iterable once and holds only n items in memory, so a generator or a huge file is fine; the cost is on the order of m·log n for a stream of m items. Both ends of the range are better served by something else: n=1 is just max(), and n close to the size of the data is plain sorted(). Items that tie on key keep their original order, matching sorted()."
      },
      "syntax": "heapq.nlargest(n, iterable, key=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/heapq.html#heapq.nlargest",
      "version": "",
      "section": "Модуль heapq",
      "subcat": "выборка",
      "color_group": "module",
      "aliases": [
        "наибольшие элементы списка",
        "топ самых больших значений",
        "несколько максимумов из списка"
      ],
      "keywords": [
        "heapq.nlargest",
        "nlargest"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import heapq",
        "data = [3, 1, 4, 1, 5, 9, 2, 6]",
        "heapq.nlargest(3, data) # → [9, 6, 5]",
        "records = [{'v': 5}, {'v': 1}, {'v': 9}]",
        "heapq.nlargest(2, records, key=lambda x: x['v'])",
        "# → [{'v': 9}, {'v': 5}]"
      ],
      "related": [
        "heapq.nsmallest"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "heapq.nsmallest",
      "title": "heapq.nsmallest()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает список из n наименьших элементов в порядке возрастания. Принимает key=, как sorted(). При n=1 дешевле min().",
        "en": "Return a list of the n smallest items in ascending order; accepts key= like sorted(). For n=1 plain min() is cheaper."
      },
      "body": {
        "ru": "Куча на входе не нужна и не используется: функция принимает любое итерируемое и строит собственную внутреннюю структуру. Поэтому вызывать её на списке, который вы уже поддерживаете кучей, бессмысленно — минимум лежит в heap[0] и достаётся за O(1). key, как и в sorted(), считается по одному разу на элемент, так что дорогая функция ключа не пересчитывается при каждом сравнении.",
        "en": "The argument need not be a heap and the heap invariant is not used: any iterable works and the function builds its own internal structure. So calling it on a list you already maintain as a heap buys nothing — the minimum is sitting in heap[0] at O(1). As with sorted(), key is applied once per element, so an expensive key function is not recomputed during comparisons."
      },
      "syntax": "heapq.nsmallest(n, iterable, key=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/heapq.html#heapq.nsmallest",
      "version": "",
      "section": "Модуль heapq",
      "subcat": "выборка",
      "color_group": "module",
      "aliases": [
        "наименьшие элементы списка",
        "топ самых маленьких значений",
        "несколько минимумов из списка"
      ],
      "keywords": [
        "heapq.nsmallest",
        "nsmallest"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import heapq",
        "data = [3, 1, 4, 1, 5, 9, 2, 6]",
        "heapq.nsmallest(3, data) # → [1, 1, 2]",
        "words = ['banana', 'kiwi', 'apple']",
        "heapq.nsmallest(2, words, key=len) # → ['kiwi', 'apple']"
      ],
      "related": [
        "heapq.nlargest"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "куча-как-приоритетная-очередь",
      "title": "Куча как приоритетная очередь",
      "kind": "term",
      "summary": {
        "ru": "Классический паттерн: хранить кортежи (приоритет, данные) в куче для эффективного планирования задач.",
        "en": "The classic pattern: keep (priority, data) tuples in a heap to schedule tasks efficiently."
      },
      "body": {
        "ru": "Кортежи сравниваются поэлементно, и при равных приоритетах Python дойдёт до самих данных — на словарях или своих объектах это TypeError. Лечится третьим полем-счётчиком: (приоритет, номер, задача); заодно получаете FIFO среди равных приоритетов. И помните, что heapq — min-куча: «первым идёт самый важный» при больших числах приоритета делают через -priority.",
        "en": "Tuples compare element by element, so when two priorities tie Python moves on to the payload — with dicts or custom objects that means TypeError. The fix is a third field, a monotonically increasing counter, as in (priority, count, task), which also gives FIFO order among equal priorities. And heapq is a min-heap: if a bigger number means more important, push -priority."
      },
      "syntax": "heapq.heappush(pq, (priority, task))\npriority, task = heapq.heappop(pq)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/heapq.html#heapq.heappush",
      "version": "",
      "section": "Модуль heapq",
      "subcat": "применение",
      "color_group": "module",
      "aliases": [
        "очередь с приоритетами",
        "планировщик задач по приоритету",
        "хранить пары приоритет и значение"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import heapq",
        "pq = []",
        "heapq.heappush(pq, (2, 'wash dishes'))",
        "heapq.heappush(pq, (1, 'urgent call'))",
        "heapq.heappush(pq, (3, 'read book'))",
        "prio, task = heapq.heappop(pq)",
        "print(task) # → 'urgent call'",
        "prio, task = heapq.heappop(pq)",
        "print(task) # → 'wash dishes'"
      ],
      "related": [
        "heapq.heappush",
        "heapq.heappop",
        "очередь-queue",
        "collections.deque"
      ],
      "related_errors": []
    },
    {
      "id": "io.BufferedIOBase",
      "title": "io.BufferedIOBase",
      "kind": "term",
      "summary": {
        "ru": "Абстрактный базовый класс буферизованных бинарных потоков (BytesIO, BufferedReader/Writer и др.).",
        "en": "The abstract base class for buffered binary streams (BytesIO, BufferedReader/Writer, …)."
      },
      "body": {
        "ru": "Практический смысл этого ABC — отличить буферизованный бинарный поток от текстового: open(path, 'rb') отдаёт BufferedReader, а open(path) — TextIOWrapper, и isinstance по базовому классу надёжнее разбора атрибута mode. У буферизованных потоков read(n) возвращает ровно n байт, пока не упрётся в конец файла, — в отличие от сырых RawIOBase, где короткое чтение законно в любой момент.",
        "en": "The practical use of this ABC is telling a buffered binary stream from a text one: open(path, 'rb') gives a BufferedReader while open(path) gives a TextIOWrapper, and an isinstance check against the base class beats poking at the mode attribute. On buffered streams read(n) returns exactly n bytes until it hits EOF, unlike raw RawIOBase streams where a short read is legal at any time."
      },
      "syntax": "issubclass(cls, io.BufferedIOBase)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.BufferedIOBase",
      "version": "",
      "section": "Модуль io",
      "subcat": "абстрактные базы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "print(issubclass(io.BytesIO, io.BufferedIOBase))   # → True",
        "print(issubclass(io.BufferedReader, io.BufferedIOBase))   # → True",
        "print(issubclass(io.StringIO, io.BufferedIOBase))   # → False",
        "print(issubclass(io.BufferedIOBase, io.IOBase))   # → True",
        "buf = io.BytesIO(b'hello')",
        "print(isinstance(buf, io.BufferedIOBase))   # → True",
        "print(io.BufferedIOBase().read())   # → io.UnsupportedOperation"
      ],
      "related": [
        "io.IOBase",
        "io.RawIOBase",
        "io.TextIOBase",
        "io.BufferedReader"
      ],
      "related_errors": []
    },
    {
      "id": "io.BufferedRWPair",
      "title": "io.BufferedRWPair",
      "kind": "term",
      "summary": {
        "ru": "Буферизованный объект, объединяющий отдельный поток чтения и отдельный поток записи (например, для сокета или пары каналов).",
        "en": "A buffered object pairing a separate reader and writer stream."
      },
      "body": {
        "ru": "Класс имеет смысл только для по-настоящему раздельных каналов — сокет, пара пайпов; передавать один и тот же объект и как reader, и как writer нельзя, для двустороннего доступа к одному потоку есть BufferedRandom. Произвольный доступ здесь не поддерживается: seekable() возвращает False, а сам объект не синхронизирует обращения к нижележащим сырым потокам, так что в многопоточном коде на него полагаться не стоит.",
        "en": "This pairing only makes sense when reading and writing really go through two different channels, such as a socket or a pair of pipes; passing the same object as both reader and writer is explicitly wrong, and BufferedRandom is the class for read-write access to one stream. There is no random access here, seekable() is False, and the object does not synchronize access to the underlying raw streams, so do not treat it as thread-safe."
      },
      "syntax": "io.BufferedRWPair(reader, writer)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.BufferedRWPair",
      "version": "",
      "section": "Модуль io",
      "subcat": "бинарные потоки",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "p = io.BufferedRWPair(io.BytesIO(b'ab'), io.BytesIO())",
        "print(p.read())   # → b'ab'",
        "print(p.readable(), p.writable())   # → True True",
        "print(p.seekable())   # → False",
        "w = io.BytesIO()",
        "q = io.BufferedRWPair(io.BytesIO(b'in'), w)",
        "print(q.write(b'out'))   # → 3",
        "q.flush()",
        "print(w.getvalue())   # → b'out'",
        "print(q.read())   # → b'in'"
      ],
      "related": [
        "io.BufferedReader",
        "io.BufferedWriter",
        "io.BufferedRandom",
        "os.pipe"
      ],
      "related_errors": []
    },
    {
      "id": "io.BufferedRandom",
      "title": "io.BufferedRandom",
      "kind": "term",
      "summary": {
        "ru": "Буферизованный поток с произвольным доступом (чтение и запись + seek) поверх сырого потока — для файлов, открытых в режиме 'r+b'.",
        "en": "A buffered random-access stream (read+write+seek) over a raw stream."
      },
      "body": {
        "ru": "Вручную этот класс почти не собирают — его возвращает open(path, 'r+b'), и разница с соседними режимами важнее самого класса: 'r+b' требует существующий файл и не обрезает его, а 'w+b' обнуляет содержимое при открытии. Запись уходит в буфер, поэтому до flush() или close() другой процесс (и даже другой дескриптор того же файла) увидит старые данные — если нужна немедленная видимость, вызывайте flush() явно.",
        "en": "You rarely construct this yourself: open(path, 'r+b') gives you one, and the mode matters more than the class name, since 'r+b' needs an existing file and leaves it intact while 'w+b' truncates it on open. Writes land in a buffer first, so until flush() or close() another process, or even another handle on the same file, still sees the old bytes; call flush() explicitly when the update has to be visible right away."
      },
      "syntax": "io.BufferedRandom(raw)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.BufferedRandom",
      "version": "",
      "section": "Модуль io",
      "subcat": "бинарные потоки",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "print(issubclass(io.BufferedRandom, io.BufferedIOBase))   # → True",
        "b = io.BufferedRandom(io.BytesIO(b'abcdef'))",
        "print(b.read(3))   # → b'abc'",
        "print(b.tell())   # → 3",
        "print(b.seek(0))   # → 0",
        "print(b.write(b'XY'))   # → 2",
        "b.seek(0)",
        "print(b.read())   # → b'XYcdef'",
        "print(b.readable(), b.writable(), b.seekable())   # → True True True"
      ],
      "related": [
        "io.BufferedReader",
        "io.BufferedWriter",
        "io.BufferedRWPair",
        "file-tell"
      ],
      "related_errors": []
    },
    {
      "id": "io.BufferedReader",
      "title": "io.BufferedReader",
      "kind": "term",
      "summary": {
        "ru": "Буферизованный поток бинарного чтения поверх сырого потока; сглаживает мелкие read() крупными блоками.",
        "en": "A buffered binary read stream wrapping a raw stream."
      },
      "body": {
        "ru": "Обычно его не создают руками — именно BufferedReader возвращает open(path, 'rb'), а через .buffer до него можно добраться у текстового файла или у sys.stdin. Полезное отличие от сырого потока: read(n) отдаёт ровно n байт и меньше только на конце файла, а peek() позволяет заглянуть вперёд, не сдвигая позицию. Помните, что read() без аргумента затягивает весь файл в память — большие файлы читайте кусками фиксированного размера или построчно.",
        "en": "You seldom build one by hand: open(path, 'rb') returns a BufferedReader, and the .buffer attribute of a text file or of sys.stdin exposes the same layer underneath. Two things the signature does not tell you: read(n) returns exactly n bytes unless the file ends, unlike a raw stream which may hand back fewer, and peek() lets you look ahead without moving the position. Calling read() with no argument pulls the whole file into memory, so read large files in fixed-size chunks or line by line instead."
      },
      "syntax": "io.BufferedReader(raw)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.BufferedReader",
      "version": "",
      "section": "Модуль io",
      "subcat": "бинарные потоки",
      "color_group": "module",
      "aliases": [
        "буферизованное чтение бинарного файла",
        "объект открытого файла в двоичном режиме чтения"
      ],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "b = io.BufferedReader(io.BytesIO(b'hello'))",
        "print(b.read())   # → b'hello'",
        "r = io.BufferedReader(io.BytesIO(b'line1\\nline2\\n'))",
        "print(r.readline())   # → b'line1\\n'",
        "print(r.readlines())   # → [b'line2\\n']",
        "p = io.BufferedReader(io.BytesIO(b'abcdef'))",
        "print(p.peek())   # → b'abcdef'",
        "print(p.read(2))   # → b'ab'",
        "print(p.write(b'x'))   # → io.UnsupportedOperation"
      ],
      "related": [
        "io.BufferedWriter",
        "io.FileIO",
        "io.BufferedRandom",
        "io.BufferedIOBase"
      ],
      "related_errors": []
    },
    {
      "id": "io.BufferedWriter",
      "title": "io.BufferedWriter",
      "kind": "term",
      "summary": {
        "ru": "Буферизованный поток бинарной записи поверх сырого потока; накапливает данные и сбрасывает их пачкой при flush/close.",
        "en": "A buffered binary write stream wrapping a raw stream; flushes in batches."
      },
      "body": {
        "ru": "Пока буфер не заполнен, записанное лежит в памяти процесса: если программа упала или файл не закрыт, на диске окажется пусто или обрезанный хвост — поэтому поток открывают через with. flush() отдаёт накопленное операционной системе, но не гарантирует, что байты уже на физическом диске; для этого нужен os.fsync() дескриптора. Сырой поток должен быть открыт на запись — для чтения есть BufferedReader, для чтения и записи сразу BufferedRandom.",
        "en": "Until the buffer fills up, whatever you wrote lives in process memory: if the program crashes or the stream is never closed, the file ends up empty or truncated, which is why you open it with a with-block. flush() hands the bytes to the operating system but does not guarantee they reached the physical disk — that takes os.fsync() on the descriptor. The wrapped raw stream must be writable; use BufferedReader for reading and BufferedRandom when you need both."
      },
      "syntax": "io.BufferedWriter(raw)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.BufferedWriter",
      "version": "",
      "section": "Модуль io",
      "subcat": "бинарные потоки",
      "color_group": "module",
      "aliases": [
        "буферизованная запись байтов в файл",
        "объект открытого файла в двоичном режиме записи"
      ],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "raw = io.BytesIO()",
        "w = io.BufferedWriter(raw)",
        "w.write(b'hi')",
        "w.flush()",
        "print(raw.getvalue())   # → b'hi'"
      ],
      "related": [
        "io.BufferedReader",
        "буферизация",
        "io.FileIO",
        "io.BufferedRandom"
      ],
      "related_errors": []
    },
    {
      "id": "io.FileIO",
      "title": "io.FileIO",
      "kind": "term",
      "summary": {
        "ru": "Сырой (небуферизованный) бинарный поток к файлу по имени или дескриптору; низкоуровневая основа, поверх которой open() строит буферизацию.",
        "en": "A raw (unbuffered) binary stream to a file by name or descriptor."
      },
      "body": {
        "ru": "Напрямую его создают редко: open(path, 'rb', buffering=0) возвращает ровно FileIO, а обычный open() надстраивает над ним буфер и текстовую обёртку. Главная ловушка сырого потока — read(n) делает один системный вызов и вправе вернуть меньше n байт задолго до конца файла, поэтому читают либо readall(), либо циклом до пустого результата.",
        "en": "You rarely construct it yourself: open(path, 'rb', buffering=0) hands you exactly a FileIO, and a normal open() stacks buffering and text decoding on top of one. The classic trap with a raw stream is that read(n) issues a single system call and may legitimately return fewer than n bytes long before EOF, so read with readall() or loop until you get an empty result."
      },
      "syntax": "io.FileIO(name, mode='r')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.FileIO",
      "version": "",
      "section": "Модуль io",
      "subcat": "бинарные потоки",
      "color_group": "module",
      "aliases": [
        "чтение файла без буферизации",
        "работа с файлом по дескриптору"
      ],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "import os",
        "print(issubclass(io.FileIO, io.RawIOBase))   # → True",
        "f = io.FileIO(os.devnull, 'r')",
        "print(f.mode)   # → rb",
        "print(f.readable(), f.writable())   # → True False",
        "print(f.read())   # → b''",
        "f.close()",
        "print(f.closed)   # → True",
        "b = open(os.devnull, 'rb')",
        "print(type(b.raw).__name__)   # → FileIO",
        "b.close()"
      ],
      "related": [
        "io.RawIOBase",
        "open",
        "io.BufferedReader",
        "буферизация"
      ],
      "related_errors": []
    },
    {
      "id": "io.IOBase",
      "title": "io.IOBase",
      "kind": "term",
      "summary": {
        "ru": "Абстрактный базовый класс всех потоков ввода-вывода (файлов и StringIO/BytesIO); задаёт общий интерфейс close/readable/writable/seekable.",
        "en": "The abstract base class of all I/O streams (files, StringIO/BytesIO)."
      },
      "body": {
        "ru": "isinstance(obj, io.IOBase) подтверждает только «это поток», но не что у него есть read() или write(): сами эти методы объявлены уже в наследниках, а IOBase задаёт лишь close(), закрытие по with и запросы readable()/writable()/seekable(). Обращение к уже закрытому потоку даёт ValueError с текстом про I/O operation on closed file, а не OSError, — ловить надо именно ValueError.",
        "en": "isinstance(obj, io.IOBase) only confirms that something is a stream, not that it can be read or written: read() and write() are introduced by subclasses, while IOBase itself provides close(), with-statement support and the readable()/writable()/seekable() queries. Touching an already closed stream raises ValueError about an I/O operation on a closed file, not OSError — catch ValueError."
      },
      "syntax": "issubclass(cls, io.IOBase)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.IOBase",
      "version": "",
      "section": "Модуль io",
      "subcat": "абстрактные базы",
      "color_group": "module",
      "aliases": [
        "проверить что объект файлоподобный",
        "общий предок файлов и потоков в памяти"
      ],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "print(issubclass(io.RawIOBase, io.IOBase))   # → True",
        "print(isinstance(io.StringIO(), io.IOBase))   # → True",
        "print(issubclass(io.TextIOBase, io.IOBase))   # → True",
        "s = io.StringIO('abc')",
        "print(s.readable(), s.writable(), s.seekable())   # → True True True",
        "s.close()",
        "print(s.closed)   # → True",
        "print(s.read())   # → ValueError"
      ],
      "related": [
        "io.RawIOBase",
        "io.BufferedIOBase",
        "io.TextIOBase"
      ],
      "related_errors": []
    },
    {
      "id": "io.IncrementalNewlineDecoder",
      "title": "io.IncrementalNewlineDecoder",
      "kind": "term",
      "summary": {
        "ru": "Инкрементальный декодер, приводящий переводы строк к '\\n' (universal newlines) по мере поступления байтов; используется текстовыми потоками.",
        "en": "An incremental decoder that normalizes newlines to '\\n' as bytes arrive."
      },
      "body": {
        "ru": "Декодер помнит состояние между вызовами: если очередной кусок данных обрывается на '\\r', этот символ придерживается до следующего вызова — пока неизвестно, одиночный это перевод строки или половина '\\r\\n'. Из-за этого decode() иногда возвращает меньше, чем вы подали, и в конце потока нужен завершающий вызов с final=True. Руками его почти не создают: это внутренний помощник TextIOWrapper для файлов, открытых с newline=None.",
        "en": "The decoder carries state between calls: if a chunk ends with '\\r' it holds that character back until the next call, because it cannot yet tell a lone newline from half of '\\r\\n'. So decode() may hand back less than you fed it, and the stream must be finished with a final=True call. You rarely instantiate it yourself — it is the helper TextIOWrapper uses for files opened with newline=None."
      },
      "syntax": "io.IncrementalNewlineDecoder(decoder, translate)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.IncrementalNewlineDecoder",
      "version": "",
      "section": "Модуль io",
      "subcat": "служебные",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io, codecs",
        "base = codecs.getincrementaldecoder('utf-8')()",
        "d = io.IncrementalNewlineDecoder(base, translate=True)",
        "print(repr(d.decode(b'a\\r\\nb')))   # → 'a\\nb'"
      ],
      "related": [
        "io.textiowrapper",
        "open",
        "io.TextIOBase"
      ],
      "related_errors": []
    },
    {
      "id": "io.RawIOBase",
      "title": "io.RawIOBase",
      "kind": "term",
      "summary": {
        "ru": "Абстрактный базовый класс сырых (небуферизованных) бинарных потоков; его конкретная реализация — FileIO.",
        "en": "The abstract base class for raw (unbuffered) binary streams; FileIO is its implementation."
      },
      "body": {
        "ru": "Сырой поток в руки попадает редко: его даёт open(path, 'rb', buffering=0), а у обычного буферизованного файла он лежит в атрибуте .raw. Каждый read()/write() здесь — отдельный системный вызов, и read(n) вправе вернуть меньше n байт, даже когда файл ещё не кончился, поэтому дочитывать приходится циклом вручную; в обычном коде берут буферизованный слой, а сырой — только когда нужен полный контроль над вводом-выводом.",
        "en": "You rarely hold a raw stream directly: open(path, 'rb', buffering=0) returns one, and an ordinary buffered file exposes it through its .raw attribute. Every read()/write() maps to a separate system call, and read(n) may return fewer than n bytes even before EOF, so you must loop to read the rest yourself — normal code sticks to the buffered layer and drops to raw only when it needs full control over I/O."
      },
      "syntax": "issubclass(cls, io.RawIOBase)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.RawIOBase",
      "version": "",
      "section": "Модуль io",
      "subcat": "абстрактные базы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "print(issubclass(io.FileIO, io.RawIOBase))   # → True",
        "print(issubclass(io.RawIOBase, io.IOBase))   # → True",
        "print(issubclass(io.BufferedReader, io.RawIOBase))   # → False",
        "print(isinstance(io.BytesIO(), io.RawIOBase))   # → False",
        "print(hasattr(io.RawIOBase, 'readinto'))   # → True"
      ],
      "related": [
        "io.FileIO",
        "io.IOBase",
        "io.BufferedIOBase",
        "буферизация"
      ],
      "related_errors": []
    },
    {
      "id": "io.Reader",
      "title": "io.Reader",
      "kind": "function",
      "summary": {
        "ru": "Протокол io.Reader[T] — абстрактный класс с методом read(), для аннотаций и структурных проверок isinstance/issubclass. Python 3.14+.",
        "en": "The io.Reader[T] protocol: an abstract class with a read() method, used for annotations and structural isinstance/issubclass checks. Python 3.14+."
      },
      "body": {
        "ru": "Появился только в 3.14 — на 3.13 и ниже обращение к io.Reader даст AttributeError, поэтому в переносимом коде пока остаются прежние аннотации вроде IO[str]. Проверка структурная: isinstance смотрит лишь на наличие метода read(), а параметр типа в рантайме не проверяется, так что Reader[str] и Reader[bytes] на этом уровне неразличимы.",
        "en": "It landed only in Python 3.14 — on 3.13 and earlier touching io.Reader raises AttributeError, so portable code still falls back on older annotations such as IO[str]. The check is structural: isinstance only looks for a read() method, and the type parameter is not verified at runtime, so Reader[str] and Reader[bytes] are indistinguishable there."
      },
      "syntax": "io.Reader[T]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.Reader",
      "version": "3.14",
      "section": "Модуль io",
      "subcat": "протоколы",
      "color_group": "module",
      "aliases": [
        "протокол чтения потока",
        "объект умеющий читать"
      ],
      "keywords": [
        "io.Reader"
      ],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "r = io.StringIO('hello world')",
        "print(isinstance(r, io.Reader))    # → True",
        "print(r.read(5))                   # → hello",
        "print(isinstance(42, io.Reader))   # → False",
        "print(io.Reader[str])              # → io.Reader[str]"
      ],
      "related": [
        "io.IOBase",
        "io.stringio",
        "protocol",
        "typing.runtime_checkable"
      ],
      "related_errors": [
        "ValueError",
        "AttributeError"
      ]
    },
    {
      "id": "io.TextIOBase",
      "title": "io.TextIOBase",
      "kind": "term",
      "summary": {
        "ru": "Абстрактный базовый класс текстовых потоков (StringIO, объект открытого текстового файла); работает со str, а не bytes.",
        "en": "The abstract base class for text streams (StringIO, an opened text file); works with str."
      },
      "body": {
        "ru": "Класс нужен для проверок типа и для наследования, напрямую его не создают: настоящие текстовые потоки — это StringIO и TextIOWrapper (именно TextIOWrapper возвращает open() в текстовом режиме, им же являются sys.stdin и sys.stdout). Практический смысл различия в том, что в текстовый поток пишется только str: попытка отдать туда bytes даёт TypeError, а бинарные потоки (BytesIO, BufferedReader) принимают наоборот только bytes. Ещё одно следствие — у текстового потока есть encoding и newlines, то есть перекодировка и трансляция переводов строк уже выполнены за вас.",
        "en": "You never instantiate this class; it exists for isinstance/issubclass checks and as a base for custom text streams. The concrete text streams are StringIO and TextIOWrapper, and the latter is what open() hands back in text mode, as well as what sys.stdin and sys.stdout are. The practical consequence is the str/bytes split: a text stream accepts only str and raises TypeError on bytes, while binary streams such as BytesIO or BufferedReader accept only bytes, and only the text side gives you encoding and newline translation."
      },
      "syntax": "issubclass(cls, io.TextIOBase)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.TextIOBase",
      "version": "",
      "section": "Модуль io",
      "subcat": "абстрактные базы",
      "color_group": "module",
      "aliases": [
        "проверить что поток текстовый а не байтовый",
        "тип объекта открытого текстового файла"
      ],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "print(issubclass(io.StringIO, io.TextIOBase))   # → True",
        "print(issubclass(io.TextIOWrapper, io.TextIOBase), issubclass(io.BytesIO, io.TextIOBase))   # → True False",
        "s = io.StringIO('hello')",
        "print(isinstance(s, io.TextIOBase), s.read())   # → True hello",
        "print(io.StringIO().write(b'x'))   # → TypeError"
      ],
      "related": [
        "io.stringio",
        "io.textiowrapper",
        "io.IOBase",
        "io.BufferedIOBase"
      ],
      "related_errors": []
    },
    {
      "id": "io.Writer",
      "title": "io.Writer",
      "kind": "function",
      "summary": {
        "ru": "Протокол io.Writer[T] — абстрактный класс с методом write(), для аннотаций и структурных проверок isinstance/issubclass. Python 3.14+.",
        "en": "The io.Writer[T] protocol: an abstract class with a write() method, used for annotations and structural isinstance/issubclass checks. Python 3.14+."
      },
      "body": {
        "ru": "Проверка здесь структурная: isinstance смотрит только на наличие метода write(), поэтому поток, пишущий байты, пройдёт её ровно так же, как текстовый — параметр T нужен аннотациям, а во время выполнения ничего не контролирует и сигнатуру метода никто не сверяет. Появился протокол только в 3.14: если код должен работать на 3.13 и ниже, для аннотаций берите typing.IO/TextIO или объявляйте собственный Protocol.",
        "en": "The check is structural: isinstance only looks for a write() method, so a byte-writing stream passes exactly like a text one — T matters to type checkers, not at runtime, and the method's signature is never verified. The protocol landed in 3.14, so code that must run on 3.13 or earlier should annotate with typing.IO/TextIO or declare its own Protocol instead."
      },
      "syntax": "io.Writer[T]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.Writer",
      "version": "3.14",
      "section": "Модуль io",
      "subcat": "протоколы",
      "color_group": "module",
      "aliases": [
        "протокол записи в поток",
        "объект умеющий писать"
      ],
      "keywords": [
        "io.Writer"
      ],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "w = io.StringIO()",
        "print(isinstance(w, io.Writer))        # → True",
        "print(w.write('hi'))                   # → 2",
        "print(w.getvalue())                    # → hi",
        "print(isinstance('text', io.Writer))   # → False"
      ],
      "related": [
        "io.IOBase",
        "io.BufferedWriter",
        "protocol",
        "typing.runtime_checkable"
      ],
      "related_errors": [
        "ValueError",
        "AttributeError"
      ]
    },
    {
      "id": "io.bytesio",
      "title": "io.BytesIO",
      "kind": "term",
      "summary": {
        "ru": "Файлоподобный объект для бинарных данных в памяти. Аналог StringIO для байтов.",
        "en": "A file-like object for binary data held in memory. The counterpart of StringIO for bytes."
      },
      "body": {
        "ru": "Классическая ловушка: после write() позиция стоит в конце буфера, поэтому read() вернёт пустые b'' — нужен либо seek(0), либо getvalue(), который отдаёт весь буфер независимо от текущей позиции. Основной сценарий — когда библиотека требует файловый объект, а данные хочется держать в памяти: собрать zip-архив или картинку, ничего не записывая на диск. После close() буфер освобождается, и getvalue() уже бросает ValueError, так что забирать данные надо до закрытия.",
        "en": "The usual trap: after write() the position sits at the end, so a following read() gives you b'' unless you seek(0) first, whereas getvalue() returns the whole buffer regardless of position. Reach for it when some library insists on a file object but you would rather keep the data in memory, for instance building a zip archive or an image without touching the disk. Once the object is closed the buffer is discarded and getvalue() raises ValueError, so grab the bytes before closing."
      },
      "syntax": "io.BytesIO(initial_bytes=b'')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.BytesIO",
      "version": "",
      "section": "Модуль io",
      "subcat": "байты",
      "color_group": "module",
      "aliases": [
        "файл из байтов в памяти",
        "бинарный буфер в памяти",
        "работать с байтами как с файлом"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import io",
        "buf = io.BytesIO()",
        "buf.write(b'\\x00\\x01\\x02')",
        "buf.getvalue() # → b'\\x00\\x01\\x02'",
        "buf.seek(0)",
        "buf.read(1) # → b'\\x00'",
        "b = io.BytesIO(b'data')",
        "b.read() # → b'data'",
        "len(io.BytesIO(b'abc').getvalue()) # → 3"
      ],
      "related": [
        "io.stringio",
        "bytes",
        "open"
      ],
      "related_errors": []
    },
    {
      "id": "io.open_code",
      "title": "io.open_code",
      "kind": "function",
      "summary": {
        "ru": "Открывает файл для чтения как исполняемый код с учётом аудит-хуков безопасности; предпочтительнее open() для загрузки кода (Python 3.8+).",
        "en": "Open a file for reading as executable code, honoring audit hooks (3.8+)."
      },
      "body": {
        "ru": "Это всегда бинарное чтение: режим 'rb', параметров encoding и newline нет, а путь ожидается абсолютной строкой. Сама функция ничего не проверяет и безопасности не добавляет — ценность в единой точке перехвата: приложение или встраивающая среда может подменить её реализацию (или повесить аудит-хук) и, например, отказаться отдавать неподписанный файл. Через неё же импорт-механизм читает то, что собирается выполнить.",
        "en": "It is always a binary read: mode 'rb', no encoding or newline arguments, and path is expected to be an absolute string. The call itself validates nothing and adds no security by itself — its value is being a single interception point, so an application or embedder can replace it (or attach an audit hook) and refuse, say, an unsigned file. The import machinery reads the code it is about to execute through the same entry point."
      },
      "syntax": "io.open_code(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.open_code",
      "version": "3.8",
      "section": "Модуль io",
      "subcat": "служебные",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "import json",
        "print(callable(io.open_code))   # → True",
        "with io.open_code(json.__file__) as f: print(f.mode, type(f).__name__)   # → rb BufferedReader",
        "print(io.open_code('no_such_file.py'))   # → FileNotFoundError",
        "print(io.open_code('script.py', 'w'))   # → TypeError"
      ],
      "related": [
        "open",
        "compile",
        "exec"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError",
        "IsADirectoryError"
      ]
    },
    {
      "id": "io.stringio",
      "title": "io.StringIO",
      "kind": "term",
      "summary": {
        "ru": "Файлоподобный объект, хранящий строковые данные в памяти. Удобен для тестирования и буферизации.",
        "en": "A file-like object that keeps string data in memory. Convenient for testing and for buffering."
      },
      "body": {
        "ru": "Курсор один на чтение и запись: после write() он стоит в конце, поэтому read() вернёт пустую строку, пока не сделаете seek(0) — а забрать всё содержимое независимо от позиции даёт getvalue(). После close() буфер уничтожается и getvalue() бросает ValueError, так что значение нужно снять до выхода из блока with. Хранит только str; для байтов есть io.BytesIO.",
        "en": "One cursor serves both reading and writing: after a write() it sits at the end, so read() gives an empty string until you seek(0) — getvalue() returns the whole buffer regardless of position. close() throws the buffer away and getvalue() then raises ValueError, so grab the value before leaving the with block. It holds str only; for bytes use io.BytesIO."
      },
      "syntax": "io.StringIO(initial_value='', newline='\\n')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.StringIO",
      "version": "",
      "section": "Модуль io",
      "subcat": "строки",
      "color_group": "module",
      "aliases": [
        "файл в памяти",
        "строка как файловый объект",
        "текстовый буфер в памяти"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import io",
        "buf = io.StringIO()",
        "buf.write('hello\\n')",
        "buf.write('world')",
        "buf.getvalue() # → 'hello\\nworld'",
        "buf.seek(0)",
        "buf.read() # → 'hello\\nworld'",
        "s = io.StringIO('line1\\nline2')",
        "list(s) # → ['line1\\n', 'line2']"
      ],
      "related": [
        "io.bytesio",
        "io.textiowrapper",
        "open",
        "sys.stdin-sys.stdout-sys.stderr"
      ],
      "related_errors": []
    },
    {
      "id": "io.text_encoding",
      "title": "io.text_encoding",
      "kind": "function",
      "summary": {
        "ru": "Возвращает переданную кодировку, а если это None — кодировку по умолчанию для текстовых файлов (обычно 'utf-8'); помогает библиотекам корректно проставлять encoding (Python 3.10+).",
        "en": "Return the given encoding, or the default text encoding if None (3.10+)."
      },
      "body": {
        "ru": "Функция ничего не открывает и не проверяет: любую непустую строку она возвращает как есть, включая несуществующее имя кодека — ошибка всплывёт уже в open(). При encoding равном None возвращается не имя кодировки, а строка 'locale' (или 'utf-8', если включён UTF-8-режим), которую open() понимает. Смысл вызывать её в своих обёртках над open() в том, что под -X warn_default_encoding она выдаст EncodingWarning, и благодаря stacklevel=2 предупреждение укажет на того, кто вызвал вашу обёртку, а не на саму обёртку.",
        "en": "It opens nothing and validates nothing: any non-empty string comes back unchanged, a misspelled codec name included, and the failure only surfaces later in open(). When encoding is None it returns the sentinel 'locale' (or 'utf-8' under UTF-8 Mode), not an actual codec name — open() knows what to do with it. Call it inside your own open() wrappers: under -X warn_default_encoding it raises EncodingWarning, and stacklevel=2 makes the warning point at your caller rather than at the wrapper."
      },
      "syntax": "io.text_encoding(encoding)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.text_encoding",
      "version": "3.10",
      "section": "Модуль io",
      "subcat": "служебные",
      "color_group": "module",
      "aliases": [
        "кодировка по умолчанию для текстовых файлов",
        "какая кодировка если encoding не указан"
      ],
      "keywords": [],
      "tags": [
        "io"
      ],
      "examples": [
        "import io",
        "print(io.text_encoding('utf-8'))   # → utf-8",
        "print(io.text_encoding('cp1251'))   # → cp1251",
        "print(io.text_encoding('no-such-codec'))   # → no-such-codec",
        "print(io.text_encoding('utf-8', 3))   # → utf-8",
        "print(io.text_encoding(None) in ('locale', 'utf-8'))   # → True"
      ],
      "related": [
        "open",
        "encodingwarning",
        "io.textiowrapper",
        "file-errors"
      ],
      "related_errors": []
    },
    {
      "id": "io.textiowrapper",
      "title": "io.TextIOWrapper",
      "kind": "term",
      "summary": {
        "ru": "Оборачивает бинарный поток, добавляя кодирование/декодирование текста. Используется в open() внутри.",
        "en": "Wraps a binary stream and adds text encoding and decoding. It is what open() uses internally."
      },
      "body": {
        "ru": "Закрытие обёртки закрывает и нижележащий бинарный поток — если он ещё нужен, сначала отвяжите его через detach(). Два неочевидных момента: при newline=None перевод строки при записи превращается в os.linesep (на Windows это пара CR LF), а seek() у текстового потока принимает не произвольные смещения, а только значения, ранее полученные от tell().",
        "en": "Closing the wrapper also closes the binary stream underneath, so call detach() first if you still need that stream. Two things the signature does not show: with newline=None a written newline is translated to os.linesep (CR LF on Windows), and seek() on a text stream accepts only the opaque values previously returned by tell(), not arbitrary byte offsets."
      },
      "syntax": "io.TextIOWrapper(buffer, encoding=None, errors=None, newline=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.TextIOWrapper",
      "version": "",
      "section": "Модуль io",
      "subcat": "обёртка",
      "color_group": "module",
      "aliases": [
        "обернуть байтовый поток в текстовый",
        "задать кодировку существующему потоку"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import io",
        "raw = io.BytesIO(b'hello')",
        "wrapper = io.TextIOWrapper(raw, encoding='utf-8')",
        "wrapper.read() # → 'hello'",
        "# Запись:",
        "buf = io.BytesIO()",
        "tw = io.TextIOWrapper(buf, encoding='utf-8')",
        "tw.write('мир')",
        "tw.flush()",
        "buf.getvalue() # → b'\\xd0\\xbc\\xd0\\xb8\\xd1\\x80'"
      ],
      "related": [
        "io.TextIOBase",
        "open",
        "io.BufferedReader",
        "io.stringio"
      ],
      "related_errors": []
    },
    {
      "id": "буферизация",
      "title": "Буферизация",
      "kind": "term",
      "summary": {
        "ru": "Буферизованный ввод-вывод накапливает данные в памяти перед записью. flush() принудительно сбрасывает буфер.",
        "en": "Buffered I/O collects the data in memory before writing it out. flush() forces the buffer out."
      },
      "body": {
        "ru": "buffering=0 допустим только в бинарном режиме — в текстовом open() бросит ValueError; строчная буферизация (buffering=1) наоборот имеет смысл лишь для текстовой записи. Буфер живёт в памяти процесса, поэтому аварийное завершение или os._exit() теряют несброшенное, а сам flush() лишь передаёт данные операционной системе — довести их до диска может только os.fsync(). Для вывода в консоль тот же эффект даёт print(..., flush=True).",
        "en": "buffering=0 is allowed in binary mode only — in text mode open() raises ValueError, while line buffering (buffering=1) makes sense only for text writing. The buffer sits in process memory, so a crash or os._exit() loses whatever was not flushed, and flush() itself only passes the data to the OS; getting it onto the actual disk takes os.fsync(). For console output the same effect is available as print(..., flush=True)."
      },
      "syntax": "open(file, buffering=-1)  # -1 авто, 0 без буфера (только binary), 1 строчный\nstream.flush()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#buffered-streams",
      "version": "",
      "section": "Модуль io",
      "subcat": "буферизация",
      "color_group": "module",
      "aliases": [
        "почему данные не записались в файл сразу",
        "принудительно сбросить буфер на диск",
        "запись пачками вместо посимвольной"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import io",
        "buf = io.StringIO()",
        "buf.write('text')",
        "buf.getvalue() # → 'text'  StringIO не нуждается в flush",
        "f = open('test_io.txt', 'w', buffering=1)",
        "f.write('line\\n')",
        "f.flush()  # строки сразу сбрасываются",
        "f.close()",
        "open('test_io.txt').read() # → 'line\\n'"
      ],
      "related": [
        "file-buffering",
        "file-flush",
        "open",
        "io.BufferedWriter"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.batched",
      "title": "itertools.batched",
      "kind": "function",
      "summary": {
        "ru": "Разбивает итерируемое на кортежи длины n (последний может быть короче); Python 3.12+.",
        "en": "Batch an iterable into tuples of length n (the last may be shorter); 3.12+."
      },
      "body": {
        "ru": "Возвращает ленивый итератор кортежей, а не список, — оборачивайте в list(), если нужен весь результат сразу. До 3.12 для нарезки писали zip(*[iter(it)]*n), но тот молча выбрасывал неполный последний кусок, а batched его сохраняет; с 3.13 есть strict=True, чтобы потребовать одинаковую длину всех кусков (иначе ValueError), а n меньше 1 всегда ошибка.",
        "en": "It returns a lazy iterator of tuples, not a list, so wrap it in list() when you need everything at once. The old pre-3.12 chunking idiom zip(*[iter(it)]*n) silently dropped an incomplete final batch, whereas batched keeps it; since 3.13 strict=True demands equal-sized batches (else ValueError), and n below 1 always raises."
      },
      "syntax": "itertools.batched(iterable, n)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.batched",
      "version": "3.12",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "разбить список на части",
        "нарезать последовательность группами",
        "пачки элементов"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.batched([1, 2, 3, 4, 5], 2)))   # → [(1, 2), (3, 4), (5,)]",
        "print(list(itertools.batched('abcdef', 3)))   # → [('a', 'b', 'c'), ('d', 'e', 'f')]",
        "print([sum(b) for b in itertools.batched(range(1, 7), 3)])   # → [6, 15]",
        "print(list(itertools.batched([], 3)))   # → []",
        "print(list(itertools.batched([1, 2, 3], 0)))   # → ValueError"
      ],
      "related": [
        "itertools.islice",
        "itertools.pairwise",
        "itertools.chain"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "itertools.combinations",
      "title": "itertools.combinations",
      "kind": "function",
      "summary": {
        "ru": "Все сочетания длины r из итерируемого (без повторов, порядок не важен).",
        "en": "All length-r combinations of the iterable (no repeats, order-independent)."
      },
      "body": {
        "ru": "Элементы считаются уникальными по позиции, а не по значению: если во входе есть повторы, в выводе появятся одинаковые кортежи. Число результатов — C(n, r), растёт комбинаторно, поэтому не оборачивайте combinations от большого входа в list() бездумно; сами кортежи идут в лексикографическом порядке входа, а при r больше n результат пуст.",
        "en": "Elements are treated as unique by position, not by value, so duplicate inputs produce duplicate output tuples. The result count is C(n, r) and grows explosively — don't blindly list() combinations of a large input; tuples come in the input's lexicographic order, and r greater than n yields nothing."
      },
      "syntax": "itertools.combinations(iterable, r)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.combinations",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "сочетания",
        "комбинаторика без повторений",
        "выбрать несколько элементов из набора"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.combinations([1, 2, 3], 2)))   # → [(1, 2), (1, 3), (2, 3)]",
        "print([''.join(p) for p in itertools.combinations('abc', 2)])   # → ['ab', 'ac', 'bc']",
        "print(len(list(itertools.combinations(range(5), 3))))   # → 10",
        "print(list(itertools.combinations([3, 1, 2], 2)))   # → [(3, 1), (3, 2), (1, 2)]",
        "print(list(itertools.combinations([1, 2], 3)))   # → []"
      ],
      "related": [
        "itertools.combinations_with_replacement",
        "itertools.permutations",
        "itertools.product"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.combinations_with_replacement",
      "title": "itertools.combinations_with_replacement",
      "kind": "function",
      "summary": {
        "ru": "Сочетания длины r с повторами элементов.",
        "en": "Length-r combinations allowing repeated elements."
      },
      "body": {
        "ru": "Отличие от combinations — один и тот же элемент можно взять несколько раз (кортежи неубывающие по позиции). От product(it, repeat=r) отличается тем, что не даёт переставленных дубликатов: только сочетания без учёта порядка, поэтому результатов C(n+r-1, r), а не n в степени r.",
        "en": "Unlike combinations, the same element may be picked more than once (tuples are non-decreasing by position). Unlike product(it, repeat=r) it omits reordered duplicates — only order-independent selections — so you get C(n+r-1, r) results instead of n**r."
      },
      "syntax": "itertools.combinations_with_replacement(iterable, r)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.combinations_with_replacement",
      "version": "3.1",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "сочетания с повторениями",
        "комбинации с возвращением"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.combinations_with_replacement([1, 2], 2)))   # → [(1, 1), (1, 2), (2, 2)]",
        "print(list(itertools.combinations_with_replacement('ab', 3)))   # → [('a', 'a', 'a'), ('a', 'a', 'b'), ('a', 'b', 'b'), ('b', 'b', 'b')]",
        "print(len(list(itertools.combinations_with_replacement(range(6), 2))))   # → 21",
        "print(list(itertools.combinations([1, 2], 2)))   # → [(1, 2)]",
        "print(list(itertools.combinations_with_replacement([], 2)))   # → []"
      ],
      "related": [
        "itertools.combinations",
        "itertools.permutations",
        "itertools.product"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.compress",
      "title": "itertools.compress",
      "kind": "function",
      "summary": {
        "ru": "Отбирает элементы data, где соответствующий элемент selectors истинен.",
        "en": "Select elements of data where the corresponding selector is true."
      },
      "body": {
        "ru": "Останавливается на более коротком из двух аргументов, лишний хвост игнорируется. Удобен, когда булева маска уже посчитана заранее (например, другим фильтром) и её хочется переиспользовать — читается чище, чем comprehension с zip; selectors — любые истинные или ложные значения, не обязательно 0 и 1.",
        "en": "It stops at the shorter of the two arguments and ignores the leftover tail. It shines when you already have a boolean mask computed elsewhere and want to reuse it — cleaner than a comprehension zipping the two — and selectors can be any truthy/falsy values, not just 0 and 1."
      },
      "syntax": "itertools.compress(data, selectors)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.compress",
      "version": "3.1",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "фильтр по маске",
        "выбрать элементы по флагам"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.compress(['a', 'b', 'c', 'd'], [1, 0, 1, 0])))   # → ['a', 'c']",
        "nums = [5, 12, 7, 20]",
        "print(list(itertools.compress(nums, [n > 10 for n in nums])))   # → [12, 20]",
        "print(list(itertools.compress([1, 2, 3], [1, 1])))   # → [1, 2]",
        "print(list(itertools.compress(['a', 'b', 'c'], ['', 'x', None])))   # → ['b']"
      ],
      "related": [
        "itertools.filterfalse",
        "filter",
        "zip"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.count",
      "title": "itertools.count",
      "kind": "function",
      "summary": {
        "ru": "Бесконечный счётчик: start, start+step, … Часто с zip()/islice().",
        "en": "An infinite counter: start, start+step, …"
      },
      "body": {
        "ru": "Счётчик бесконечен: list(count()) или for без break повесит программу и съест память — ограничивайте его через islice(), zip() или takewhile(). Типичное применение — генератор индексов в паре с zip() (как enumerate), а step может быть и дробным.",
        "en": "The counter is infinite: list(count()) or a for-loop without a break will hang and exhaust memory — bound it with islice(), zip(), or takewhile(). A common use is as an index generator paired with zip() (enumerate-style), and step may be a float."
      },
      "syntax": "itertools.count(start=0, step=1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.count",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "бесконечный счётчик",
        "нумерация с заданным шагом",
        "бесконечная последовательность чисел"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "c = itertools.count(10)",
        "print(next(c), next(c), next(c))   # → 10 11 12",
        "print(list(itertools.islice(itertools.count(0, 5), 4)))   # → [0, 5, 10, 15]",
        "print(list(zip(itertools.count(1), 'abc')))   # → [(1, 'a'), (2, 'b'), (3, 'c')]",
        "print(list(itertools.islice(itertools.count(3, -1), 5)))   # → [3, 2, 1, 0, -1]"
      ],
      "related": [
        "itertools.cycle",
        "itertools.repeat",
        "itertools.islice",
        "enumerate"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.cycle",
      "title": "itertools.cycle",
      "kind": "function",
      "summary": {
        "ru": "Бесконечно повторяет элементы итерируемого по кругу.",
        "en": "Repeat the elements of an iterable endlessly, cycling."
      },
      "body": {
        "ru": "На первом проходе cycle сохраняет копию всех элементов, чтобы потом повторять их бесконечно, — поэтому на огромном или бесконечном источнике он незаметно съест память. Сам итератор никогда не заканчивается: list(cycle(...)) зависнет навсегда, всегда ограничивай его через islice или через next() в цикле с собственным условием выхода.",
        "en": "On its first pass cycle stores a copy of every element so it can repeat them forever, which means it quietly holds the whole input in memory on huge or infinite sources. The iterator itself never ends: list(cycle(...)) hangs forever, so always bound it with islice or a next() loop that has its own exit condition."
      },
      "syntax": "itertools.cycle(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.cycle",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "зациклить последовательность",
        "повторять элементы по кругу"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "c = itertools.cycle([1, 2, 3])",
        "print([next(c) for _ in range(5)])   # → [1, 2, 3, 1, 2]",
        "print(list(itertools.islice(itertools.cycle('ab'), 5)))   # → ['a', 'b', 'a', 'b', 'a']",
        "print(list(zip('abcd', itertools.cycle([0, 1]))))   # → [('a', 0), ('b', 1), ('c', 0), ('d', 1)]",
        "print(list(itertools.islice(itertools.cycle([]), 3)))   # → []"
      ],
      "related": [
        "itertools.repeat",
        "itertools.count",
        "itertools.islice"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.dropwhile",
      "title": "itertools.dropwhile",
      "kind": "function",
      "summary": {
        "ru": "Пропускает элементы, пока предикат истинен, затем отдаёт остаток целиком.",
        "en": "Drop elements while the predicate is true, then yield the rest."
      },
      "body": {
        "ru": "Предикат проверяется только до первого False — после этого dropwhile отдаёт весь остаток как есть, даже если дальше снова попадаются элементы, которые предикату подходят. Именно этим он отличается от filter, который проверяет каждый элемент по отдельности.",
        "en": "The predicate is tested only until it first returns False — after that dropwhile passes the entire remainder through unchanged, even elements that would again satisfy it. That is exactly what separates it from filter, which evaluates every element individually."
      },
      "syntax": "itertools.dropwhile(predicate, iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.dropwhile",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "пропустить элементы пока условие истинно",
        "отбросить начало последовательности"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.dropwhile(lambda x: x < 3, [1, 2, 3, 1, 2])))   # → [3, 1, 2]",
        "print(list(itertools.dropwhile(lambda x: x == 0, [0, 0, 5, 0, 7])))  # → [5, 0, 7]",
        "print(''.join(itertools.dropwhile(str.isspace, '   код')))  # → код",
        "print(list(itertools.dropwhile(lambda x: x > 0, [1, 2, 3])))  # → []",
        "print(list(filter(lambda x: x >= 3, [1, 2, 3, 1, 2])))  # → [3]"
      ],
      "related": [
        "itertools.takewhile",
        "itertools.filterfalse",
        "itertools.islice"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.filterfalse",
      "title": "itertools.filterfalse",
      "kind": "function",
      "summary": {
        "ru": "Отдаёт элементы, для которых предикат ложен (обратное filter()).",
        "en": "Yield elements for which the predicate is false (the opposite of filter())."
      },
      "body": {
        "ru": "Как и у filter, значение predicate=None означает «проверять сам элемент на истинность», так что filterfalse(None, it) отдаёт именно ложные элементы — нули, пустые строки, None. В паре с filter на одном источнике (через tee) это стандартный способ разбить данные на две группы по условию.",
        "en": "As with filter, passing predicate=None means \"test the element's own truthiness\", so filterfalse(None, it) yields exactly the falsy items — zeros, empty strings, None. Paired with filter over one source (via tee) it is the standard way to split data into two groups by a condition."
      },
      "syntax": "itertools.filterfalse(predicate, iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.filterfalse",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "обратный фильтр",
        "оставить элементы не прошедшие условие"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.filterfalse(lambda x: x % 2, [1, 2, 3, 4])))   # → [2, 4]",
        "print(list(itertools.filterfalse(None, [0, 1, '', 'a', [], [2]])))  # → [0, '', []]",
        "print(list(itertools.filterfalse(lambda w: len(w) > 3, ['кот', 'дом', 'программа', 'и'])))  # → ['кот', 'дом', 'и']",
        "print(list(itertools.filterfalse(lambda x: True, [1, 2, 3])))  # → []",
        "print(list(filter(lambda x: x % 2, [1, 2, 3, 4])))  # → [1, 3]"
      ],
      "related": [
        "filter",
        "itertools.compress",
        "itertools.dropwhile"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.pairwise",
      "title": "itertools.pairwise",
      "kind": "function",
      "summary": {
        "ru": "Соседние пары элементов: (s0,s1), (s1,s2), … (Python 3.10+).",
        "en": "Successive overlapping pairs: (s0,s1), (s1,s2), … (3.10+)."
      },
      "body": {
        "ru": "В отличие от старого приёма zip(seq, seq[1:]), pairwise работает с любым итерируемым — включая генераторы и бесконечные потоки, — потому что лениво держит в памяти только предыдущий элемент. Если на входе меньше двух элементов, результат пустой.",
        "en": "Unlike the old zip(seq, seq[1:]) trick, pairwise works on any iterable — generators and infinite streams included — because it lazily keeps only the previous element around. With fewer than two input items it yields nothing."
      },
      "syntax": "itertools.pairwise(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.pairwise",
      "version": "3.10",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "соседние пары элементов",
        "пары подряд идущих значений",
        "сравнить соседние элементы списка"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.pairwise([1, 2, 3, 4])))   # → [(1, 2), (2, 3), (3, 4)]",
        "print(list(itertools.pairwise('abc')))  # → [('a', 'b'), ('b', 'c')]",
        "print([b - a for a, b in itertools.pairwise([10, 13, 12, 20])])  # → [3, -1, 8]",
        "print(all(a <= b for a, b in itertools.pairwise([1, 2, 2, 5])))  # → True",
        "print(list(itertools.pairwise([7])))  # → []"
      ],
      "related": [
        "zip",
        "itertools.batched",
        "itertools.tee"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.permutations",
      "title": "itertools.permutations",
      "kind": "function",
      "summary": {
        "ru": "Все перестановки длины r из итерируемого (порядок важен).",
        "en": "All length-r permutations of the iterable (order matters)."
      },
      "body": {
        "ru": "Число результатов растёт как n!/(n−r)!, поэтому на большом входе перебор взрывается почти мгновенно — не заворачивай его в list() бездумно. Элементы считаются уникальными по позиции, а не по значению: в [1, 1] две единицы дадут внешне «дублирующиеся» кортежи, а если порядок внутри группы не важен, тебе нужен combinations.",
        "en": "The result count grows as n!/(n−r)!, so it explodes almost instantly on large inputs — don't wrap it in list() carelessly. Elements are treated as unique by position, not by value: two equal items like [1, 1] produce look-alike duplicate tuples, and if order within a group doesn't matter you want combinations instead."
      },
      "syntax": "itertools.permutations(iterable, r=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.permutations",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "перестановки",
        "все порядки элементов",
        "перебор всех расстановок"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.permutations([1, 2, 3], 2)))   # → [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]",
        "print([''.join(p) for p in itertools.permutations('abc')])  # → ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']",
        "print(len(list(itertools.permutations(range(4)))))  # → 24",
        "print(list(itertools.permutations('aab', 2)))  # → [('a', 'a'), ('a', 'b'), ('a', 'a'), ('a', 'b'), ('b', 'a'), ('b', 'a')]",
        "print(list(itertools.permutations([1, 2], 3)))  # → []"
      ],
      "related": [
        "itertools.combinations",
        "itertools.product",
        "itertools.combinations_with_replacement"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.product",
      "title": "itertools.product",
      "kind": "function",
      "summary": {
        "ru": "Декартово произведение итерируемых (вложенные циклы как плоский итератор).",
        "en": "Cartesian product of iterables (nested loops as a flat iterator)."
      },
      "body": {
        "ru": "product заменяет вложенные for-циклы одним плоским итератором, но перед началом полностью считывает все входы в память — бесконечный или огромный итерируемый передавать нельзя. Крайний правый аргумент меняется быстрее всех; repeat=N даёт произведение набора с самим собой N раз (product(xs, repeat=3) вместо тройного цикла).",
        "en": "product flattens nested for-loops into one iterator, but it fully materializes every input into memory first — so an infinite or huge iterable will hang or exhaust RAM. The rightmost argument varies fastest, and repeat=N gives the product of a set with itself N times instead of an N-deep loop nest."
      },
      "syntax": "itertools.product(*iterables, repeat=1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.product",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "декартово произведение",
        "все комбинации из нескольких списков",
        "замена вложенным циклам"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.product([1, 2], ['a', 'b'])))   # → [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]",
        "print(list(itertools.product('ab', repeat=2)))  # → [('a', 'a'), ('a', 'b'), ('b', 'a'), ('b', 'b')]",
        "print([''.join(p) for p in itertools.product('01', repeat=3)])  # → ['000', '001', '010', '011', '100', '101', '110', '111']",
        "print(len(list(itertools.product(range(3), range(4)))))  # → 12",
        "print(list(itertools.product([1, 2], [])))  # → []"
      ],
      "related": [
        "itertools.permutations",
        "вложенные-циклы",
        "itertools.combinations"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.repeat",
      "title": "itertools.repeat",
      "kind": "function",
      "summary": {
        "ru": "Повторяет одно значение: бесконечно или ровно times раз.",
        "en": "Repeat a single value endlessly or exactly `times` times."
      },
      "body": {
        "ru": "Без аргумента times поток бесконечен — не оборачивай его в list(), а комбинируй с zip()/map(), чтобы подать константу к каждому элементу (map(pow, base, repeat(2))). repeat отдаёт один и тот же объект, а не копии, поэтому для изменяемого значения все элементы окажутся ссылками на одну и ту же вещь.",
        "en": "With no times argument the stream is infinite — never wrap it in list(); pair it with zip()/map() to feed a constant alongside each element (map(pow, base, repeat(2))). repeat yields the same object every time, not copies, so with a mutable value every item is a reference to one shared thing."
      },
      "syntax": "itertools.repeat(obj, times)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.repeat",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "повторить значение несколько раз",
        "последовательность одинаковых элементов"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.repeat('x', 3)))   # → ['x', 'x', 'x']",
        "print(sum(itertools.repeat(5, 4)))   # → 20",
        "print(list(map(pow, [1, 2, 3], itertools.repeat(2))))   # → [1, 4, 9]",
        "print(list(itertools.islice(itertools.repeat(7), 3)))   # → [7, 7, 7]",
        "print(list(itertools.repeat('x', -1)))   # → []"
      ],
      "related": [
        "itertools.cycle",
        "itertools.count",
        "map"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.starmap",
      "title": "itertools.starmap",
      "kind": "function",
      "summary": {
        "ru": "Как map(), но аргументы берутся распаковкой кортежей: f(*args).",
        "en": "Like map(), but arguments are taken by unpacking each tuple: f(*args)."
      },
      "body": {
        "ru": "Бери starmap, когда аргументы уже сгруппированы в кортежи внутри одного итерируемого; обычный map(f, a, b) — когда итерируемые лежат отдельно. По сути starmap(f, seq) — это (f(*args) for args in seq).",
        "en": "Reach for starmap when the arguments already come grouped as tuples inside one iterable; use plain map(f, a, b) when the iterables are separate. It is essentially (f(*args) for args in seq)."
      },
      "syntax": "itertools.starmap(function, iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.starmap",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "применить функцию к кортежам аргументов",
        "отображение с распаковкой аргументов"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.starmap(pow, [(2, 3), (3, 2)])))   # → [8, 9]",
        "print(list(itertools.starmap(max, [(1, 5), (9, 2), (4, 4)])))   # → [5, 9, 4]",
        "print(list(itertools.starmap(lambda n, s: f'{n}: {s}', [('Ann', 90), ('Bob', 75)])))   # → ['Ann: 90', 'Bob: 75']",
        "print(list(map(pow, [2, 3], [3, 2])))   # → [8, 9]",
        "print(list(itertools.starmap(pow, [(2,)])))   # → TypeError"
      ],
      "related": [
        "map",
        "zip",
        "args"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.takewhile",
      "title": "itertools.takewhile",
      "kind": "function",
      "summary": {
        "ru": "Отдаёт элементы, пока предикат истинен, затем останавливается.",
        "en": "Yield elements while the predicate is true, then stop."
      },
      "body": {
        "ru": "Останавливается навсегда на первом элементе, где предикат ложь, — в отличие от filter(), который просто пропускает неподходящие и идёт дальше (для противоположного поведения есть dropwhile). Учти: элемент, оборвавший выдачу, уже вытянут из исходного итератора и потерян, если продолжить читать тот же итератор.",
        "en": "It stops for good at the first element where the predicate is false — unlike filter(), which merely skips non-matching items and keeps going (dropwhile is the mirror image). Note that the element that breaks the run is already pulled from the source iterator and lost if you keep reading that same iterator."
      },
      "syntax": "itertools.takewhile(predicate, iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.takewhile",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "брать элементы пока условие истинно",
        "остановиться на первом несовпадении",
        "начало последовательности по условию"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.takewhile(lambda x: x < 3, [1, 2, 3, 1])))   # → [1, 2]",
        "print(''.join(itertools.takewhile(str.isdigit, '2026год')))   # → 2026",
        "print(list(itertools.takewhile(lambda x: x > 0, [-1, 5, 7])))   # → []",
        "print(list(filter(lambda x: x < 3, [1, 2, 3, 1])))   # → [1, 2, 1]",
        "print(list(itertools.dropwhile(lambda x: x < 3, [1, 2, 3, 1])))   # → [3, 1]"
      ],
      "related": [
        "itertools.dropwhile",
        "itertools.islice",
        "itertools.filterfalse"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.tee",
      "title": "itertools.tee",
      "kind": "function",
      "summary": {
        "ru": "Создаёт n независимых итераторов из одного (исходный дальше использовать нельзя).",
        "en": "Create n independent iterators from a single iterable."
      },
      "body": {
        "ru": "После tee исходный итератор использовать нельзя — работай только с возвращёнными ветками. Если одну ветку прочитать далеко вперёд другой, tee буферизует все промежуточные элементы в памяти, так что при большом разрыве проще сделать list(); ветки к тому же не потокобезопасны.",
        "en": "After tee, stop using the original iterator and work only through the returned branches. If one branch races far ahead of another, tee buffers every element in between in memory — so for a large gap a plain list() is cheaper — and the branches are not thread-safe."
      },
      "syntax": "itertools.tee(iterable, n=2)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.tee",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "размножить итератор",
        "копия генератора",
        "несколько независимых итераторов"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "a, b = itertools.tee([1, 2, 3])",
        "print(list(a), list(b))   # → [1, 2, 3] [1, 2, 3]",
        "print(list(a))   # → []",
        "c, d, e = itertools.tee([1, 2, 3], 3)",
        "print(next(c), next(c), next(d), list(e))   # → 1 2 1 [1, 2, 3]"
      ],
      "related": [
        "iter-next",
        "itertools.pairwise",
        "itertools.islice"
      ],
      "related_errors": []
    },
    {
      "id": "itertools.zip_longest",
      "title": "itertools.zip_longest",
      "kind": "function",
      "summary": {
        "ru": "Как zip(), но продолжает до самого длинного итерируемого, добивая fillvalue.",
        "en": "Like zip(), but continues to the longest iterable, padding with fillvalue."
      },
      "body": {
        "ru": "Ленивая — отдаёт итератор, значения тянутся по требованию. Главная ловушка: fillvalue один на все короткие последовательности, а его дефолт None легко совпадает с настоящим None в данных, и тогда не отличить добитый пропуск от реального значения. Противоположность — zip(..., strict=True) из 3.10, который наоборот падает, если длины не совпали.",
        "en": "It is lazy — it returns an iterator and produces tuples on demand. The catch: fillvalue is a single shared value for every short iterable, and its default None easily collides with a genuine None in your data, so you cannot tell padding apart from a real value. Its opposite is zip(..., strict=True) from 3.10, which instead raises when the lengths differ."
      },
      "syntax": "itertools.zip_longest(*iterables, fillvalue=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/itertools.html#itertools.zip_longest",
      "version": "",
      "section": "Модуль itertools",
      "subcat": "itertools",
      "color_group": "iter",
      "aliases": [
        "объединить списки разной длины",
        "заполнить недостающие элементы при объединении"
      ],
      "keywords": [],
      "tags": [
        "itertools"
      ],
      "examples": [
        "import itertools",
        "print(list(itertools.zip_longest([1, 2], ['a'], fillvalue='?')))   # → [(1, 'a'), (2, '?')]",
        "print(list(itertools.zip_longest([1, 2], ['a'])))   # → [(1, 'a'), (2, None)]",
        "print(list(zip([1, 2], ['a'])))   # → [(1, 'a')]",
        "print([sum(p) for p in itertools.zip_longest([1, 2, 3], [10, 20], fillvalue=0)])   # → [11, 22, 3]",
        "print(list(itertools.zip_longest('ab', '1', '', fillvalue='-')))   # → [('a', '1', '-'), ('b', '-', '-')]"
      ],
      "related": [
        "zip",
        "zip-strict-true",
        "itertools.chain"
      ],
      "related_errors": []
    },
    {
      "id": "json.JSONDecoder",
      "title": "json.JSONDecoder",
      "kind": "term",
      "summary": {
        "ru": "Класс-декодер JSON → объекты Python; позволяет настроить разбор (напр. хук объектов). json.loads использует его внутри.",
        "en": "The JSON→Python decoder class; allows customizing parsing (e.g. object hooks)."
      },
      "body": {
        "ru": "Для разового разбора класс не нужен — loads принимает те же object_hook, parse_float и прочие настройки и сам создаёт декодер под капотом. Держать экземпляр стоит ради raw_decode(s): он разбирает первое JSON-значение и возвращает пару (объект, индекс конца), поэтому им можно пройтись по строке с несколькими документами подряд, где обычный loads падает с Extra data.",
        "en": "For a one-off parse you do not need this class: loads takes the same object_hook, parse_float and friends and builds a decoder for you. An instance earns its keep through raw_decode(s), which parses only the first JSON value and returns (object, end index) — that lets you walk a string holding several concatenated documents, where plain loads would fail with Extra data."
      },
      "syntax": "json.JSONDecoder().decode(s)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/json.html#json.JSONDecoder",
      "version": "",
      "section": "Модуль json",
      "subcat": "классы",
      "color_group": "module",
      "aliases": [
        "настроить разбор json",
        "свой декодер json"
      ],
      "keywords": [],
      "tags": [
        "json"
      ],
      "examples": [
        "import json",
        "print(json.JSONDecoder().decode('[1, 2]'))   # → [1, 2]",
        "print(json.JSONDecoder().decode('{\"a\": 1, \"b\": null}'))   # → {'a': 1, 'b': None}",
        "print(json.JSONDecoder(object_hook=lambda d: sum(d.values())).decode('{\"a\": 1, \"b\": 2}'))   # → 3",
        "print(json.JSONDecoder().raw_decode('[1, 2] tail'))   # → ([1, 2], 6)",
        "print(json.JSONDecoder().decode('{oops}'))   # → JSONDecodeError"
      ],
      "related": [
        "json.loads",
        "json.JSONEncoder",
        "json.decoder.jsondecodeerror"
      ],
      "related_errors": []
    },
    {
      "id": "json.JSONEncoder",
      "title": "json.JSONEncoder",
      "kind": "term",
      "summary": {
        "ru": "Класс-кодер объектов Python → JSON; для сериализации нестандартных типов переопределяют метод default. json.dumps использует его внутри.",
        "en": "The Python→JSON encoder class; override default() to serialize custom types."
      },
      "body": {
        "ru": "На практике свой кодер редко создают вручную — подкласс передают как json.dumps(obj, cls=MyEncoder). Метод default вызывается только для значений, которые кодер не осилил сам, и обязан вернуть уже сериализуемый объект (словарь, список, строку), а не готовый JSON-текст; для всего по-прежнему чужого нужно вызвать super().default(o), чтобы получить честный TypeError. Заодно помни: кортежи превратятся в массивы, а нестроковые ключи словаря будут приведены к строкам.",
        "en": "In practice you rarely instantiate the encoder by hand — you subclass it and pass it as json.dumps(obj, cls=MyEncoder). default() is called only for values the encoder could not handle itself, and it must return something already serialisable (a dict, list, string), not a chunk of JSON text; for anything still unknown, call super().default(o) so a proper TypeError is raised. Note too that tuples come out as arrays and non-string dict keys are coerced to strings."
      },
      "syntax": "json.JSONEncoder().encode(obj)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/json.html#json.JSONEncoder",
      "version": "",
      "section": "Модуль json",
      "subcat": "классы",
      "color_group": "module",
      "aliases": [
        "сериализация своих классов в json",
        "как сохранить нестандартный тип в json"
      ],
      "keywords": [],
      "tags": [
        "json"
      ],
      "examples": [
        "import json",
        "print(json.JSONEncoder().encode([1, 2]))   # → [1, 2]",
        "print(json.JSONEncoder().encode({'a': 1, 'b': None}))   # → {\"a\": 1, \"b\": null}",
        "print(json.JSONEncoder(sort_keys=True).encode({'b': 2, 'a': 1}))   # → {\"a\": 1, \"b\": 2}",
        "print(json.JSONEncoder().encode(range(3)))   # → TypeError",
        "print(json.JSONEncoder(default=str).encode(range(3)))   # → \"range(0, 3)\""
      ],
      "related": [
        "json.dumps",
        "json.JSONDecoder",
        "json.dump"
      ],
      "related_errors": []
    },
    {
      "id": "json.dump",
      "title": "json.dump",
      "kind": "function",
      "summary": {
        "ru": "Сериализует объект в JSON и пишет в файловый объект (потоково, без промежуточной строки).",
        "en": "Serialize an object as JSON and write it to a file object."
      },
      "body": {
        "ru": "dump отдаёт текст, поэтому файл должен быть открыт в текстовом режиме ('w'), а не 'wb' — с бинарным получите TypeError. По умолчанию ensure_ascii=True, и кириллица уедет в файл escape-последовательностями ру…; чтобы буквы остались читаемыми, нужны ensure_ascii=False и open(..., encoding='utf-8'). Перевод строки в конец не дописывается — при записи нескольких документов подряд его добавляют вручную.",
        "en": "dump emits text, so the file must be opened in text mode ('w'); a binary handle raises TypeError. With the default ensure_ascii=True every non-ASCII character is written as a \\uXXXX escape — pass ensure_ascii=False and open the file with encoding='utf-8' to keep it readable. No trailing newline is written, which matters if you append several documents to one file."
      },
      "syntax": "json.dump(obj, fp)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/json.html#json.dump",
      "version": "",
      "section": "Модуль json",
      "subcat": "json",
      "color_group": "op",
      "aliases": [
        "сохранить словарь в файл",
        "записать json в файл",
        "сериализация в файл"
      ],
      "keywords": [],
      "tags": [
        "json"
      ],
      "examples": [
        "import json, io",
        "buf = io.StringIO()",
        "json.dump({'a': 1}, buf)",
        "print(buf.getvalue())   # → {\"a\": 1}"
      ],
      "related": [
        "json.dumps",
        "json.load",
        "open"
      ],
      "related_errors": [
        "TypeError",
        "ValueError"
      ]
    },
    {
      "id": "json.dumps",
      "title": "json.dumps",
      "kind": "function",
      "summary": {
        "ru": "Сериализует объект Python в JSON-строку (dict/list/str/int/float/bool/None).",
        "en": "Serialize a Python object to a JSON string."
      },
      "body": {
        "ru": "В JSON ключи бывают только строковыми: dumps({1: 'a'}) даст {\"1\": \"a\"}, и после обратного разбора ключ останется строкой — тип не восстановится. Всё, что вне dict/list/tuple/str/int/float/bool/None (set, datetime, Decimal, свои классы), роняет TypeError; такие объекты переводят через параметр default. Ещё nan и inf сериализуются как NaN/Infinity — Python их прочитает, а строгий чужой парсер откажется.",
        "en": "JSON keys are always strings: dumps({1: 'a'}) gives {\"1\": \"a\"}, and parsing it back leaves the key as a string — the original type is gone. Anything outside dict/list/tuple/str/int/float/bool/None (sets, datetime, Decimal, your own classes) raises TypeError; convert them via the default= hook. Note also that nan and inf come out as NaN/Infinity, which Python reads back but strict third-party parsers reject."
      },
      "syntax": "json.dumps(obj, *, indent=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/json.html#json.dumps",
      "version": "",
      "section": "Модуль json",
      "subcat": "json",
      "color_group": "op",
      "aliases": [
        "преобразовать словарь в строку json",
        "сериализация объекта в строку",
        "вывод json с отступами"
      ],
      "keywords": [],
      "tags": [
        "json"
      ],
      "examples": [
        "import json",
        "print(json.dumps({'a': 1, 'b': [2, 3]}))   # → {\"a\": 1, \"b\": [2, 3]}",
        "print(json.dumps([1, 2, 3]))                 # → [1, 2, 3]",
        "print(json.dumps('привет', ensure_ascii=False))   # → \"привет\"",
        "print(json.dumps({'b': 1, 'a': 2}, sort_keys=True))   # → {\"a\": 2, \"b\": 1}",
        "print(json.dumps({1, 2}))   # → TypeError"
      ],
      "related": [
        "json.loads",
        "json.dump",
        "json.JSONEncoder"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "json.load",
      "title": "json.load",
      "kind": "function",
      "summary": {
        "ru": "Читает JSON из файлового объекта и возвращает объект Python.",
        "en": "Read JSON from a file object and return a Python object."
      },
      "body": {
        "ru": "Несмотря на «файловый» вид, потоковости здесь нет: load по сути делает loads(fp.read()) и держит весь документ в памяти, так что для огромных файлов берут формат JSON Lines (разбирать построчно) или сторонний ijson. В файле должен лежать ровно один JSON-документ: два объекта подряд или любой лишний текст после него дают JSONDecodeError с сообщением про Extra data.",
        "en": "Despite taking a file object, this is not streaming: load is essentially loads(fp.read()) and holds the whole document in memory, so huge files call for JSON Lines (parsed line by line) or a third-party parser like ijson. The file must hold exactly one JSON document — two objects back to back, or any leftover text after the first one, raise JSONDecodeError with an Extra data message."
      },
      "syntax": "json.load(fp)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/json.html#json.load",
      "version": "",
      "section": "Модуль json",
      "subcat": "json",
      "color_group": "op",
      "aliases": [
        "прочитать json из файла",
        "загрузить словарь из файла"
      ],
      "keywords": [],
      "tags": [
        "json"
      ],
      "examples": [
        "import json, io",
        "print(json.load(io.StringIO('[1, 2, 3]')))   # → [1, 2, 3]",
        "print(json.load(io.StringIO('{\"name\": \"Bob\", \"age\": 30}')))   # → {'name': 'Bob', 'age': 30}",
        "data = json.load(io.StringIO('{\"items\": [1, 2, 3]}'))",
        "print(sum(data['items']))   # → 6",
        "print(json.load(io.StringIO('')))   # → JSONDecodeError (пустой ввод)"
      ],
      "related": [
        "json.loads",
        "json.dump",
        "json.decoder.jsondecodeerror"
      ],
      "related_errors": [
        "ValueError",
        "AttributeError"
      ]
    },
    {
      "id": "json.loads",
      "title": "json.loads",
      "kind": "function",
      "summary": {
        "ru": "Разбирает JSON-строку в объект Python.",
        "en": "Parse a JSON string into a Python object."
      },
      "body": {
        "ru": "JSON — не Python-литерал: true/false/null вместо True/False/None, только двойные кавычки, никаких висячих запятых. Именно на этом обычно и падает json.JSONDecodeError (наследник ValueError) — ловите его по имени, а у объекта исключения есть .pos, .lineno и .colno, которые показывают место поломки. Разбирать JSON через eval нельзя: это дыра в безопасности, да и на true/null такой разбор всё равно упадёт.",
        "en": "JSON is not a Python literal: true/false/null instead of True/False/None, double quotes only, no trailing commas — this is what usually triggers json.JSONDecodeError (a ValueError subclass). Catch it by that name and read its .pos, .lineno and .colno to point at the exact spot that broke. Never parse JSON with eval: it is a security hole and it chokes on true/null anyway."
      },
      "syntax": "json.loads(s)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/json.html#json.loads",
      "version": "",
      "section": "Модуль json",
      "subcat": "json",
      "color_group": "op",
      "aliases": [
        "разобрать json-строку",
        "распарсить json",
        "строку в словарь"
      ],
      "keywords": [],
      "tags": [
        "json"
      ],
      "examples": [
        "import json",
        "print(json.loads('{\"x\": 5, \"y\": true}'))   # → {'x': 5, 'y': True}",
        "print(json.loads('[1, 2, 3]'))              # → [1, 2, 3]",
        "data = json.loads('{\"user\": {\"name\": \"Bob\", \"scores\": [4, 5]}}')",
        "print(data['user']['scores'][1])   # → 5",
        "print(json.loads(\"{'x': 1}\"))   # → JSONDecodeError (нужны двойные кавычки)"
      ],
      "related": [
        "json.load",
        "json.dumps",
        "json.decoder.jsondecodeerror"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "logging-FileHandler",
      "title": "logging.FileHandler",
      "kind": "term",
      "summary": {
        "ru": "Обработчик, записывающий логи в файл. Поддерживает режимы 'a' (дополнять) и 'w' (перезаписывать), а также кодировку.",
        "en": "A handler that writes log records to a file. It supports the modes 'a' (append) and 'w' (overwrite), as well as an encoding."
      },
      "body": {
        "ru": "Без encoding='utf-8' файл пишется в кодировке локали (на Windows это обычно cp1251): в другой системе лог покажется кракозябрами, а любой символ вне этой кодировки записан не будет — logging напечатает ошибку в stderr и потеряет строку. Режим 'w' обрезает лог при каждом запуске программы, так что для истории нужен 'a' (он и стоит по умолчанию). Файл открывается сразу при создании обработчика (если не задан delay=True) и растёт без ограничений — за размером следят RotatingFileHandler и TimedRotatingFileHandler из logging.handlers.",
        "en": "Without encoding='utf-8' the file uses the locale encoding (cp1251 on a Russian Windows): the log turns into mojibake elsewhere, and any character outside that encoding never gets written — logging reports the failure on stderr and drops the line. Mode 'w' truncates the log on every run, so keep the default 'a' when you want history. The file is opened as soon as the handler is built (unless delay=True) and grows without limit — use RotatingFileHandler or TimedRotatingFileHandler from logging.handlers for that."
      },
      "syntax": "handler = logging.FileHandler(filename, mode='a', encoding=None, delay=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/logging.handlers.html#logging.FileHandler",
      "version": "",
      "section": "Модуль logging",
      "subcat": "обработчики",
      "color_group": "module",
      "aliases": [
        "записывать логи в файл",
        "лог-файл"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import logging",
        "logger = logging.getLogger('myapp')",
        "fh = logging.FileHandler('app.log', mode='a', encoding='utf-8')",
        "fh.setFormatter(logging.Formatter('%(asctime)s %(message)s'))",
        "logger.addHandler(fh)",
        "logger.warning('Записано в файл')"
      ],
      "related": [
        "logging-StreamHandler",
        "logging-Handler",
        "logging-Formatter"
      ],
      "related_errors": []
    },
    {
      "id": "logging-Formatter",
      "title": "logging.Formatter",
      "kind": "term",
      "summary": {
        "ru": "Задаёт формат строк лога: время, уровень, имя логгера, сообщение и т.д. Привязывается к Handler.",
        "en": "Sets the layout of a log line: the time, the level, the logger name, the message and so on. It is attached to a Handler."
      },
      "body": {
        "ru": "Форматтер ставится на обработчик через handler.setFormatter(), а не на логгер — у Logger такого метода вообще нет, и это самая частая причина «формат не применился». Плейсхолдеры по умолчанию в стиле %-подстановки: чтобы писать {message} в фигурных скобках, нужно явно передать style='{'. Если задать свой datefmt, из времени исчезнут миллисекунды — их придётся вернуть отдельным %(msecs)d.",
        "en": "A formatter belongs to a handler via handler.setFormatter(); Logger has no such method at all, which is the usual reason a custom format seems to be ignored. Placeholders default to %-style, so writing {message} in braces does nothing unless you pass style='{'. Supplying your own datefmt drops the milliseconds from the timestamp — add %(msecs)d yourself if you still want them."
      },
      "syntax": "fmt = logging.Formatter(fmt=None, datefmt=None, style='%')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/logging.html#logging.Formatter",
      "version": "",
      "section": "Модуль logging",
      "subcat": "форматирование",
      "color_group": "module",
      "aliases": [
        "формат строки лога",
        "добавить время и уровень в лог",
        "настроить вид сообщений лога"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import logging",
        "handler = logging.StreamHandler()",
        "formatter = logging.Formatter(",
        "fmt='%(asctime)s [%(levelname)s] %(name)s: %(message)s',",
        "datefmt='%Y-%m-%d %H:%M:%S'",
        ")",
        "handler.setFormatter(formatter)",
        "logging.getLogger().addHandler(handler)"
      ],
      "related": [
        "logging-Handler",
        "logging-basicConfig",
        "logging-FileHandler"
      ],
      "related_errors": []
    },
    {
      "id": "logging-Handler",
      "title": "logging.Handler",
      "kind": "term",
      "summary": {
        "ru": "Базовый класс для всех обработчиков логов. Определяет, куда и как отправляются записи: в консоль, файл, сеть. Не используется напрямую.",
        "en": "The base class of every log handler. It decides where the records go and how: to the console, a file, the network. Not used directly."
      },
      "body": {
        "ru": "Уровень проверяется дважды — сначала логгером, потом обработчиком, поэтому setLevel(DEBUG) на обработчике ничего не даст, пока сам логгер стоит на WARNING. Один логгер может нести несколько обработчиков с разными уровнями и форматтерами: типовая связка — подробности в файл, только ошибки в консоль. Свой обработчик пишут, наследуя Handler и реализуя emit(); повторный addHandler внутри функции, которую вызывают не один раз, — обычная причина задвоенных строк.",
        "en": "The level is checked twice, first by the logger and then by the handler, so setLevel(DEBUG) on a handler changes nothing while the logger itself sits at WARNING. One logger can carry several handlers with different levels and formatters — the usual pairing is full detail to a file, errors only to the console. To write your own, subclass Handler and implement emit(); calling addHandler inside a function that runs more than once is the classic source of duplicated lines."
      },
      "syntax": "handler.setLevel(level)\nhandler.setFormatter(formatter)\nlogger.addHandler(handler)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/logging.html#logging.Handler",
      "version": "",
      "section": "Модуль logging",
      "subcat": "обработчики",
      "color_group": "module",
      "aliases": [
        "обработчик логов",
        "куда отправляются логи"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import logging",
        "# Handler — абстрактный базовый класс",
        "# Используй конкретные подклассы:",
        "sh = logging.StreamHandler()   # консоль",
        "fh = logging.FileHandler('app.log')  # файл"
      ],
      "related": [
        "logging-StreamHandler",
        "logging-FileHandler",
        "logging-Formatter"
      ],
      "related_errors": []
    },
    {
      "id": "logging-StreamHandler",
      "title": "logging.StreamHandler",
      "kind": "term",
      "summary": {
        "ru": "Обработчик, выводящий логи в поток: по умолчанию в sys.stderr. Чаще всего используется для вывода в консоль.",
        "en": "A handler that writes log records to a stream — sys.stderr by default. Most often used for console output."
      },
      "body": {
        "ru": "Сам по себе обработчик ничего не выводит — его нужно прицепить к логгеру через logger.addHandler(). Частая накладка: корневой логгер уже настроен basicConfig(), и добавленный вручную StreamHandler печатает каждое сообщение дважды — один раз своим обработчиком, второй раз через propagate у корневого (лечится logger.propagate = False или отказом от лишнего обработчика). Уровень обработчика — второй фильтр после уровня логгера: запись, отсечённая на logger.setLevel(), до handler.setLevel() просто не дойдёт.",
        "en": "A handler on its own prints nothing until you attach it with logger.addHandler(). A classic slip: basicConfig() has already put a StreamHandler on the root logger, so your own handler makes every message appear twice — once from your handler and once after propagation to root (set logger.propagate = False, or drop the extra handler). The handler level is only a second filter: a record rejected by logger.setLevel() never reaches handler.setLevel() at all."
      },
      "syntax": "handler = logging.StreamHandler(stream=None)  # None → sys.stderr",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/logging.handlers.html#logging.StreamHandler",
      "version": "",
      "section": "Модуль logging",
      "subcat": "обработчики",
      "color_group": "module",
      "aliases": [
        "вывод логов в консоль",
        "логи на экран",
        "логи в поток ошибок"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import logging, sys",
        "logger = logging.getLogger('app')",
        "sh = logging.StreamHandler(sys.stdout)",
        "sh.setLevel(logging.INFO)",
        "sh.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))",
        "logger.addHandler(sh)",
        "logger.info('Вывод в stdout')"
      ],
      "related": [
        "logging-FileHandler",
        "logging-Handler",
        "sys.stdin-sys.stdout-sys.stderr"
      ],
      "related_errors": []
    },
    {
      "id": "logging-basicConfig",
      "title": "logging.basicConfig()",
      "kind": "function",
      "summary": {
        "ru": "Быстрая базовая настройка системы логирования: уровень, формат, файл или поток вывода. Вызывается один раз до первого log-вызова.",
        "en": "Quick basic setup of the logging system: the level, the format, the output file or stream. Call it once, before the first logging call."
      },
      "body": {
        "ru": "Настройка срабатывает, только пока у корневого логгера нет обработчиков: второй вызов — и даже первый, если раньше в программе случайно проскочил logging.warning(), — молча ничего не изменит, пока не передать force=True (доступен с 3.8). Уровень по умолчанию WARNING, поэтому debug- и info-сообщения исчезают, пока явно не понизишь level. В библиотеках basicConfig не зовут: настройку вывода оставляют приложению.",
        "en": "The call takes effect only while the root logger has no handlers yet: a second call — or even the first one, if a stray logging.warning() already auto-configured the root — is silently ignored unless you pass force=True (available since 3.8). The default level is WARNING, so debug and info messages vanish until you lower it explicitly. Libraries should never call it; leave output configuration to the application."
      },
      "syntax": "logging.basicConfig(level=, format=, filename=, filemode=, encoding=)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/logging.html#logging.basicConfig",
      "version": "",
      "section": "Модуль logging",
      "subcat": "настройка",
      "color_group": "module",
      "aliases": [
        "настроить логирование",
        "включить логи",
        "уровень логирования"
      ],
      "keywords": [
        "logging.basicConfig"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import logging",
        "logging.basicConfig(",
        "level=logging.DEBUG,",
        "format='%(asctime)s %(levelname)s %(message)s'",
        ")",
        "logging.debug('старт')    # DEBUG:root:старт",
        "logging.info('готово')    # INFO:root:готово"
      ],
      "related": [
        "logging-getLogger",
        "logging-Formatter",
        "logging-FileHandler"
      ],
      "related_errors": []
    },
    {
      "id": "logging-debug",
      "title": "logging.debug()",
      "kind": "function",
      "summary": {
        "ru": "Записывает сообщение с уровнем DEBUG — самым низким. Используй для подробной диагностики при разработке. В production обычно отключён.",
        "en": "Writes a message at the DEBUG level — the lowest one. Use it for detailed diagnostics during development. Usually switched off in production."
      },
      "body": {
        "ru": "Самая частая осечка — вызвать logging.debug() и не увидеть ровным счётом ничего: у корневого логгера порог по умолчанию WARNING, так что DEBUG отбрасывается, пока не задан basicConfig(level=logging.DEBUG) — и задать его нужно до первого лог-вызова, на уже настроенном логгере повторный basicConfig() молча ничего не меняет. Аргументы подставляются лениво, только если запись реально дойдёт до вывода, поэтому передавай их плейсхолдерами (%s, %r), а не склеивай f-строку: при выключенном DEBUG форматирование вообще не выполнится.",
        "en": "The usual stumble is calling logging.debug() and seeing nothing at all: the root logger's default threshold is WARNING, so DEBUG records are dropped until you call basicConfig(level=logging.DEBUG) — and it must come before the first logging call, since basicConfig() silently does nothing once the logger is configured. Arguments are interpolated lazily, only when the record is actually emitted, so pass them as %s/%r placeholders instead of building an f-string: with DEBUG off the formatting never runs."
      },
      "syntax": "logging.debug(msg, *args, **kwargs)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/logging.html#logging.debug",
      "version": "",
      "section": "Модуль logging",
      "subcat": "уровни",
      "color_group": "module",
      "aliases": [
        "отладочное сообщение в лог",
        "лог уровня отладки",
        "подробный лог при разработке"
      ],
      "keywords": [
        "logging.debug"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import logging",
        "logging.basicConfig(level=logging.DEBUG)",
        "logging.debug('Переменная x = %s', 42)",
        "logging.debug('Входные данные: %r', [1, 2, 3])"
      ],
      "related": [
        "logging-info",
        "logging-warning",
        "logging-basicConfig"
      ],
      "related_errors": []
    },
    {
      "id": "logging-error",
      "title": "logging.error()",
      "kind": "function",
      "summary": {
        "ru": "Записывает сообщение с уровнем ERROR. Используй для ошибок, из-за которых часть логики не выполнилась, но программа продолжает работу.",
        "en": "Writes a message at the ERROR level. Use it when part of the logic failed but the program keeps running."
      },
      "body": {
        "ru": "Внутри except почти всегда лучше logging.exception(...) вместо logging.error(e): уровень тот же ERROR, но в лог попадает полный traceback (то же даёт error(..., exc_info=True)) — по одному str(e) вроде «division by zero» потом невозможно понять, где именно упало. И помни, что запись в лог — не обработка ошибки: если продолжать нельзя, после логирования нужно пробросить исключение или вернуть управление, а не идти дальше как ни в чём не бывало.",
        "en": "Inside an except block prefer logging.exception(...) over logging.error(e): the level is the same ERROR, but the full traceback goes into the log (error(..., exc_info=True) does the same) — a bare str(e) like \"division by zero\" tells you nothing about where it happened. And logging is not handling: if you cannot continue, re-raise or return after the log call instead of carrying on as if nothing went wrong."
      },
      "syntax": "logging.error(msg, *args, exc_info=False, **kwargs)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/logging.html#logging.error",
      "version": "",
      "section": "Модуль logging",
      "subcat": "уровни",
      "color_group": "module",
      "aliases": [
        "записать ошибку в лог",
        "сообщение об ошибке в лог",
        "залогировать исключение"
      ],
      "keywords": [
        "logging.error"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import logging",
        "try:",
        "    result = 1 / 0",
        "except ZeroDivisionError:",
        "    logging.error('Деление на ноль', exc_info=True)  # + traceback"
      ],
      "related": [
        "logging-warning",
        "logging-info",
        "try-except"
      ],
      "related_errors": []
    },
    {
      "id": "logging-getLogger",
      "title": "logging.getLogger()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает именованный логгер. Логгеры образуют иерархию по точке в имени. Лучше создавать логгер на уровне модуля: getLogger(__name__).",
        "en": "Returns a named logger. Loggers form a hierarchy along the dots in their names. Best created at module level: getLogger(__name__)."
      },
      "body": {
        "ru": "На одно и то же имя всегда возвращается один и тот же объект, поэтому логгер можно «получать» в любом месте программы, не протаскивая его через параметры. Запись поднимается вверх по точечной иерархии имён, и обработчики предков тоже срабатывают — отсюда классическое задвоение строк, когда handler повесили и на модульный логгер, и на корневой. Собственный уровень логгера по умолчанию NOTSET: он молча берёт уровень ближайшего предка, у которого уровень задан.",
        "en": "The same name always hands back the same object, so you can fetch a logger anywhere instead of threading it through arguments. A record travels up the dotted name hierarchy and the ancestors' handlers fire as well — the usual reason lines appear twice after attaching a handler to both a module logger and the root. A logger's own level defaults to NOTSET, meaning it quietly inherits the level of the nearest ancestor that has one."
      },
      "syntax": "logger = logging.getLogger(name)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/logging.html#logging.getLogger",
      "version": "",
      "section": "Модуль logging",
      "subcat": "настройка",
      "color_group": "module",
      "aliases": [
        "создать логгер",
        "именованный логгер",
        "логгер модуля"
      ],
      "keywords": [
        "logging.getLogger"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import logging",
        "logger = logging.getLogger(__name__)",
        "logger.setLevel(logging.DEBUG)",
        "logger.info('сообщение из модуля')",
        "# иерархия",
        "parent = logging.getLogger('app')",
        "child  = logging.getLogger('app.db')  # наследует настройки parent"
      ],
      "related": [
        "logging-basicConfig",
        "logging-Handler",
        "logging-info"
      ],
      "related_errors": []
    },
    {
      "id": "logging-info",
      "title": "logging.info()",
      "kind": "function",
      "summary": {
        "ru": "Записывает сообщение с уровнем INFO. Используй для штатных событий: запуск, успешное завершение, статус операции.",
        "en": "Writes a message at the INFO level. Use it for normal events: startup, successful completion, the status of an operation."
      },
      "body": {
        "ru": "Модульная функция logging.info() пишет в корневой логгер и при первом вызове сама его настраивает — для скрипта это нормально, а внутри модуля или библиотеки так делать не стоит: заводи свой logger = logging.getLogger(__name__), иначе ты навязываешь конфигурацию чужой программе. Граница с DEBUG — по адресату: INFO читает тот, кто программу эксплуатирует (что произошло), DEBUG — тот, кто её чинит (почему); если сообщение интересно только тебе во время отладки, ему место в debug.",
        "en": "The module-level logging.info() writes to the root logger and configures it on the first call — fine in a script, but wrong inside a module or library: create your own logger = logging.getLogger(__name__), otherwise you impose configuration on someone else's application. The line between INFO and DEBUG is about the audience: INFO is read by whoever operates the program (what happened), DEBUG by whoever repairs it (why); a message that only helps you while debugging belongs in debug."
      },
      "syntax": "logging.info(msg, *args, **kwargs)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/logging.html#logging.info",
      "version": "",
      "section": "Модуль logging",
      "subcat": "уровни",
      "color_group": "module",
      "aliases": [
        "информационное сообщение в лог",
        "записать в лог штатное событие",
        "уровень лога инфо"
      ],
      "keywords": [
        "logging.info"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import logging",
        "logging.basicConfig(level=logging.INFO)",
        "logging.info('Сервер запущен на порту %d', 8080)",
        "logging.info('Загружено %d записей', 1024)"
      ],
      "related": [
        "logging-debug",
        "logging-warning",
        "logging-basicConfig"
      ],
      "related_errors": []
    },
    {
      "id": "logging-warning",
      "title": "logging.warning()",
      "kind": "function",
      "summary": {
        "ru": "Записывает сообщение с уровнем WARNING. Используй для потенциальных проблем, которые не ломают работу прямо сейчас.",
        "en": "Writes a message at the WARNING level. Use it for potential problems that are not breaking anything right now."
      },
      "body": {
        "ru": "WARNING — это порог корневого логгера по умолчанию, поэтому warning() и всё, что выше, видно даже без basicConfig(), а info() и debug() молчат; отсюда частое впечатление новичка, будто «работает только warning». Не путай с warnings.warn() из модуля warnings: та адресована программисту (устаревший API, подозрительное использование) и по умолчанию показывается один раз на место вызова, а logging.warning() — обычное событие времени выполнения в общем логе, оно пишется столько раз, сколько случилось.",
        "en": "WARNING is the root logger's default threshold, so warning() and anything above it shows up even without basicConfig(), while info() and debug() stay silent — hence the beginner's impression that \"only warning works\". Don't confuse it with warnings.warn() from the warnings module: that one addresses the programmer (deprecated API, suspicious usage) and by default fires once per call site, whereas logging.warning() records an ordinary runtime event as many times as it occurs."
      },
      "syntax": "logging.warning(msg, *args, **kwargs)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/logging.html#logging.warning",
      "version": "",
      "section": "Модуль logging",
      "subcat": "уровни",
      "color_group": "module",
      "aliases": [
        "предупреждение в лог",
        "записать в лог возможную проблему",
        "уровень лога предупреждение"
      ],
      "keywords": [
        "logging.warning"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import logging",
        "logging.warning('Диск заполнен на %d%%', 90)",
        "logging.warning('Устаревший API вызван: %s', 'old_func')",
        "logging.warning('Не удалось разобрать %r, беру 0', 'abc')  # → в stderr: WARNING:root:Не удалось разобрать 'abc', беру 0",
        "logging.info('Это сообщение не появится')  # → ничего: уровень root по умолчанию WARNING",
        "print(logging.WARNING)  # → 30"
      ],
      "related": [
        "logging-error",
        "logging-info"
      ],
      "related_errors": []
    },
    {
      "id": "math.acos",
      "title": "math.acos",
      "kind": "term",
      "summary": {
        "ru": "Арккосинус угла. Принимает значение из [-1, 1], возвращает угол в радианах из [0, π].",
        "en": "The arc cosine of a value. It takes a value in [-1, 1] and returns an angle in radians in [0, π]."
      },
      "body": {
        "ru": "Самая частая авария — ValueError: math domain error, когда после вычислений с плавающей точкой аргумент вылезает за [-1, 1] на 1e-16 (косинусная близость, нормированное скалярное произведение). Лечится зажимом перед вызовом: max(-1.0, min(1.0, x)). Результат — радианы, в градусы его переводит math.degrees().",
        "en": "The usual crash is ValueError: math domain error, when floating-point rounding pushes the argument just past 1.0 or -1.0 by about 1e-16 — typical for cosine similarity or a normalised dot product. Clamp it before the call with max(-1.0, min(1.0, x)). The result is in radians; math.degrees() converts it."
      },
      "syntax": "math.acos(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.acos",
      "version": "",
      "section": "Модуль math",
      "subcat": "тригонометрия",
      "color_group": "module",
      "aliases": [
        "арккосинус",
        "обратный косинус",
        "угол по косинусу"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.acos(1))    # → 0.0",
        "print(math.acos(0))    # → 1.5707... (π/2)",
        "print(math.acos(-1))   # → 3.1415... (π)",
        "print(math.acos(0.5))  # → 1.0471... (π/3)",
        "print(math.degrees(math.acos(0.5)))  # → 60.0",
        "print(math.degrees(math.acos(0)))  # → 90.0"
      ],
      "related": [
        "math.cos",
        "math.asin",
        "math.atan"
      ],
      "related_errors": []
    },
    {
      "id": "math.asin",
      "title": "math.asin",
      "kind": "term",
      "summary": {
        "ru": "Арксинус угла. Принимает значение из [-1, 1], возвращает угол в радианах из [-π/2, π/2].",
        "en": "The arc sine of a value. It takes a value in [-1, 1] and returns an angle in radians in [-π/2, π/2]."
      },
      "body": {
        "ru": "Та же ловушка с областью определения: аргумент вне [-1, 1] — пусть даже на 1e-16 из-за погрешности — даёт ValueError: math domain error, поэтому вычисленное значение стоит зажимать. И не восстанавливай по asin угол точки: результат заперт в [-π/2, π/2] и теряет четверть — для этого есть math.atan2(y, x), которая учитывает знаки обоих аргументов и корректно работает при x равном нулю.",
        "en": "Same domain trap: an argument outside [-1, 1], even by 1e-16 of rounding error, raises ValueError: math domain error, so clamp computed values before passing them in. Do not use asin to recover the angle of a point either — its result is confined to [-π/2, π/2] and loses the quadrant; math.atan2(y, x) uses the signs of both arguments and handles x equal to zero."
      },
      "syntax": "math.asin(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.asin",
      "version": "",
      "section": "Модуль math",
      "subcat": "тригонометрия",
      "color_group": "module",
      "aliases": [
        "арксинус",
        "обратный синус",
        "угол по синусу"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.asin(0))    # → 0.0",
        "print(math.asin(1))    # → 1.5707... (π/2)",
        "print(math.asin(-1))   # → -1.5707...",
        "print(math.asin(0.5))  # → 0.5235... (π/6)",
        "print(math.degrees(math.asin(0.5)))  # → 30.0",
        "print(round(math.asin(math.sqrt(2)/2),4))  # → 0.7854 (π/4)"
      ],
      "related": [
        "math.sin",
        "math.acos",
        "math.atan"
      ],
      "related_errors": []
    },
    {
      "id": "math.atan",
      "title": "math.atan",
      "kind": "term",
      "summary": {
        "ru": "Арктангенс угла. Принимает любое вещественное число, возвращает угол в радианах из (-π/2, π/2).",
        "en": "The arc tangent of a value. It takes any real number and returns an angle in radians in (-π/2, π/2)."
      },
      "body": {
        "ru": "atan видит только отношение y/x и поэтому теряет квадрант: у точек (1, 1) и (-1, -1) отношение одинаковое, а углы отличаются на π. Для угла вектора берите math.atan2(y, x) — она различает квадранты и не падает при x == 0, тогда как math.atan(y/x) в этом месте даст ZeroDivisionError.",
        "en": "atan only sees the ratio y/x, so the quadrant is lost: the points (1, 1) and (-1, -1) give the same ratio but angles that differ by π. To get the direction of a vector use math.atan2(y, x) instead — it keeps the quadrant and survives x == 0, where math.atan(y/x) would raise ZeroDivisionError."
      },
      "syntax": "math.atan(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.atan",
      "version": "",
      "section": "Модуль math",
      "subcat": "тригонометрия",
      "color_group": "module",
      "aliases": [
        "арктангенс",
        "обратный тангенс",
        "угол по тангенсу"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.atan(0))    # → 0.0",
        "print(math.atan(1))    # → 0.7853... (π/4)",
        "print(math.atan(-1))   # → -0.7853...",
        "print(math.atan(math.inf))  # → 1.5707... (π/2)",
        "print(math.degrees(math.atan(1)))  # → 45.0",
        "print(math.atan(0.5))  # → 0.4636..."
      ],
      "related": [
        "math.atan2",
        "math.tan",
        "math.acos"
      ],
      "related_errors": []
    },
    {
      "id": "math.atan2",
      "title": "math.atan2",
      "kind": "term",
      "summary": {
        "ru": "Арктангенс y/x с учётом квадранта — правильно обрабатывает все знаки. Результат в [-π, π].",
        "en": "The arc tangent of y/x, taking the quadrant into account — it handles every combination of signs correctly. The result is in [-π, π]."
      },
      "body": {
        "ru": "Порядок аргументов обратный привычной записи дроби: сначала y, потом x. Перепутать легко, а ошибка тихая — вместо исключения вы получите зеркально отражённый угол. Для точек ниже оси x результат отрицательный; если нужен диапазон [0, 2π), приведите сами: math.atan2(y, x) % (2 * math.pi).",
        "en": "The argument order is the reverse of how the fraction reads: y comes first, then x. Swapping them raises nothing — you just get a mirrored angle, which is a quiet and easily missed bug. Points below the x axis come back negative; if you need [0, 2π), normalise yourself with math.atan2(y, x) % (2 * math.pi)."
      },
      "syntax": "math.atan2(y, x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.atan2",
      "version": "",
      "section": "Модуль math",
      "subcat": "тригонометрия",
      "color_group": "module",
      "aliases": [
        "угол по координатам",
        "арктангенс с учётом квадранта",
        "полярный угол"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.atan2(1, 1))    # → 0.7853... (π/4, I квадрант)",
        "print(math.atan2(1, -1))   # → 2.3561... (3π/4, II квадрант)",
        "print(math.atan2(-1, -1))  # → -2.3561... (III)",
        "print(math.atan2(0, 1))    # → 0.0",
        "print(math.degrees(math.atan2(1, 0)))  # → 90.0",
        "print(math.degrees(math.atan2(-1, 0)))  # → -90.0"
      ],
      "related": [
        "math.atan",
        "math.hypot",
        "math.degrees"
      ],
      "related_errors": []
    },
    {
      "id": "math.ceil",
      "title": "math.ceil",
      "kind": "term",
      "summary": {
        "ru": "Округление вверх до ближайшего целого. math.ceil(2.1) → 3, math.ceil(-2.1) → -2.",
        "en": "Rounds up to the nearest integer. math.ceil(2.1) → 3, math.ceil(-2.1) → -2."
      },
      "body": {
        "ru": "Возвращает int, а не float, и делегирует работу методу __ceil__ объекта — у Decimal и Fraction он есть, поэтому они округляются точно, без потери разрядов. А вот целочисленное деление вверх лучше писать как -(-a // b): math.ceil(a / b) сначала переводит частное в float, и на больших числах округление даст неверный ответ.",
        "en": "It returns an int, not a float, and defers to the object's __ceil__ method — Decimal and Fraction implement it, so they round exactly with no loss of digits. For ceiling division of integers, though, write -(-a // b): math.ceil(a / b) builds a float quotient first, and on large values that rounding gives the wrong answer."
      },
      "syntax": "math.ceil(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.ceil",
      "version": "",
      "section": "Модуль math",
      "subcat": "округление",
      "color_group": "module",
      "aliases": [
        "округление вверх",
        "округлить вверх до целого",
        "потолок числа"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.ceil(3.1))   # → 4",
        "print(math.ceil(3.9))   # → 4",
        "print(math.ceil(-3.1))  # → -3",
        "print(math.ceil(-3.9))  # → -3",
        "print(math.ceil(5.0))   # → 5",
        "print(math.ceil(0.001)) # → 1"
      ],
      "related": [
        "math.floor",
        "math.trunc",
        "round"
      ],
      "related_errors": []
    },
    {
      "id": "math.comb",
      "title": "math.comb",
      "kind": "term",
      "summary": {
        "ru": "Биномиальный коэффициент C(n, k): число сочетаний из n элементов по k без повторений и без учёта порядка.",
        "en": "The binomial coefficient C(n, k): the number of ways to choose k items out of n, without repetition and without regard to order."
      },
      "body": {
        "ru": "Считает точно и без промежуточных гигантов — в отличие от формулы через факториалы, где сначала строятся числа в тысячи цифр, а потом сокращаются. Если k больше n, возвращается 0, а не ошибка: перепутанный порядок аргументов не падает, а тихо отдаёт ноль; ValueError вылетит только на отрицательных значениях, TypeError — на нецелых. Часто выручает симметрия: число сочетаний из n по k равно числу сочетаний из n по n минус k.",
        "en": "It computes the result exactly and without blowing up intermediate values, unlike the textbook factorial formula that first builds numbers thousands of digits long and then cancels them. When k exceeds n the answer is 0 rather than an error, so swapping the arguments by mistake gives a silent zero instead of a crash; only negative values raise ValueError and non-integers raise TypeError. The symmetry C(n, k) = C(n, n - k) is often worth exploiting."
      },
      "syntax": "math.comb(n, k)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.comb",
      "version": "3.8",
      "section": "Модуль math",
      "subcat": "комбинаторика",
      "color_group": "module",
      "aliases": [
        "число сочетаний",
        "биномиальный коэффициент",
        "сколькими способами выбрать"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.comb(5, 2))   # → 10",
        "print(math.comb(10, 3))  # → 120",
        "print(math.comb(5, 0))   # → 1",
        "print(math.comb(5, 5))   # → 1",
        "print(math.comb(6, 3))   # → 20",
        "print(math.comb(52, 5))  # → 2598960 (покер)"
      ],
      "related": [
        "math.perm",
        "math.factorial",
        "itertools.combinations"
      ],
      "related_errors": []
    },
    {
      "id": "math.cos",
      "title": "math.cos",
      "kind": "term",
      "summary": {
        "ru": "Возвращает косинус угла в радианах. Результат всегда в диапазоне [-1, 1]. Для градусов: math.cos(math.radians(угол)).",
        "en": "Returns the cosine of an angle given in radians. The result is always in [-1, 1]. For degrees: math.cos(math.radians(angle))."
      },
      "body": {
        "ru": "math.pi — приближение в пределах точности float, поэтому math.cos(math.pi / 2) возвращает 6.1e-17, а не ровно ноль; сравнивать результат через == почти всегда ошибка, нужен math.isclose() с abs_tol. Единицы измерения функция не проверяет: math.cos(60) молча посчитает косинус 60 радиан и вернёт правдоподобное, но неверное число.",
        "en": "math.pi is only a float approximation, so math.cos(math.pi / 2) comes out as 6.1e-17 rather than an exact zero — comparing the result with == is nearly always wrong, use math.isclose() with an abs_tol. And the function does not check units: math.cos(60) quietly returns the cosine of 60 radians, a plausible-looking but wrong answer."
      },
      "syntax": "math.cos(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.cos",
      "version": "",
      "section": "Модуль math",
      "subcat": "тригонометрия",
      "color_group": "module",
      "aliases": [
        "косинус",
        "косинус угла",
        "косинус в градусах"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.cos(0))            # → 1.0",
        "print(math.cos(math.pi))      # → -1.0",
        "print(math.cos(math.pi/2))    # → 6.12e-17 ≈ 0",
        "print(round(math.cos(math.pi/3), 2))  # → 0.5",
        "print(math.cos(math.pi/4))    # → 0.7071...",
        "print(math.cos(-math.pi))     # → -1.0"
      ],
      "related": [
        "math.sin",
        "math.radians",
        "math.acos"
      ],
      "related_errors": []
    },
    {
      "id": "math.degrees",
      "title": "math.degrees",
      "kind": "term",
      "summary": {
        "ru": "Конвертирует угол из радиан в градусы. math.degrees(math.pi) → 180.0.",
        "en": "Converts an angle from radians to degrees. math.degrees(math.pi) → 180.0."
      },
      "body": {
        "ru": "Функция ничего не нормализует, а просто умножает на 180/π: 3π превратится в 540.0, а отрицательный угол от math.atan2 останется отрицательным, в диапазоне [-180, 180]. Если нужен привычный 0..360, приводите сами через math.degrees(a) % 360.",
        "en": "There is no normalisation involved — it is a plain multiplication by 180/π, so 3π becomes 540.0 and a negative angle from math.atan2 stays negative, somewhere in [-180, 180]. When you want the familiar 0..360 range, wrap it yourself: math.degrees(a) % 360."
      },
      "syntax": "math.degrees(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.degrees",
      "version": "",
      "section": "Модуль math",
      "subcat": "тригонометрия",
      "color_group": "module",
      "aliases": [
        "перевод радиан в градусы",
        "получить угол в градусах"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.degrees(math.pi))      # → 180.0",
        "print(math.degrees(math.pi/2))    # → 90.0",
        "print(math.degrees(2*math.pi))    # → 360.0",
        "print(math.degrees(0))            # → 0.0",
        "print(math.degrees(math.pi/6))    # → 30.0",
        "print(math.degrees(math.pi/4))    # → 45.0"
      ],
      "related": [
        "math.radians",
        "math.pi"
      ],
      "related_errors": []
    },
    {
      "id": "math.e",
      "title": "math.e",
      "kind": "term",
      "summary": {
        "ru": "Число Эйлера e ≈ 2.718281828459045 — основание натурального логарифма.",
        "en": "Euler's number e ≈ 2.718281828459045 — the base of the natural logarithm."
      },
      "body": {
        "ru": "Это обычный double, ближайший к настоящему e, а не символьная константа: любые выражения с ним округляются, поэтому math.e ** x в последних битах разойдётся с math.exp(x) — для экспоненты берите math.exp(). И помните, что math.log(x) уже натуральный по умолчанию: писать math.log(x, math.e) не нужно, второй аргумент только добавит лишнее деление и погрешность.",
        "en": "This is an ordinary double — the nearest representable value to the true e, not a symbolic constant — so math.e ** x drifts from math.exp(x) in the last bits; use math.exp() when you want the exponential. Also note that math.log(x) is already the natural logarithm: passing math.e as a second argument adds a needless division and its rounding error."
      },
      "syntax": "math.e",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.e",
      "version": "",
      "section": "Модуль math",
      "subcat": "константы",
      "color_group": "module",
      "aliases": [
        "число эйлера",
        "основание натурального логарифма"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.e)  # → 2.718281828459045",
        "print(math.e ** 2)  # → 7.389...",
        "print(math.log(math.e))  # → 1.0",
        "print(math.exp(1))  # → 2.718...",
        "print(math.e ** math.pi)  # → 23.140...",
        "print(round(math.e, 5))  # → 2.71828"
      ],
      "related": [
        "math.exp",
        "math.log",
        "math.pi"
      ],
      "related_errors": []
    },
    {
      "id": "math.exp",
      "title": "math.exp",
      "kind": "term",
      "summary": {
        "ru": "Возвращает e в степени x (экспоненту). Эквивалент math.e ** x, но точнее и быстрее для больших x.",
        "en": "Returns e raised to the power of x (the exponential). Equivalent to math.e ** x, but more accurate and faster for large x."
      },
      "body": {
        "ru": "На больших аргументах экспонента не уходит в inf, а поднимает OverflowError: math range error — примерно с x около 710, так что результат стоит либо ограничивать заранее, либо ловить. В обратную сторону молчаливо получается 0.0. Если считаете exp(x) - 1 при крошечном x, берите math.expm1(x): обычная разность там теряет почти все значащие цифры.",
        "en": "For large arguments this does not quietly return inf — it raises OverflowError: math range error, roughly above x = 710, so either clamp the input or catch it. Underflow in the other direction is silent and just gives 0.0. When you need exp(x) - 1 for tiny x, use math.expm1(x); the plain subtraction throws away almost all significant digits there."
      },
      "syntax": "math.exp(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.exp",
      "version": "",
      "section": "Модуль math",
      "subcat": "константы",
      "color_group": "module",
      "aliases": [
        "экспонента",
        "показательная функция"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.exp(0))   # → 1.0",
        "print(math.exp(1))   # → 2.718281828...",
        "print(math.exp(2))   # → 7.389...",
        "print(math.exp(-1))  # → 0.3678...",
        "print(math.exp(10))  # → 22026.465...",
        "print(math.exp(math.log(5)))  # → 5.0"
      ],
      "related": [
        "math.log",
        "math.e",
        "math.pow"
      ],
      "related_errors": []
    },
    {
      "id": "math.fabs",
      "title": "math.fabs",
      "kind": "term",
      "summary": {
        "ru": "Возвращает абсолютное значение числа как float. В отличие от abs(), всегда возвращает float, а не int.",
        "en": "Returns the absolute value of a number as a float. Unlike abs(), it always returns a float, never an int."
      },
      "body": {
        "ru": "В обычном коде почти всегда нужен abs(): он сохраняет тип (int, Decimal, Fraction) и умеет комплексные числа, а fabs() приводит всё к float и на комплексном падает с TypeError. Отсюда и потеря точности: у большого целого fabs() оставит только 53 бита мантиссы, а на очень большом — вообще выбросит OverflowError. Брать fabs() имеет смысл там, где результат всё равно нужен как float, например при сравнении с допуском.",
        "en": "In everyday code abs() is the right call: it keeps the type (int, Decimal, Fraction) and handles complex numbers, whereas fabs() coerces everything to float and raises TypeError on a complex argument. That coercion costs precision — a large int keeps only 53 bits of mantissa, and a huge one raises OverflowError outright. Reach for fabs() only when a float is what you want anyway, such as comparing against a tolerance."
      },
      "syntax": "math.fabs(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.fabs",
      "version": "",
      "section": "Модуль math",
      "subcat": "абсолютное",
      "color_group": "module",
      "aliases": [
        "модуль числа",
        "абсолютное значение",
        "убрать минус у числа"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.fabs(-5.0))  # → 5.0",
        "print(math.fabs(3.14))  # → 3.14",
        "print(math.fabs(-0.0))  # → 0.0",
        "print(type(math.fabs(-3)))  # → float (в отличие от abs)",
        "print(math.fabs(-1e100))  # → 1e+100",
        "print(math.fabs(0))  # → 0.0"
      ],
      "related": [
        "abs",
        "math.copysign"
      ],
      "related_errors": []
    },
    {
      "id": "math.factorial",
      "title": "math.factorial",
      "kind": "term",
      "summary": {
        "ru": "Возвращает факториал неотрицательного целого числа. factorial(5) → 120. Для отрицательных чисел — ValueError.",
        "en": "Returns the factorial of a non-negative integer. factorial(5) → 120. A negative number raises ValueError."
      },
      "body": {
        "ru": "Float с целым значением раньше принимался, но в Python 3.12 эту поблажку убрали — теперь дробный тип даёт TypeError, поэтому результат деления приводите к int явно. Значение растёт быстрее любой экспоненты, и считать через факториал сочетания или размещения не стоит: math.comb и math.perm дают тот же ответ на порядки быстрее и без чисел в тысячи цифр.",
        "en": "Floats with integral values used to be accepted, but that was removed in Python 3.12, so a float argument now raises TypeError and you must convert division results with int() yourself. The value grows faster than any exponential, so never build combinations or permutations out of factorials by hand: math.comb and math.perm give the same answer orders of magnitude faster and without thousand-digit intermediates."
      },
      "syntax": "math.factorial(n)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.factorial",
      "version": "",
      "section": "Модуль math",
      "subcat": "комбинаторика",
      "color_group": "module",
      "aliases": [
        "факториал",
        "произведение чисел от 1 до n"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.factorial(0))  # → 1",
        "print(math.factorial(5))  # → 120",
        "print(math.factorial(10)) # → 3628800",
        "print(math.factorial(20)) # → 2432902008176640000",
        "print(math.factorial(1))  # → 1",
        "print(math.factorial(6))  # → 720"
      ],
      "related": [
        "math.comb",
        "math.perm",
        "math.prod"
      ],
      "related_errors": []
    },
    {
      "id": "math.floor",
      "title": "math.floor",
      "kind": "term",
      "summary": {
        "ru": "Округление вниз до ближайшего целого. math.floor(2.9) → 2, math.floor(-2.9) → -3.",
        "en": "Rounds down to the nearest integer. math.floor(2.9) → 2, math.floor(-2.9) → -3."
      },
      "body": {
        "ru": "Возвращает именно int, а не float, поэтому результат сразу годится в качестве индекса. На отрицательных числах floor расходится с int(): int(-3.1) даёт -3, просто отбрасывая дробь, а floor уходит вниз, к -4 — по смыслу floor это близнец целочисленного деления //, а не усечения. Для Fraction и Decimal вызывается их собственный __floor__, так что промежуточного перевода во float с потерей точности не происходит.",
        "en": "It returns an int, not a float, so the result is immediately usable as an index. On negatives floor parts ways with int(): int(-3.1) is -3 because it merely drops the fraction, while floor goes down to -4 — floor is the twin of the // operator, not of truncation. For Fraction and Decimal it calls their own __floor__, so there is no lossy detour through float."
      },
      "syntax": "math.floor(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.floor",
      "version": "",
      "section": "Модуль math",
      "subcat": "округление",
      "color_group": "module",
      "aliases": [
        "округление вниз",
        "округлить вниз до целого",
        "нижнее целое"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.floor(3.9))   # → 3",
        "print(math.floor(3.1))   # → 3",
        "print(math.floor(-3.1))  # → -4",
        "print(math.floor(-3.9))  # → -4",
        "print(math.floor(5.0))   # → 5",
        "print(math.floor(0.999)) # → 0"
      ],
      "related": [
        "math.ceil",
        "math.trunc",
        "round"
      ],
      "related_errors": []
    },
    {
      "id": "math.fsum",
      "title": "math.fsum",
      "kind": "term",
      "summary": {
        "ru": "Точная сумма чисел с плавающей точкой (без ошибок округления).",
        "en": "An accurate sum of floating-point numbers (free of rounding error)."
      },
      "body": {
        "ru": "Обычный sum() накапливает ошибку округления на каждом шаге, а fsum() держит набор частичных сумм и округляет один раз в самом конце — разница вылезает на длинных последовательностях и на слагаемых очень разного порядка. Платить приходится скоростью: fsum() заметно медленнее и всегда возвращает float, даже если на входе целые. Для денег это не лекарство — там нужен decimal.Decimal, а не fsum().",
        "en": "Plain sum() lets rounding error pile up at every step, while fsum() keeps a set of partial sums and rounds only once at the very end — the gap shows up on long sequences and on terms of wildly different magnitude. The price is speed: fsum() is noticeably slower and always hands back a float, even for integer input. It is not the cure for money calculations either — use decimal.Decimal for those."
      },
      "syntax": "math.fsum(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.fsum",
      "version": "",
      "section": "Модуль math",
      "subcat": "агрегация",
      "color_group": "module",
      "aliases": [
        "точная сумма дробных чисел",
        "сумма без ошибок округления"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(sum([0.1]*10))         # → 0.9999999999999999",
        "print(math.fsum([0.1]*10))   # → 1.0",
        "print(math.fsum([1e100, 1, -1e100]))  # → 1.0",
        "print(sum([1e100, 1, -1e100]))        # → 0.0 (неточно!)",
        "print(math.fsum([0.1, 0.2, 0.3]))    # → 0.6",
        "print(sum([0.1, 0.2, 0.3]))          # → 0.6000000000000001"
      ],
      "related": [
        "sum",
        "math.prod",
        "math.isclose"
      ],
      "related_errors": []
    },
    {
      "id": "math.gcd",
      "title": "math.gcd",
      "kind": "term",
      "summary": {
        "ru": "Возвращает наибольший общий делитель двух или более целых чисел. math.gcd(48, 36) → 12. Python 3.9+: несколько аргументов.",
        "en": "Returns the greatest common divisor of two or more integers. math.gcd(48, 36) → 12. Python 3.9+: several arguments at once."
      },
      "body": {
        "ru": "Результат всегда неотрицательный, знаки аргументов роли не играют, НОД нуля и x равен модулю x, а вызов вообще без аргументов даёт 0. Именно поэтому ноль удобен как стартовое значение при свёртке списка через functools.reduce — спецслучаев не нужно. До Python 3.9 функция принимала ровно два аргумента, так что многоаргументный вызов на старом интерпретаторе упадёт.",
        "en": "The result is always non-negative and the signs of the arguments are irrelevant; the gcd of 0 and x is abs(x), and calling it with no arguments at all yields 0. That makes 0 a natural starting value when folding a list with functools.reduce, with no special cases needed. Before Python 3.9 the function took exactly two arguments, so a multi-argument call breaks on older interpreters."
      },
      "syntax": "math.gcd(*integers)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.gcd",
      "version": "3.5",
      "section": "Модуль math",
      "subcat": "комбинаторика",
      "color_group": "module",
      "aliases": [
        "наибольший общий делитель",
        "нод",
        "сократить дробь"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.gcd(12, 8))    # → 4",
        "print(math.gcd(100, 75))  # → 25",
        "print(math.gcd(0, 5))     # → 5",
        "print(math.gcd(7, 13))    # → 1 (взаимно просты)",
        "print(math.gcd(12, 18, 24))  # → 6 (Python 3.9+)",
        "print(math.gcd(48, 64))   # → 16"
      ],
      "related": [
        "math.lcm",
        "fractions.fraction"
      ],
      "related_errors": []
    },
    {
      "id": "math.hypot",
      "title": "math.hypot",
      "kind": "term",
      "summary": {
        "ru": "Евклидово расстояние от начала координат. Python 3.8+: любое число аргументов.",
        "en": "The Euclidean distance from the origin. Python 3.8+: any number of arguments."
      },
      "body": {
        "ru": "hypot() считает не «в лоб»: промежуточные значения масштабируются, поэтому там, где sqrt(x*x + y*y) уже переполнится или потеряет значащие цифры, hypot() вернёт нормальный ответ. Для расстояния между двумя точками не вычитайте координаты руками — есть math.dist(p, q), которая делает это сама. Приём произвольного числа аргументов появился только в 3.8: в 3.7 и старше hypot() принимает ровно два.",
        "en": "hypot() does not compute the formula literally — it rescales the intermediate values, so it still gives a sane answer where sqrt(x*x + y*y) would overflow or lose significant digits. To measure the distance between two points, do not subtract the coordinates by hand: math.dist(p, q) does it for you. Accepting an arbitrary number of arguments arrived only in 3.8; on 3.7 and older hypot() takes exactly two."
      },
      "syntax": "math.hypot(*coordinates)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.hypot",
      "version": "",
      "section": "Модуль math",
      "subcat": "геометрия",
      "color_group": "module",
      "aliases": [
        "гипотенуза",
        "теорема пифагора",
        "длина вектора"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.hypot(3, 4))      # → 5.0",
        "print(math.hypot(5, 12))     # → 13.0",
        "print(math.hypot(1, 1))      # → 1.41421...",
        "print(math.hypot(0, 5))      # → 5.0",
        "print(math.hypot(1, 1, 1))   # → 1.732... (3D)",
        "print(math.hypot(3, 4, 0))   # → 5.0"
      ],
      "related": [
        "math.dist",
        "math.sqrt",
        "math.atan2"
      ],
      "related_errors": []
    },
    {
      "id": "math.inf",
      "title": "math.inf",
      "kind": "term",
      "summary": {
        "ru": "Положительная бесконечность (float). Используется как начальное значение при поиске минимума или в сравнениях.",
        "en": "Positive infinity (a float). Used as the starting value when searching for a minimum, and in comparisons."
      },
      "body": {
        "ru": "Это ровно то же значение, что float('inf'), просто читаемее. Арифметика с ним не падает, но легко возвращает nan: math.inf - math.inf и math.inf / math.inf дают именно nan, а не ошибку и не ноль. При этом деление на обычный ноль в Python бесконечности не даёт — там по-прежнему ZeroDivisionError, и int(math.inf) тоже не пройдёт, будет OverflowError.",
        "en": "It is exactly the same value as float('inf'), just easier to read. Arithmetic with it never raises, but it quietly yields nan: math.inf - math.inf and math.inf / math.inf both produce nan rather than an error or zero. Note that dividing by a plain zero in Python does not give infinity — that is still ZeroDivisionError — and int(math.inf) fails with OverflowError."
      },
      "syntax": "math.inf",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.inf",
      "version": "3.5",
      "section": "Модуль math",
      "subcat": "константы",
      "color_group": "module",
      "aliases": [
        "бесконечность",
        "начальное значение при поиске минимума"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.inf)  # → inf",
        "print(-math.inf)  # → -inf",
        "print(math.isinf(math.inf))  # → True",
        "print(math.inf > 10**308)  # → True",
        "print(math.inf + 1)  # → inf",
        "print(math.inf - math.inf)  # → nan"
      ],
      "related": [
        "math.isinf",
        "math.isfinite",
        "math.nan"
      ],
      "related_errors": []
    },
    {
      "id": "math.isclose",
      "title": "math.isclose",
      "kind": "term",
      "summary": {
        "ru": "Проверяет примерное равенство двух чисел с учётом погрешности.",
        "en": "Checks whether two numbers are approximately equal, within a tolerance."
      },
      "body": {
        "ru": "Допуск по умолчанию относительный: он считается в долях от большего по модулю из двух чисел, поэтому сравнение с нулём всегда проваливается — math.isclose(1e-12, 0) вернёт False при любом rel_tol. Если один из операндов может оказаться нулём или очень малой величиной, задавай abs_tol явно. Функция появилась в Python 3.5; NaN не близок ни к чему, включая самого себя, а для комплексных чисел есть cmath.isclose.",
        "en": "The default tolerance is relative — it is computed as a fraction of the larger operand — so comparing against zero never succeeds: math.isclose(1e-12, 0) is False no matter what rel_tol you pass. Whenever one side can be zero or tiny, set abs_tol explicitly. Available since Python 3.5; NaN is never close to anything, itself included, and complex numbers need cmath.isclose."
      },
      "syntax": "math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.isclose",
      "version": "3.5",
      "section": "Модуль math",
      "subcat": "сравнение",
      "color_group": "module",
      "aliases": [
        "сравнение чисел с погрешностью",
        "приблизительное равенство",
        "сравнить вещественные числа"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.isclose(0.1+0.2, 0.3))  # → True",
        "print(math.isclose(1.0, 1.0000001))  # → True",
        "print(math.isclose(1.0, 1.001))  # → False",
        "print(math.isclose(1.0, 1.001, rel_tol=0.01))  # → True",
        "print(math.isclose(0.0, 0.0))  # → True",
        "print(math.isclose(1e-10, 0, abs_tol=1e-9))  # → True"
      ],
      "related": [
        "float",
        "операторы-сравнения",
        "decimal.decimal"
      ],
      "related_errors": []
    },
    {
      "id": "math.isfinite",
      "title": "math.isfinite",
      "kind": "term",
      "summary": {
        "ru": "Возвращает True, если число конечное — не inf и не nan. Удобно для проверки результата вычислений.",
        "en": "Returns True if the number is finite — neither inf nor nan. Handy for checking the result of a calculation."
      },
      "body": {
        "ru": "Один вызов закрывает обе аномалии сразу: isfinite ложно и для inf, и для nan, тогда как isinf(nan) равно False, и проверка только на бесконечность тихо пропустит nan. Особенно полезно на границе с данными: float() без возражений разберёт из ввода строку 'inf' или 'nan', а переполнение во float-арифметике (1e308 * 10) молча вернёт inf вместо ошибки.",
        "en": "One call covers both anomalies at once: isfinite is false for inf and for nan alike, whereas isinf(nan) is False, so an infinity-only check quietly lets nan through. That matters at the data boundary: float() happily parses the strings 'inf' and 'nan' from input, and overflow in float arithmetic (1e308 * 10) silently yields inf instead of raising."
      },
      "syntax": "math.isfinite(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.isfinite",
      "version": "3.2",
      "section": "Модуль math",
      "subcat": "проверка",
      "color_group": "module",
      "aliases": [
        "проверка на конечность числа",
        "конечное ли число"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.isfinite(42.0))    # → True",
        "print(math.isfinite(math.inf))  # → False",
        "print(math.isfinite(math.nan))  # → False",
        "print(math.isfinite(1e308))   # → True",
        "print(math.isfinite(float('inf')))  # → False",
        "print(math.isfinite(-0.0))  # → True"
      ],
      "related": [
        "math.isinf",
        "math.isnan",
        "math.inf"
      ],
      "related_errors": []
    },
    {
      "id": "math.isinf",
      "title": "math.isinf",
      "kind": "term",
      "summary": {
        "ru": "Возвращает True, если число бесконечно (float('inf') или float('-inf')).",
        "en": "Returns True if the number is infinite (float('inf') or float('-inf'))."
      },
      "body": {
        "ru": "inf почти никогда не берётся из деления на ноль — оно возбуждает ZeroDivisionError; реальные источники это float('inf'), math.inf, разбор строки из ввода и переполнение float-арифметики вроде 1e308 * 10. Если задача просто убедиться, что число нормальное, берите math.isfinite: isinf(nan) равно False, и одной проверки на бесконечность недостаточно.",
        "en": "Infinity almost never comes from dividing by zero — that raises ZeroDivisionError; the real sources are float('inf'), math.inf, parsing user input, and overflow in float arithmetic such as 1e308 * 10. If you only want to confirm a number is sane, reach for math.isfinite instead: isinf(nan) is False, so an infinity check alone is not enough."
      },
      "syntax": "math.isinf(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.isinf",
      "version": "",
      "section": "Модуль math",
      "subcat": "проверка",
      "color_group": "module",
      "aliases": [
        "проверка на бесконечность",
        "бесконечно ли число",
        "деление дало бесконечность"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.isinf(math.inf))   # → True",
        "print(math.isinf(-math.inf))  # → True",
        "print(math.isinf(42.0))       # → False",
        "print(math.isinf(1e309))      # → True (overflow)",
        "print(math.isinf(float('inf')))  # → True",
        "print(math.isinf(0.0))  # → False"
      ],
      "related": [
        "math.inf",
        "math.isfinite",
        "math.isnan"
      ],
      "related_errors": []
    },
    {
      "id": "math.isnan",
      "title": "math.isnan",
      "kind": "term",
      "summary": {
        "ru": "Возвращает True, если число — NaN (Not a Number). Нельзя проверить через ==, так как nan != nan.",
        "en": "Returns True if the number is NaN (Not a Number). It cannot be tested with ==, because nan != nan."
      },
      "body": {
        "ru": "nan берётся не из деления на ноль (там ZeroDivisionError), а из float('nan'), разбора строки 'nan' во вводе и операций вроде math.inf - math.inf. Отдельная ловушка: оператор in и list.index сначала сравнивают объекты по идентичности, поэтому один и тот же объект nan «находится» в списке, хотя nan == nan ложно — искать и отфильтровывать nan надёжно только через math.isnan.",
        "en": "nan does not come from division by zero (that raises ZeroDivisionError) but from float('nan'), from parsing the text 'nan' out of input, and from operations like math.inf - math.inf. A separate trap: the in operator and list.index compare by identity first, so the very same nan object is reported as present in a list even though nan == nan is false — the only reliable way to find or filter nan is math.isnan."
      },
      "syntax": "math.isnan(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.isnan",
      "version": "",
      "section": "Модуль math",
      "subcat": "проверка",
      "color_group": "module",
      "aliases": [
        "проверка на не-число",
        "нечисловой результат вычислений",
        "значение не равно само себе"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.isnan(math.nan))  # → True",
        "print(math.isnan(float('nan')))  # → True",
        "print(math.isnan(42.0))  # → False",
        "print(math.isnan(math.inf - math.inf))  # → True",
        "print(math.isnan(0.0))  # → False",
        "print(math.isnan(math.nan * 0))  # → True"
      ],
      "related": [
        "math.nan",
        "math.isfinite",
        "math.isinf"
      ],
      "related_errors": []
    },
    {
      "id": "math.lcm",
      "title": "math.lcm",
      "kind": "term",
      "summary": {
        "ru": "Возвращает наименьшее общее кратное целых чисел. math.lcm(4, 6) → 12. Python 3.9+.",
        "en": "Returns the least common multiple of the integers. math.lcm(4, 6) → 12. Python 3.9+."
      },
      "body": {
        "ru": "Ноль поглощает всё: если хотя бы один аргумент равен 0, результат тоже 0 — при свёртке по списку одна нулевая величина молча обнуляет весь ответ. Без аргументов возвращается 1, поэтому единица — правильное стартовое значение для накопления. Функция появилась только в 3.9; на более старом Python её заменяют произведением, делённым нацело на НОД.",
        "en": "Zero is absorbing: if any argument is 0 the result is 0, so a single zero in a list silently wipes out the whole fold. With no arguments the answer is 1, which makes 1 the right seed when accumulating. The function only arrived in Python 3.9; on older versions you emulate it with the product divided by the gcd."
      },
      "syntax": "math.lcm(*integers)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.lcm",
      "version": "3.9",
      "section": "Модуль math",
      "subcat": "комбинаторика",
      "color_group": "module",
      "aliases": [
        "наименьшее общее кратное",
        "нок",
        "общий знаменатель"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.lcm(4, 6))     # → 12",
        "print(math.lcm(12, 18))   # → 36",
        "print(math.lcm(3, 5, 7))  # → 105",
        "print(math.lcm(0, 5))     # → 0",
        "print(math.lcm(1, 100))   # → 100",
        "print(math.lcm(6, 10, 15))  # → 30"
      ],
      "related": [
        "math.gcd",
        "fractions.fraction"
      ],
      "related_errors": []
    },
    {
      "id": "math.log",
      "title": "math.log",
      "kind": "term",
      "summary": {
        "ru": "Натуральный логарифм. log(x, base) — логарифм по основанию base.",
        "en": "The natural logarithm. log(x, base) is the logarithm to the given base."
      },
      "body": {
        "ru": "Ноль и отрицательный аргумент дают не -inf и не nan, а ValueError с текстом math domain error; для комплексного результата нужен cmath.log. Двухаргументная форма считается как log(x) / log(base), то есть с двумя округлениями: math.log(1000, 10) возвращает 2.9999999999999996, а не 3.0. Поэтому для оснований 10 и 2 берите math.log10 и math.log2 и не сравнивайте результат логарифма через ==.",
        "en": "Zero or a negative argument raises ValueError (\"math domain error\") instead of returning -inf or nan; use cmath.log if you want a complex result. The two-argument form is computed as log(x) / log(base), so it rounds twice: math.log(1000, 10) gives 2.9999999999999996, not 3.0. Prefer math.log10 and math.log2 for those bases, and never compare a logarithm with == ."
      },
      "syntax": "math.log(x[, base])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.log",
      "version": "",
      "section": "Модуль math",
      "subcat": "логарифмы",
      "color_group": "module",
      "aliases": [
        "логарифм",
        "натуральный логарифм",
        "логарифм по основанию"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.log(math.e))  # → 1.0",
        "print(math.log(1))       # → 0.0",
        "print(math.log(100, 10)) # → 2.0",
        "print(math.log(8, 2))    # → 3.0",
        "print(math.log(1000))    # → 6.907...",
        "print(math.log(2))       # → 0.6931..."
      ],
      "related": [
        "math.log10",
        "math.log2",
        "math.exp",
        "math.e"
      ],
      "related_errors": []
    },
    {
      "id": "math.log10",
      "title": "math.log10",
      "kind": "term",
      "summary": {
        "ru": "Возвращает логарифм по основанию 10. Точнее, чем math.log(x, 10). Используется для порядка величин.",
        "en": "Returns the base-10 logarithm. More accurate than math.log(x, 10). Used for orders of magnitude."
      },
      "body": {
        "ru": "Ноль и отрицательные — это ValueError (math domain error), а не -inf. Целые числа, слишком большие для float, math.log10 всё-таки принимает — там, где float(n) уже падает с OverflowError, — но результат всё равно приближённый. Поэтому считать количество цифр как int(math.log10(n)) + 1 ненадёжно на границах степеней десятки: len(str(n)) даёт точный ответ.",
        "en": "Zero and negative inputs raise ValueError (\"math domain error\") rather than returning -inf. Integers too large to convert to float are still accepted — where float(n) already raises OverflowError — but the answer is an approximation. So counting digits with int(math.log10(n)) + 1 is fragile right at powers of ten; len(str(n)) is exact."
      },
      "syntax": "math.log10(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.log10",
      "version": "",
      "section": "Модуль math",
      "subcat": "логарифмы",
      "color_group": "module",
      "aliases": [
        "десятичный логарифм",
        "логарифм по основанию 10",
        "порядок числа"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.log10(1))      # → 0.0",
        "print(math.log10(10))     # → 1.0",
        "print(math.log10(100))    # → 2.0",
        "print(math.log10(1000))   # → 3.0",
        "print(math.log10(0.001))  # → -3.0",
        "print(math.log10(2))      # → 0.30102..."
      ],
      "related": [
        "math.log",
        "math.log2"
      ],
      "related_errors": []
    },
    {
      "id": "math.log2",
      "title": "math.log2",
      "kind": "term",
      "summary": {
        "ru": "Возвращает логарифм по основанию 2. Точнее и быстрее, чем math.log(x, 2). Полезен в алгоритмах с двоичным деревом.",
        "en": "Returns the base-2 logarithm. More accurate and faster than math.log(x, 2). Useful in binary-tree algorithms."
      },
      "body": {
        "ru": "Ноль и отрицательные — ValueError (math domain error), а не -inf. Для целых чисел почти всегда лучше обойтись без float: показатель степени двойки — это n.bit_length() - 1, а проверка «n — степень двойки» пишется как n > 0 and n & (n - 1) == 0. На очень больших числах float-логарифм округляется и на границе может ошибиться, целочисленные приёмы — нет.",
        "en": "Zero and negative inputs raise ValueError (\"math domain error\"), not -inf. For integers it is usually better to skip floats entirely: the floor of the base-2 logarithm is n.bit_length() - 1, and the power-of-two test is n > 0 and n & (n - 1) == 0. On very large integers the float version rounds and can be off right at a boundary; the integer tricks never are."
      },
      "syntax": "math.log2(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.log2",
      "version": "3.3",
      "section": "Модуль math",
      "subcat": "логарифмы",
      "color_group": "module",
      "aliases": [
        "двоичный логарифм",
        "логарифм по основанию 2"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.log2(1))    # → 0.0",
        "print(math.log2(2))    # → 1.0",
        "print(math.log2(8))    # → 3.0",
        "print(math.log2(1024)) # → 10.0",
        "print(math.log2(3))    # → 1.584962...",
        "print(math.log2(0.5))  # → -1.0"
      ],
      "related": [
        "math.log",
        "math.log10",
        "int.bit_length"
      ],
      "related_errors": []
    },
    {
      "id": "math.nan",
      "title": "math.nan",
      "kind": "term",
      "summary": {
        "ru": "Not a Number — специальное значение float для результата неопределённых операций. Не равно самому себе: nan != nan.",
        "en": "Not a Number — the special float value produced by undefined operations. It is not equal to itself: nan != nan."
      },
      "body": {
        "ru": "Проверять nan можно только через math.isnan(x): любое сравнение с ним ложно, поэтому условие x == math.nan не сработает никогда. Он ещё и заразен — любая арифметика с nan даёт nan, а сортировка или min/max по списку с nan возвращает мусорный порядок, потому что сравнения перестают быть согласованными. Отдельная ловушка: math.nan in [math.nan] даёт True, так как контейнеры сначала сверяют объекты по identity, а float('nan') in [float('nan')] уже False.",
        "en": "The only reliable test is math.isnan(x): every comparison against nan is false, so a check like x == math.nan can never fire. It is also contagious — any arithmetic involving nan returns nan, and sorting or min/max over a list containing nan gives a meaningless result because the comparisons stop being consistent. One more trap: math.nan in [math.nan] is True, since containers compare by identity first, while float('nan') in [float('nan')] is False."
      },
      "syntax": "math.nan",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.nan",
      "version": "3.5",
      "section": "Модуль math",
      "subcat": "константы",
      "color_group": "module",
      "aliases": [
        "не число",
        "нечисловое значение"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.nan)  # → nan",
        "print(math.isnan(math.nan))  # → True",
        "print(math.nan == math.nan)  # → False (!)",
        "print(math.nan + 1)  # → nan",
        "print(float('nan'))  # → nan",
        "print(math.isnan(0.0))  # → False"
      ],
      "related": [
        "math.isnan",
        "math.inf",
        "math.isfinite"
      ],
      "related_errors": []
    },
    {
      "id": "math.perm",
      "title": "math.perm",
      "kind": "term",
      "summary": {
        "ru": "Число размещений P(n, k): количество упорядоченных выборок k элементов из n без повторений.",
        "en": "The number of permutations P(n, k): how many ordered selections of k items out of n there are, without repetition."
      },
      "body": {
        "ru": "Отличие от math.comb — учёт порядка: размещений ровно в k! раз больше, чем сочетаний, так что perm берут для упорядоченных расстановок, а comb — для наборов, где порядок не важен. Без второго аргумента возвращается n!, то есть то же, что у math.factorial. Как и у comb, k больше n даёт 0, а отрицательные аргументы — ValueError.",
        "en": "The difference from math.comb is that order counts: there are exactly k! times more permutations than combinations, so reach for perm when arrangements matter and comb when only the set does. Omitting the second argument returns n!, the same value math.factorial gives. As with comb, k greater than n quietly yields 0, while negative arguments raise ValueError."
      },
      "syntax": "math.perm(n, k=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.perm",
      "version": "3.8",
      "section": "Модуль math",
      "subcat": "комбинаторика",
      "color_group": "module",
      "aliases": [
        "число размещений",
        "количество перестановок из n по k"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.perm(5, 2))   # → 20",
        "print(math.perm(10, 3))  # → 720",
        "print(math.perm(5))      # → 120 (= 5!)",
        "print(math.perm(5, 0))   # → 1",
        "print(math.perm(5, 1))   # → 5",
        "print(math.perm(4, 4))   # → 24 (= 4!)"
      ],
      "related": [
        "math.comb",
        "math.factorial",
        "itertools.permutations"
      ],
      "related_errors": []
    },
    {
      "id": "math.pi",
      "title": "math.pi",
      "kind": "term",
      "summary": {
        "ru": "Математическая константа π ≈ 3.141592653589793. Используется в тригонометрии и геометрии.",
        "en": "The mathematical constant π ≈ 3.141592653589793. Used in trigonometry and geometry."
      },
      "body": {
        "ru": "Это ближайший double к настоящему π, поэтому math.sin(math.pi) даёт не ноль, а около 1.2e-16 — сравнивать такие результаты с нулём через == бессмысленно, нужен math.isclose с abs_tol. Все тригонометрические функции модуля ждут радианы: градусы переводите math.radians(), иначе получите тихо неверный ответ без всякой ошибки. Для полного оборота есть готовая math.tau, равная 2π.",
        "en": "It is the nearest double to the true π, so math.sin(math.pi) returns about 1.2e-16 rather than zero — comparing such results to zero with == is pointless, use math.isclose with an abs_tol. Every trigonometric function in the module takes radians: convert degrees with math.radians(), otherwise you get a silently wrong answer and no error at all. For a full turn there is math.tau, which equals 2π."
      },
      "syntax": "math.pi",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.pi",
      "version": "",
      "section": "Модуль math",
      "subcat": "константы",
      "color_group": "module",
      "aliases": [
        "число пи",
        "константа пи"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.pi)  # → 3.141592653589793",
        "print(2 * math.pi)  # → 6.283185307179586",
        "print(math.pi ** 2)  # → 9.869604401089358",
        "area = math.pi * 5**2",
        "print(round(area, 2))  # → 78.54",
        "print(math.tau / 2)  # → 3.141592...  (tau = 2*pi)",
        "print(math.degrees(math.pi))  # → 180.0"
      ],
      "related": [
        "math.tau",
        "math.radians",
        "math.degrees"
      ],
      "related_errors": []
    },
    {
      "id": "math.pow",
      "title": "math.pow",
      "kind": "term",
      "summary": {
        "ru": "Возводит x в степень y, всегда возвращает float. Отличие от **:  math.pow(-1, 0.5) — ValueError, ** — complex.",
        "en": "Raises x to the power of y and always returns a float. The difference from **: math.pow(-1, 0.5) raises ValueError, while ** gives a complex number."
      },
      "body": {
        "ru": "Оба аргумента сначала приводятся к float, поэтому на больших целых результат теряет точность, а math.pow(2, 10000) просто падает с OverflowError — там, где 2 ** 10000 считается точно и без ограничений. Для целочисленной арифметики бери оператор ** или встроенный pow(); только у встроенного есть третий аргумент — модуль, незаменимый в задачах на быстрое возведение по модулю.",
        "en": "Both arguments are converted to float first, so large integers lose precision and math.pow(2, 10000) raises OverflowError, while 2 ** 10000 is computed exactly. Keep math.pow for real-valued math and use ** or the built-in pow() for integers — only the built-in accepts a third argument, the modulus, which is what modular exponentiation problems need."
      },
      "syntax": "math.pow(x, y)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.pow",
      "version": "",
      "section": "Модуль math",
      "subcat": "степень/корень",
      "color_group": "module",
      "aliases": [
        "возведение в степень",
        "число в степени"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.pow(2, 10))   # → 1024.0",
        "print(math.pow(4, 0.5))  # → 2.0 (квадратный корень)",
        "print(math.pow(3, 3))    # → 27.0",
        "print(math.pow(2, -1))   # → 0.5",
        "print(math.pow(0, 0))    # → 1.0",
        "print(math.pow(10, 3))   # → 1000.0"
      ],
      "related": [
        "степень",
        "pow",
        "math.sqrt"
      ],
      "related_errors": []
    },
    {
      "id": "math.prod",
      "title": "math.prod",
      "kind": "term",
      "summary": {
        "ru": "Возвращает произведение всех элементов итерируемого. Python 3.8+. math.prod([2, 3, 4]) → 24.",
        "en": "Returns the product of all the items of an iterable. Python 3.8+. math.prod([2, 3, 4]) → 24."
      },
      "body": {
        "ru": "У пустого итерируемого произведение равно start, то есть 1 — это математическая договорённость, а не баг; если по условию задачи пустой вход должен давать 0, проверяйте его сами. Аргумент start здесь умножается, а не прибавляется, как в sum(). На целых результат точный при любой длине чисел, а на float ошибки округления копятся, поэтому факториал лучше считать через math.factorial().",
        "en": "For an empty iterable the result is start, i.e. 1 — a mathematical convention, not a bug; if your task needs 0 for empty input, check for it yourself. Note that start is multiplied in, not added, unlike in sum(). With ints the result stays exact at any size, but with floats rounding error accumulates, so compute factorials with math.factorial() instead."
      },
      "syntax": "math.prod(iterable, start=1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.prod",
      "version": "3.8",
      "section": "Модуль math",
      "subcat": "агрегация",
      "color_group": "module",
      "aliases": [
        "произведение элементов списка",
        "перемножить все числа"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.prod([1,2,3,4,5]))  # → 120",
        "print(math.prod(range(1,6)))   # → 120 (= 5!)",
        "print(math.prod([1.5, 2.0, 3.0]))  # → 9.0",
        "print(math.prod([2]*10))       # → 1024 (2^10)",
        "print(math.prod([], start=5))  # → 5",
        "print(math.prod(range(1,8)))   # → 5040 (= 7!)"
      ],
      "related": [
        "sum",
        "math.fsum",
        "functools.reduce"
      ],
      "related_errors": []
    },
    {
      "id": "math.radians",
      "title": "math.radians",
      "kind": "term",
      "summary": {
        "ru": "Конвертирует угол из градусов в радианы. math.radians(180) → 3.14159... Нужно для тригонометрических функций.",
        "en": "Converts an angle from degrees to radians. math.radians(180) → 3.14159... Needed by the trigonometric functions."
      },
      "body": {
        "ru": "Все тригонометрические функции math ждут радианы и никак не сообщают, что вы ошиблись: math.sin(30) не упадёт, а вернёт синус 30 радиан — число выглядит нормально, но задача решена неверно. Поэтому градусы прогоняйте через radians() прямо в месте вызова: math.sin(math.radians(30)).",
        "en": "Every trigonometric function in math expects radians and has no way to warn you otherwise: math.sin(30) does not fail, it returns the sine of 30 radians — a normal-looking number that silently breaks the answer. Convert at the call site instead: math.sin(math.radians(30))."
      },
      "syntax": "math.radians(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.radians",
      "version": "",
      "section": "Модуль math",
      "subcat": "тригонометрия",
      "color_group": "module",
      "aliases": [
        "перевод градусов в радианы",
        "тригонометрия в градусах"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.radians(180))  # → 3.14159...",
        "print(math.radians(90))   # → 1.5707...",
        "print(math.radians(0))    # → 0.0",
        "print(math.radians(360))  # → 6.2831...",
        "print(math.radians(45))   # → 0.7853...",
        "print(math.radians(60))   # → 1.0471..."
      ],
      "related": [
        "math.degrees",
        "math.cos",
        "math.pi"
      ],
      "related_errors": []
    },
    {
      "id": "math.sin",
      "title": "math.sin",
      "kind": "term",
      "summary": {
        "ru": "Возвращает синус угла в радианах. Для перевода градусов в радианы используй math.radians().",
        "en": "Returns the sine of an angle given in radians. To convert degrees to radians use math.radians()."
      },
      "body": {
        "ru": "math.pi — лишь float-приближение π, поэтому точных нулей не бывает: math.sin(math.pi) даёт 1.22e-16, а не 0. Сравнивать результат с 0 или 1 через == нельзя — только math.isclose() или round() до нужного знака. Обратная функция math.asin() тоже отдаёт радианы, так что для градусов её результат прогоняют через math.degrees().",
        "en": "math.pi is only a float approximation of π, so exact zeros never show up: math.sin(math.pi) returns 1.22e-16, not 0. Never test the result against 0 or 1 with ==; use math.isclose() or round() to a fixed number of digits. The inverse, math.asin(), also speaks radians, so pass its result through math.degrees() when you need degrees."
      },
      "syntax": "math.sin(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.sin",
      "version": "",
      "section": "Модуль math",
      "subcat": "тригонометрия",
      "color_group": "module",
      "aliases": [
        "синус",
        "синус угла",
        "синус в градусах"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.sin(0))            # → 0.0",
        "print(math.sin(math.pi/2))    # → 1.0",
        "print(math.sin(math.pi))      # → 1.22e-16 ≈ 0",
        "print(round(math.sin(math.pi/6), 2))  # → 0.5",
        "print(math.sin(math.pi/4))    # → 0.7071...",
        "print(math.sin(-math.pi/2))   # → -1.0"
      ],
      "related": [
        "math.cos",
        "math.tan",
        "math.radians",
        "math.asin"
      ],
      "related_errors": []
    },
    {
      "id": "math.sqrt",
      "title": "math.sqrt",
      "kind": "term",
      "summary": {
        "ru": "Возвращает квадратный корень числа как float. Для целочисленного результата используй isqrt(). Отрицательные числа вызывают ValueError.",
        "en": "Returns the square root of a number as a float. For an integer result use isqrt(). A negative number raises ValueError."
      },
      "body": {
        "ru": "Аргумент всегда приводится к float, так что у больших целых корень выходит приблизительным, а для чисел свыше примерно 1.8e308 будет OverflowError — целые любой длины обрабатывает math.isqrt (Python 3.8+). По той же причине проверка «полный ли квадрат» через math.sqrt(n) ** 2 == n на больших n врёт: надёжный вариант — сравнить math.isqrt(n) ** 2 с n.",
        "en": "The argument is converted to float first, so the root of a large integer is only approximate, and anything above roughly 1.8e308 raises OverflowError — math.isqrt (Python 3.8+) handles integers of any size. For the same reason a perfect-square test written as math.sqrt(n) ** 2 == n starts giving wrong answers on big values; compare math.isqrt(n) ** 2 with n instead."
      },
      "syntax": "math.sqrt(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.sqrt",
      "version": "",
      "section": "Модуль math",
      "subcat": "степень/корень",
      "color_group": "module",
      "aliases": [
        "квадратный корень",
        "извлечь корень из числа",
        "корень числа"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.sqrt(4))   # → 2.0",
        "print(math.sqrt(2))   # → 1.41421356...",
        "print(math.sqrt(0))   # → 0.0",
        "print(math.sqrt(100)) # → 10.0",
        "print(math.sqrt(2.25)) # → 1.5",
        "print(round(math.sqrt(3), 6))  # → 1.732051"
      ],
      "related": [
        "math.isqrt",
        "math.pow",
        "math.cbrt"
      ],
      "related_errors": []
    },
    {
      "id": "math.tan",
      "title": "math.tan",
      "kind": "term",
      "summary": {
        "ru": "Возвращает тангенс угла в радианах. math.tan(math.pi/4) ≈ 1.0. Не определён при x = π/2 + πn.",
        "en": "Returns the tangent of an angle given in radians. math.tan(math.pi/4) ≈ 1.0. It is undefined at x = π/2 + πn."
      },
      "body": {
        "ru": "В точке разрыва Python не бросает исключение и не возвращает inf: math.pi/2 лишь приближение настоящего π/2, поэтому math.tan(math.pi/2) выдаёт огромное конечное число около 1.6e16. Ловить здесь нечего — если разрыв важен для задачи, проверяй аргумент сам, до вызова. И рядом с π/2 ответ ненадёжен в принципе: сдвиг аргумента на 1e-16 меняет его в разы.",
        "en": "At the discontinuity Python neither raises nor returns inf: math.pi/2 is only an approximation of the true π/2, so math.tan(math.pi/2) yields a huge finite number around 1.6e16. There is no exception to catch, so if the discontinuity matters, screen the argument yourself before calling. Near π/2 the answer is inherently unreliable anyway: nudging the input by 1e-16 changes it by orders of magnitude."
      },
      "syntax": "math.tan(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.tan",
      "version": "",
      "section": "Модуль math",
      "subcat": "тригонометрия",
      "color_group": "module",
      "aliases": [
        "тангенс",
        "тангенс угла"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.tan(0))             # → 0.0",
        "print(math.tan(math.pi/4))    # → 1.0",
        "print(round(math.tan(math.pi/6), 4))  # → 0.5774",
        "print(math.tan(math.pi))      # → -1.22e-16 ≈ 0",
        "print(math.tan(-math.pi/4))   # → -1.0",
        "print(round(math.tan(1), 4))  # → 1.5574"
      ],
      "related": [
        "math.sin",
        "math.cos",
        "math.atan",
        "math.radians"
      ],
      "related_errors": []
    },
    {
      "id": "math.tau",
      "title": "math.tau",
      "kind": "term",
      "summary": {
        "ru": "Математическая константа τ = 2π ≈ 6.283185307179586. Удобна в формулах, где часто встречается 2π.",
        "en": "The mathematical constant τ = 2π ≈ 6.283185307179586. Convenient in formulas where 2π keeps coming up."
      },
      "body": {
        "ru": "Константа появилась в Python 3.6, так что код для более старых интерпретаторов пишут через 2 * math.pi. Равенство math.tau == 2 * math.pi выполняется точно, а не по счастливой случайности: умножение float на двойку не теряет ни бита, и обе константы округляются к одному и тому же double. Отсюда вывод: tau даёт выигрыш в читаемости (tau/8 сразу читается как восьмая часть оборота), но не в точности.",
        "en": "Added in Python 3.6, so code that must run on older interpreters still writes 2 * math.pi. The equality math.tau == 2 * math.pi is exact rather than accidental: doubling a float loses no bits, so both constants round to the same double. The payoff is readability — tau/8 reads as one eighth of a full turn — not extra precision."
      },
      "syntax": "math.tau",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.tau",
      "version": "3.6",
      "section": "Модуль math",
      "subcat": "константы",
      "color_group": "module",
      "aliases": [
        "два пи",
        "полный оборот в радианах"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.tau)  # → 6.283185307179586",
        "print(math.tau == 2 * math.pi)  # → True",
        "print(math.tau / 4)  # → 1.5707... (π/2)",
        "print(math.sin(math.tau))  # → -2.44e-16 ≈ 0",
        "print(math.cos(math.tau))  # → 1.0",
        "print(math.tau * 5)  # → 31.416..."
      ],
      "related": [
        "math.pi",
        "math.radians"
      ],
      "related_errors": []
    },
    {
      "id": "math.trunc",
      "title": "math.trunc",
      "kind": "term",
      "summary": {
        "ru": "Усечение дробной части — округление к нулю. math.trunc(2.9) → 2, math.trunc(-2.9) → -2.",
        "en": "Cuts off the fractional part — rounding towards zero. math.trunc(2.9) → 2, math.trunc(-2.9) → -2."
      },
      "body": {
        "ru": "На положительных числах trunc и floor неотличимы, разница вылезает только на отрицательных: trunc(-3.5) даёт -3, а floor(-3.5) даёт -4. Для float trunc делает ровно то же, что int(x), поэтому в учебном коде его пишут редко — берут, когда нужно подчеркнуть намерение или задействовать __trunc__ собственного класса.",
        "en": "For positive numbers trunc and floor are indistinguishable; the difference shows up only on negatives: trunc(-3.5) is -3 while floor(-3.5) is -4. On a float trunc does exactly what int(x) does, which is why it rarely appears in beginner code — it is used to state the intent explicitly or to hook into a custom class's __trunc__."
      },
      "syntax": "math.trunc(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.trunc",
      "version": "",
      "section": "Модуль math",
      "subcat": "округление",
      "color_group": "module",
      "aliases": [
        "отбросить дробную часть",
        "усечение дробной части",
        "округление к нулю"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import math",
        "print(math.trunc(3.9))   # → 3",
        "print(math.trunc(-3.9))  # → -3",
        "print(math.trunc(3.1))   # → 3",
        "print(math.trunc(-3.1))  # → -3",
        "print(math.trunc(0.9))   # → 0",
        "print(math.trunc(-0.5))  # → 0"
      ],
      "related": [
        "math.floor",
        "math.ceil",
        "int"
      ],
      "related_errors": []
    },
    {
      "id": "operator.add",
      "title": "operator.add",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a + b (функциональный эквивалент оператора `+`).",
        "en": "Return a + b (the functional form of `+`)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.add(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.add",
      "version": "",
      "section": "Модуль operator",
      "subcat": "арифметика",
      "color_group": "module",
      "aliases": [
        "сложение как функция",
        "функция вместо оператора плюс"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "import functools",
        "print(operator.add(1, 2))   # → 3",
        "print(operator.add(1, 2) == 1 + 2)   # → True",
        "print(operator.add('py', 'thon'))   # → python",
        "print(functools.reduce(operator.add, [1, 2, 3, 4]))   # → 10",
        "print(list(map(operator.add, [1, 2, 3], [10, 20, 30])))   # → [11, 22, 33]"
      ],
      "related": [
        "operator.sub",
        "operator.iadd",
        "сложение",
        "operator.concat"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.and_",
      "title": "operator.and_",
      "kind": "function",
      "summary": {
        "ru": "Побитовое И: a & b (подчёркивание в имени — чтобы не совпасть с ключевым словом `and`).",
        "en": "Bitwise AND: a & b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.and_(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.and_",
      "version": "",
      "section": "Модуль operator",
      "subcat": "битовые",
      "color_group": "module",
      "aliases": [
        "побитовое И",
        "битовое И как функция"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "import functools",
        "print(operator.and_(5, 3))   # → 1",
        "print(operator.and_(5, 3) == 5 & 3)   # → True",
        "print(operator.and_(2, 3) == (2 and 3))   # → False",
        "print(functools.reduce(operator.and_, [0b1110, 0b1011, 0b0111]))   # → 2",
        "print(operator.and_(True, False))   # → False"
      ],
      "related": [
        "operator.or_",
        "operator.xor",
        "operator.iand",
        "побитовые-операторы"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.attrgetter",
      "title": "operator.attrgetter",
      "kind": "function",
      "summary": {
        "ru": "Фабрика: возвращает функцию, извлекающую атрибут(ы) по фиксированному имени (поддерживает точечные пути).",
        "en": "A factory returning a callable that fetches attributes by fixed name."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "f = operator.attrgetter('attr')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.attrgetter",
      "version": "",
      "section": "Модуль operator",
      "subcat": "вызов и доступ",
      "color_group": "module",
      "aliases": [
        "извлечь атрибут по имени",
        "сортировка объектов по атрибуту"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "g = operator.attrgetter('real')",
        "print(g(3 + 4j))   # → 3.0",
        "print(operator.attrgetter('real')(3 + 4j) == (3 + 4j).real)   # → True",
        "print(operator.attrgetter('real', 'imag')(3 + 4j))   # → (3.0, 4.0)",
        "print(operator.attrgetter('imag.real')(3 + 4j))   # → 4.0",
        "print(sorted([3 + 9j, 1 + 2j], key=operator.attrgetter('imag')))   # → [(1+2j), (3+9j)]"
      ],
      "related": [
        "operator.itemgetter",
        "operator.methodcaller",
        "sorted-с-key",
        "getattr"
      ],
      "related_errors": [
        "AttributeError"
      ]
    },
    {
      "id": "operator.call",
      "title": "operator.call",
      "kind": "function",
      "summary": {
        "ru": "Вызывает объект с аргументами: call(f, *args, **kwargs) == f(*args, **kwargs) (Python 3.11+).",
        "en": "Call an object: call(f, *args, **kwargs) == f(*args, **kwargs) (3.11+)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.call(f, *args, **kwargs)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.call",
      "version": "",
      "section": "Модуль operator",
      "subcat": "вызов и доступ",
      "color_group": "module",
      "aliases": [
        "вызвать объект как функцию",
        "вызов функции из переменной"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.call(abs, -5))   # → 5",
        "print(operator.call(abs, -5) == abs(-5))   # → True",
        "print(operator.call(round, 3.14159, ndigits=2))   # → 3.14",
        "print([operator.call(f) for f in (list, dict, tuple)])   # → [[], {}, ()]",
        "print(operator.call(5))   # → TypeError"
      ],
      "related": [
        "operator.methodcaller",
        "вызываемые-объекты-__call__",
        "callable"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.concat",
      "title": "operator.concat",
      "kind": "function",
      "summary": {
        "ru": "Конкатенация двух последовательностей: a + b (списки, строки, кортежи).",
        "en": "Concatenate two sequences: a + b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.concat(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.concat",
      "version": "",
      "section": "Модуль operator",
      "subcat": "последовательности",
      "color_group": "module",
      "aliases": [
        "склеить две последовательности",
        "конкатенация функцией"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "from functools import reduce",
        "print(operator.concat([1], [2]))   # → [1, 2]",
        "print(operator.concat('py', 'thon'))   # → python",
        "print(operator.concat((1, 2), (3,)))   # → (1, 2, 3)",
        "print(reduce(operator.concat, [[1], [2], [3]]))   # → [1, 2, 3]",
        "print(operator.concat(1, 2))   # → TypeError"
      ],
      "related": [
        "operator.iconcat",
        "operator.add",
        "объединение-повторение-списков"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.contains",
      "title": "operator.contains",
      "kind": "function",
      "summary": {
        "ru": "Проверка вхождения: b in a (обратите внимание на порядок аргументов).",
        "en": "Membership test: b in a (note the argument order)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.contains(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.contains",
      "version": "",
      "section": "Модуль operator",
      "subcat": "последовательности",
      "color_group": "module",
      "aliases": [
        "проверить вхождение элемента",
        "есть ли элемент в последовательности"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.contains([1, 2, 3], 2))   # → True",
        "print(operator.contains('python', 'th'))   # → True",
        "print(operator.contains({'a': 1}, 'a'))   # → True",
        "print(operator.contains({'a': 1}, 1))   # → False",
        "print(operator.contains(2, [1, 2, 3]))   # → TypeError"
      ],
      "related": [
        "in",
        "operator.countOf",
        "operator.indexOf"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.countOf",
      "title": "operator.countOf",
      "kind": "function",
      "summary": {
        "ru": "Число вхождений b в последовательности a.",
        "en": "The number of occurrences of b in a."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.countOf(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.countOf",
      "version": "",
      "section": "Модуль operator",
      "subcat": "последовательности",
      "color_group": "module",
      "aliases": [
        "сколько раз встречается элемент",
        "подсчёт вхождений в последовательности"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.countOf([1, 2, 2, 3], 2))   # → 2",
        "print(operator.countOf('abracadabra', 'a'))   # → 5",
        "print(operator.countOf([1, 2, 3], 9))   # → 0",
        "print(operator.countOf({'x': 1, 'y': 1}.values(), 1))   # → 2",
        "print(operator.countOf('abracadabra', 'ab'))   # → 0"
      ],
      "related": [
        "list.count",
        "operator.indexOf",
        "operator.contains"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.delitem",
      "title": "operator.delitem",
      "kind": "function",
      "summary": {
        "ru": "Удаление по ключу/индексу: del a[b].",
        "en": "Item deletion: del a[b]."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.delitem(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.delitem",
      "version": "",
      "section": "Модуль operator",
      "subcat": "последовательности",
      "color_group": "module",
      "aliases": [
        "удалить элемент по индексу функцией",
        "удаление по ключу без оператора del"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "l = [1, 2, 3]",
        "operator.delitem(l, 1)",
        "print(l)   # → [1, 3]"
      ],
      "related": [
        "operator.setitem",
        "operator.getitem",
        "del-для-списка",
        "del-для-словаря"
      ],
      "related_errors": [
        "IndexError",
        "KeyError",
        "TypeError"
      ]
    },
    {
      "id": "operator.eq",
      "title": "operator.eq",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a == b (функциональный эквивалент `==`).",
        "en": "Return a == b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.eq(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.eq",
      "version": "",
      "section": "Модуль operator",
      "subcat": "сравнение",
      "color_group": "module",
      "aliases": [
        "проверка на равенство",
        "равны ли два объекта"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.eq(1, 1))   # → True",
        "print(operator.eq(1, 1.0))  # → True",
        "print(operator.eq([1, 2], (1, 2)))  # → False",
        "print(list(map(operator.eq, [1, 2, 3], [1, 5, 3])))  # → [True, False, True]",
        "print(operator.eq(float('nan'), float('nan')))  # → False"
      ],
      "related": [
        "operator.ne",
        "operator.is_",
        "операторы-сравнения"
      ],
      "related_errors": []
    },
    {
      "id": "operator.floordiv",
      "title": "operator.floordiv",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a // b — целочисленное деление с округлением вниз (эквивалент `//`).",
        "en": "Return a // b — floor division (the functional form of `//`)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.floordiv(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.floordiv",
      "version": "",
      "section": "Модуль operator",
      "subcat": "арифметика",
      "color_group": "module",
      "aliases": [
        "целочисленное деление как функция",
        "деление нацело функцией"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.floordiv(7, 2))   # → 3",
        "print(operator.floordiv(7, 2) == 7 // 2)   # → True",
        "print(operator.floordiv(-7, 2))   # → -4",
        "print(list(map(operator.floordiv, [10, 20, 30], [3, 3, 4])))   # → [3, 6, 7]",
        "print(operator.floordiv(1, 0))   # → ZeroDivisionError"
      ],
      "related": [
        "operator.truediv",
        "operator.mod",
        "целочисленное-деление",
        "divmod"
      ],
      "related_errors": [
        "ZeroDivisionError",
        "TypeError"
      ]
    },
    {
      "id": "operator.ge",
      "title": "operator.ge",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a >= b.",
        "en": "Return a >= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.ge(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.ge",
      "version": "",
      "section": "Модуль operator",
      "subcat": "сравнение",
      "color_group": "module",
      "aliases": [
        "больше или равно",
        "не меньше чем"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.ge(2, 2))   # → True",
        "print(operator.ge('b', 'a'))  # → True",
        "scores = [70, 55, 90]",
        "print([operator.ge(s, 60) for s in scores])  # → [True, False, True]",
        "print(operator.ge({1, 2, 3}, {1, 2}))  # → True",
        "print(operator.ge({1, 2}, {2, 3}))  # → False"
      ],
      "related": [
        "operator.le",
        "operator.gt",
        "operator.lt"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.getitem",
      "title": "operator.getitem",
      "kind": "function",
      "summary": {
        "ru": "Доступ по ключу/индексу: a[b].",
        "en": "Item access: a[b]."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.getitem(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.getitem",
      "version": "",
      "section": "Модуль operator",
      "subcat": "последовательности",
      "color_group": "module",
      "aliases": [
        "взять элемент по индексу функцией",
        "получить значение по ключу функцией"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.getitem([10, 20, 30], 1))   # → 20",
        "print(operator.getitem({'a': 1, 'b': 2}, 'b'))  # → 2",
        "print(operator.getitem([10, 20, 30], slice(0, 2)))  # → [10, 20]",
        "rows = [(1, 'anna'), (2, 'boris')]",
        "print([operator.getitem(r, 1) for r in rows])  # → ['anna', 'boris']",
        "print(operator.getitem([10, 20, 30], 5))  # → IndexError"
      ],
      "related": [
        "operator.setitem",
        "operator.delitem",
        "operator.itemgetter"
      ],
      "related_errors": [
        "IndexError",
        "KeyError",
        "TypeError"
      ]
    },
    {
      "id": "operator.gt",
      "title": "operator.gt",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a > b.",
        "en": "Return a > b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.gt(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.gt",
      "version": "",
      "section": "Модуль operator",
      "subcat": "сравнение",
      "color_group": "module",
      "aliases": [
        "строго больше",
        "больше чем"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.gt(3, 2))   # → True",
        "print(operator.gt(2, 2))   # → False",
        "print(operator.gt('b', 'a'))   # → True",
        "pairs = [(3, 2), (1, 4), (5, 5)]",
        "print([operator.gt(a, b) for a, b in pairs])   # → [True, False, False]",
        "print(operator.gt(1, 'a'))   # → TypeError"
      ],
      "related": [
        "operator.lt",
        "operator.ge",
        "operator.le"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.iadd",
      "title": "operator.iadd",
      "kind": "function",
      "summary": {
        "ru": "In-place сложение: a += b (для изменяемых типов мутирует a, для неизменяемых — как add).",
        "en": "In-place addition: a += b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.iadd(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.iadd",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "сложение с присваиванием",
        "прибавить на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.iadd(1, 2))   # → 3",
        "print(operator.iadd(1, 2) == 1 + 2)   # → True",
        "nums = [1, 2]",
        "print(operator.iadd(nums, [3]))   # → [1, 2, 3]",
        "print(nums)   # → [1, 2, 3]",
        "t = (1, 2)",
        "print(operator.iadd(t, (3,)))   # → (1, 2, 3)",
        "print(t)   # → (1, 2)"
      ],
      "related": [
        "operator.add",
        "operator.iconcat",
        "операторы-присваивания",
        "operator.isub"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.iand",
      "title": "operator.iand",
      "kind": "function",
      "summary": {
        "ru": "In-place побитовое И: a &= b.",
        "en": "In-place bitwise AND: a &= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.iand(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.iand",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "побитовое И с присваиванием",
        "наложить маску на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.iand(5, 3))   # → 1",
        "print(operator.iand(5, 3) == (5 & 3))   # → True",
        "x = 5",
        "print(operator.iand(x, 3), x)   # → 1 5",
        "print(sorted(operator.iand({1, 2, 3}, {2, 3, 4})))   # → [2, 3]",
        "print(list(map(operator.iand, [6, 12], [3, 10])))   # → [2, 8]"
      ],
      "related": [
        "operator.and_",
        "operator.ior",
        "operator.ixor"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.iconcat",
      "title": "operator.iconcat",
      "kind": "function",
      "summary": {
        "ru": "In-place конкатенация: a += b для последовательностей (мутирует список).",
        "en": "In-place concatenation: a += b for sequences."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.iconcat(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.iconcat",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "дописать список на месте",
        "конкатенация с присваиванием"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.iconcat([1], [2]))   # → [1, 2]",
        "nums = [1, 2]",
        "print(operator.iconcat(nums, [3, 4]) is nums)   # → True",
        "print(nums)   # → [1, 2, 3, 4]",
        "print(operator.iconcat('ab', 'cd'))   # → abcd",
        "print(operator.iconcat([1], 2))   # → TypeError"
      ],
      "related": [
        "operator.concat",
        "operator.iadd",
        "list.extend"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.ifloordiv",
      "title": "operator.ifloordiv",
      "kind": "function",
      "summary": {
        "ru": "In-place целочисленное деление: a //= b.",
        "en": "In-place floor division: a //= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.ifloordiv(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.ifloordiv",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "целочисленное деление с присваиванием",
        "поделить нацело на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.ifloordiv(7, 2))   # → 3",
        "print(operator.ifloordiv(7, 2) == 7 // 2)   # → True",
        "print(operator.ifloordiv(-7, 2))   # → -4",
        "print(list(map(operator.ifloordiv, [10, 20, 30], [3, 6, 7])))   # → [3, 3, 4]",
        "print(operator.ifloordiv(7, 0))   # → ZeroDivisionError"
      ],
      "related": [
        "operator.floordiv",
        "operator.itruediv",
        "операторы-присваивания"
      ],
      "related_errors": [
        "ZeroDivisionError"
      ]
    },
    {
      "id": "operator.ilshift",
      "title": "operator.ilshift",
      "kind": "function",
      "summary": {
        "ru": "In-place сдвиг влево: a <<= b.",
        "en": "In-place left shift: a <<= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.ilshift(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.ilshift",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "сдвиг влево с присваиванием",
        "сдвинуть биты влево на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.ilshift(1, 3))   # → 8",
        "print(operator.ilshift(1, 3) == 1 << 3)   # → True",
        "print(operator.ilshift(5, 3), 5 * 2 ** 3)   # → 40 40",
        "print(list(map(operator.ilshift, [1, 2, 3], [4, 3, 2])))   # → [16, 16, 12]",
        "print(operator.ilshift(1, -1))   # → ValueError"
      ],
      "related": [
        "operator.lshift",
        "operator.irshift",
        "побитовые-операторы"
      ],
      "related_errors": []
    },
    {
      "id": "operator.imatmul",
      "title": "operator.imatmul",
      "kind": "function",
      "summary": {
        "ru": "In-place матричное умножение: a @= b (вызывает __imatmul__).",
        "en": "In-place matrix multiplication: a @= b (invokes __imatmul__)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.imatmul(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.imatmul",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "матричное умножение с присваиванием",
        "перемножить матрицы на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "class M:",
        "    def __imatmul__(self, other): return 'imm'",
        "print(operator.imatmul(M(), M()))   # → imm"
      ],
      "related": [
        "operator.matmul",
        "operator.imul",
        "операторы-присваивания"
      ],
      "related_errors": []
    },
    {
      "id": "operator.imod",
      "title": "operator.imod",
      "kind": "function",
      "summary": {
        "ru": "In-place остаток: a %= b.",
        "en": "In-place modulo: a %= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.imod(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.imod",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "остаток с присваиванием",
        "взять остаток на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.imod(7, 3))   # → 1",
        "print(operator.imod(7, 3) == 7 % 3)   # → True",
        "print(operator.imod(-7, 3))   # → 2",
        "print(list(map(operator.imod, [10, 11, 12], [3, 3, 5])))   # → [1, 2, 2]",
        "print(operator.imod('%s!', 'hi'))   # → hi!"
      ],
      "related": [
        "operator.mod",
        "operator.ifloordiv",
        "операторы-присваивания"
      ],
      "related_errors": [
        "ZeroDivisionError"
      ]
    },
    {
      "id": "operator.imul",
      "title": "operator.imul",
      "kind": "function",
      "summary": {
        "ru": "In-place умножение: a *= b.",
        "en": "In-place multiplication: a *= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.imul(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.imul",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "умножение с присваиванием",
        "умножить на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.imul(3, 4))   # → 12",
        "print(operator.imul('ab', 3))   # → ababab",
        "nums = [1, 2]",
        "print(operator.imul(nums, 2) is nums)   # → True",
        "print(nums)   # → [1, 2, 1, 2]",
        "n = 3   # int неизменяем: результат надо присвоить обратно",
        "print(operator.imul(n, 4), n)   # → 12 3"
      ],
      "related": [
        "operator.mul",
        "operator.itruediv",
        "операторы-присваивания"
      ],
      "related_errors": []
    },
    {
      "id": "operator.index",
      "title": "operator.index",
      "kind": "function",
      "summary": {
        "ru": "Возвращает объект, приведённый к целому через протокол __index__() — эквивалент a.__index__(); с версии 3.10 результат всегда точного типа int.",
        "en": "Returns a converted to an integer via the __index__() protocol — equivalent to a.__index__(); since 3.10 the result always has exact type int."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.index(a)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.index",
      "version": "",
      "section": "Модуль operator",
      "subcat": "арифметика",
      "color_group": "module",
      "aliases": [
        "привести объект к индексу",
        "преобразовать в целое для индексации"
      ],
      "keywords": [
        "operator.index",
        "operator.__index__"
      ],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.index(5))                   # → 5",
        "print(operator.index(True))                # → 1",
        "print(type(operator.index(True)))          # → <class 'int'>",
        "print(operator.__index__(-3))              # → -3",
        "print([10, 20, 30][operator.index(True)])  # → 20"
      ],
      "related": [
        "int",
        "bin",
        "hex",
        "slice"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.indexOf",
      "title": "operator.indexOf",
      "kind": "function",
      "summary": {
        "ru": "Индекс первого вхождения b в a (аналог list.index).",
        "en": "The index of the first occurrence of b in a."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.indexOf(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.indexOf",
      "version": "",
      "section": "Модуль operator",
      "subcat": "последовательности",
      "color_group": "module",
      "aliases": [
        "найти позицию элемента",
        "номер первого вхождения"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.indexOf([1, 2, 3], 3))   # → 2",
        "print(operator.indexOf('hello', 'l'))  # → 2",
        "print(operator.indexOf((10, 20, 30), 20))  # → 1",
        "print(operator.indexOf(iter([5, 6, 7]), 7))  # → 2",
        "print(operator.indexOf([1, 2, 3], 9))  # → ValueError"
      ],
      "related": [
        "list.index",
        "operator.countOf",
        "operator.contains"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "operator.inv",
      "title": "operator.inv",
      "kind": "function",
      "summary": {
        "ru": "Псевдоним operator.invert: побитовая инверсия ~a.",
        "en": "An alias of operator.invert: bitwise inversion ~a."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.inv(a)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.inv",
      "version": "",
      "section": "Модуль operator",
      "subcat": "битовые",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.inv(5))   # → -6",
        "print(operator.inv(5) == ~5)   # → True",
        "print(operator.inv(5) == operator.invert(5))   # → True",
        "print(list(map(operator.inv, [0, 1, -1])))   # → [-1, -2, 0]",
        "print(operator.inv(2.0))   # → TypeError"
      ],
      "related": [
        "operator.invert",
        "operator.neg",
        "побитовые-операторы"
      ],
      "related_errors": []
    },
    {
      "id": "operator.invert",
      "title": "operator.invert",
      "kind": "function",
      "summary": {
        "ru": "Побитовая инверсия ~a (эквивалентно −a−1 для int).",
        "en": "Bitwise inversion ~a."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.invert(a)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.invert",
      "version": "",
      "section": "Модуль operator",
      "subcat": "битовые",
      "color_group": "module",
      "aliases": [
        "побитовая инверсия",
        "инвертировать биты числа",
        "перевернуть все биты"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.invert(0))   # → -1",
        "print(operator.invert(5) == ~5)   # → True",
        "print(operator.invert(5) == -5 - 1)   # → True",
        "print(list(map(operator.invert, [1, 2, 3])))   # → [-2, -3, -4]",
        "print(operator.invert(True))   # → -2"
      ],
      "related": [
        "operator.inv",
        "operator.and_",
        "operator.xor",
        "побитовые-операторы"
      ],
      "related_errors": []
    },
    {
      "id": "operator.ior",
      "title": "operator.ior",
      "kind": "function",
      "summary": {
        "ru": "In-place побитовое ИЛИ: a |= b.",
        "en": "In-place bitwise OR: a |= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.ior(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.ior",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "побитовое ИЛИ с присваиванием",
        "объединить множества на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.ior(5, 2))   # → 7",
        "read, write = 4, 2   # типовой сценарий: объединение битовых флагов",
        "print(operator.ior(read, write))   # → 6",
        "flags = {'r', 'w'}",
        "print(operator.ior(flags, {'x'}) is flags)   # → True",
        "print(sorted(flags))   # → ['r', 'w', 'x']",
        "conf = {'debug': False}",
        "print(operator.ior(conf, {'level': 3}))   # → {'debug': False, 'level': 3}"
      ],
      "related": [
        "operator.or_",
        "operator.iand",
        "dict-merge-update"
      ],
      "related_errors": []
    },
    {
      "id": "operator.ipow",
      "title": "operator.ipow",
      "kind": "function",
      "summary": {
        "ru": "In-place возведение в степень: a **= b.",
        "en": "In-place exponentiation: a **= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.ipow(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.ipow",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "возведение в степень с присваиванием",
        "степень на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.ipow(2, 3))   # → 8",
        "print(operator.ipow(2, -1))   # → 0.5",
        "print(operator.ipow(9, 0.5))   # → 3.0",
        "base = 3   # ни один встроенный тип не меняется по **= — присваивайте результат",
        "print(operator.ipow(base, 4), base)   # → 81 3",
        "print(operator.ipow(2, 10, 1000))   # → TypeError"
      ],
      "related": [
        "pow",
        "степень",
        "operator.imul"
      ],
      "related_errors": []
    },
    {
      "id": "operator.irshift",
      "title": "operator.irshift",
      "kind": "function",
      "summary": {
        "ru": "In-place сдвиг вправо: a >>= b.",
        "en": "In-place right shift: a >>= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.irshift(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.irshift",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "сдвиг вправо с присваиванием",
        "битовый сдвиг вправо на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.irshift(16, 2))   # → 4",
        "print(operator.irshift(5, 10))   # → 0",
        "color = 0xFF8800   # достать старший байт цвета",
        "print(operator.irshift(color, 16))   # → 255",
        "print(operator.irshift(-17, 2))   # → -5",
        "print(operator.irshift(1, -1))   # → ValueError"
      ],
      "related": [
        "operator.rshift",
        "operator.ilshift",
        "побитовые-операторы"
      ],
      "related_errors": []
    },
    {
      "id": "operator.is_",
      "title": "operator.is_",
      "kind": "function",
      "summary": {
        "ru": "Проверка тождества: a is b (тот же объект в памяти).",
        "en": "Identity test: a is b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.is_(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.is_",
      "version": "",
      "section": "Модуль operator",
      "subcat": "тождество и логика",
      "color_group": "module",
      "aliases": [
        "сравнение по ссылке",
        "тот же объект в памяти"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "x = []",
        "print(operator.is_(x, x))   # → True",
        "y = []",
        "print(operator.is_(x, y))   # → False",
        "print(operator.eq(x, y))   # → True",
        "values = [None, 0, None]",
        "print([operator.is_(v, None) for v in values])   # → [True, False, True]",
        "print(operator.is_(True, 1))   # → False"
      ],
      "related": [
        "operator.is_not",
        "is-is-not",
        "operator.eq"
      ],
      "related_errors": []
    },
    {
      "id": "operator.is_none",
      "title": "operator.is_none",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a is None — проверка тождества с None в функциональной форме. Появилась в Python 3.14.",
        "en": "Returns a is None — the identity test against None in functional form. Added in Python 3.14."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.is_none(a)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.is_none",
      "version": "3.14",
      "section": "Модуль operator",
      "subcat": "тождество и логика",
      "color_group": "module",
      "aliases": [
        "проверка тождества с пустым значением",
        "предикат проверки на пустое значение"
      ],
      "keywords": [
        "operator.is_none",
        "is_none"
      ],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "is_none = getattr(operator, 'is_none', lambda a: a is None)  # 3.14+ или аналог",
        "print(is_none(None))                              # → True",
        "print(is_none(0))                                 # → False",
        "print([is_none(x) for x in (1, None, '')])        # → [False, True, False]",
        "print(list(filter(is_none, [1, None, 2, None])))  # → [None, None]"
      ],
      "related": [
        "operator.is_",
        "operator.is_not",
        "nonetype",
        "is-is-not"
      ],
      "related_errors": [
        "AttributeError"
      ]
    },
    {
      "id": "operator.is_not",
      "title": "operator.is_not",
      "kind": "function",
      "summary": {
        "ru": "Проверка нетождества: a is not b.",
        "en": "Negated identity test: a is not b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.is_not(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.is_not",
      "version": "",
      "section": "Модуль operator",
      "subcat": "тождество и логика",
      "color_group": "module",
      "aliases": [
        "не тот же объект",
        "проверка нетождественности",
        "разные объекты в памяти"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.is_not([], []))   # → True",
        "a = [1, 2]",
        "print(operator.is_not(a, a))   # → False",
        "print(operator.is_not(a, a[:]))   # → True",
        "print(operator.is_not(None, None))   # → False",
        "print(operator.is_not(a, [1, 2]), a != [1, 2])   # → True False"
      ],
      "related": [
        "operator.is_",
        "is-is-not",
        "operator.ne"
      ],
      "related_errors": []
    },
    {
      "id": "operator.is_not_none",
      "title": "operator.is_not_none",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a is not None — функциональная форма проверки «значение задано»; удобен как предикат для filter(). Появилась в Python 3.14.",
        "en": "Returns a is not None — functional form of the \"value is set\" check; handy as a predicate for filter(). Added in Python 3.14."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.is_not_none(a)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.is_not_none",
      "version": "3.14",
      "section": "Модуль operator",
      "subcat": "тождество и логика",
      "color_group": "module",
      "aliases": [
        "проверка что значение не пустое",
        "отфильтровать пустые значения из списка"
      ],
      "keywords": [
        "operator.is_not_none",
        "is_not_none"
      ],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "f = getattr(operator, 'is_not_none', lambda a: a is not None)  # 3.14+ или аналог",
        "print(f(None))                              # → False",
        "print(f(0))                                 # → True",
        "print(list(filter(f, [1, None, 2, None])))  # → [1, 2]",
        "print([f(x) for x in ('', None, 0)])        # → [True, False, True]"
      ],
      "related": [
        "operator.is_not",
        "operator.is_",
        "nonetype",
        "filter"
      ],
      "related_errors": [
        "AttributeError"
      ]
    },
    {
      "id": "operator.isub",
      "title": "operator.isub",
      "kind": "function",
      "summary": {
        "ru": "In-place вычитание: a −= b.",
        "en": "In-place subtraction: a −= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.isub(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.isub",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "вычитание с присваиванием",
        "уменьшить значение на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.isub(5, 2))   # → 3",
        "print(operator.isub(0.3, 0.1))   # → 0.19999999999999998",
        "tags = {'a', 'b', 'c'}",
        "print(operator.isub(tags, {'b'}) is tags)   # → True",
        "print(sorted(tags))   # → ['a', 'c']",
        "print(operator.isub([1, 2, 3], [2]))   # → TypeError"
      ],
      "related": [
        "operator.sub",
        "operator.iadd",
        "операторы-присваивания"
      ],
      "related_errors": []
    },
    {
      "id": "operator.itemgetter",
      "title": "operator.itemgetter",
      "kind": "function",
      "summary": {
        "ru": "Фабрика: возвращает функцию, извлекающую элемент(ы) по фиксированному индексу/ключу — удобно для key= в sorted/map.",
        "en": "A factory returning a callable that fetches items by fixed index/key."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "f = operator.itemgetter(index)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.itemgetter",
      "version": "",
      "section": "Модуль operator",
      "subcat": "вызов и доступ",
      "color_group": "module",
      "aliases": [
        "извлечь элемент по индексу",
        "ключ сортировки по индексу",
        "сортировка кортежей по второму элементу"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "g = operator.itemgetter(1)",
        "print(g([10, 20, 30]))   # → 20",
        "print(operator.itemgetter(1)([10, 20, 30]) == [10, 20, 30][1])   # → True",
        "print(operator.itemgetter(0, 2)('abcd'))   # → ('a', 'c')",
        "print(operator.itemgetter('b')({'a': 1, 'b': 2}))   # → 2",
        "pairs = [('a', 3), ('b', 1)]",
        "print(sorted(pairs, key=operator.itemgetter(1)))   # → [('b', 1), ('a', 3)]"
      ],
      "related": [
        "operator.attrgetter",
        "sorted-с-key",
        "operator.getitem",
        "operator.methodcaller"
      ],
      "related_errors": [
        "IndexError",
        "KeyError"
      ]
    },
    {
      "id": "operator.itruediv",
      "title": "operator.itruediv",
      "kind": "function",
      "summary": {
        "ru": "In-place истинное деление: a /= b.",
        "en": "In-place true division: a /= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.itruediv(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.itruediv",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "деление с присваиванием",
        "истинное деление на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "from functools import reduce",
        "print(operator.itruediv(7, 2))   # → 3.5",
        "print(operator.itruediv(9, 3))   # → 3.0",
        "print(reduce(operator.itruediv, [100, 2, 5]))   # → 10.0",
        "print(operator.ifloordiv(7, 2))   # → 3",
        "print(operator.itruediv(1, 0))   # → ZeroDivisionError"
      ],
      "related": [
        "operator.truediv",
        "operator.ifloordiv",
        "операторы-присваивания"
      ],
      "related_errors": [
        "ZeroDivisionError"
      ]
    },
    {
      "id": "operator.ixor",
      "title": "operator.ixor",
      "kind": "function",
      "summary": {
        "ru": "In-place исключающее ИЛИ: a ^= b.",
        "en": "In-place bitwise XOR: a ^= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.ixor(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.ixor",
      "version": "",
      "section": "Модуль operator",
      "subcat": "на месте (in-place)",
      "color_group": "module",
      "aliases": [
        "исключающее ИЛИ с присваиванием",
        "побитовое исключающее или на месте"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "from functools import reduce",
        "print(operator.ixor(5, 3))   # → 6",
        "print(reduce(operator.ixor, [4, 7, 4, 7, 9]))   # → 9",
        "flags = 0b1010",
        "print(bin(operator.ixor(flags, 0b0010)))   # → 0b1000",
        "s = {1, 2, 3}",
        "print(sorted(operator.ixor(s, {3, 4})))   # → [1, 2, 4]",
        "print(sorted(s))   # → [1, 2, 4]"
      ],
      "related": [
        "operator.xor",
        "operator.ior",
        "operator.iand"
      ],
      "related_errors": []
    },
    {
      "id": "operator.le",
      "title": "operator.le",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a <= b.",
        "en": "Return a <= b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.le(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.le",
      "version": "",
      "section": "Модуль operator",
      "subcat": "сравнение",
      "color_group": "module",
      "aliases": [
        "меньше или равно",
        "не больше чем"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.le(2, 2))   # → True",
        "print(operator.le(3, 2))   # → False",
        "scores = [5, 10, 3]",
        "print([s for s in scores if operator.le(s, 5)])   # → [5, 3]",
        "print(operator.le({1}, {1, 2}))   # → True",
        "print(operator.le({1, 2}, {2, 3}))   # → False"
      ],
      "related": [
        "operator.ge",
        "operator.lt",
        "operator.gt"
      ],
      "related_errors": []
    },
    {
      "id": "operator.length_hint",
      "title": "operator.length_hint",
      "kind": "function",
      "summary": {
        "ru": "Оценка длины объекта: точная (len) или предполагаемая через __length_hint__ (для итераторов); второй аргумент — значение по умолчанию.",
        "en": "Estimate an object's length (exact or via __length_hint__)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.length_hint(obj, default=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.length_hint",
      "version": "3.4",
      "section": "Модуль operator",
      "subcat": "последовательности",
      "color_group": "module",
      "aliases": [
        "оценка длины итератора",
        "предполагаемое число элементов"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.length_hint([1, 2, 3]))   # → 3",
        "it = iter([1, 2, 3])",
        "print(operator.length_hint(it))  # → 3",
        "print(next(it))  # → 1",
        "print(operator.length_hint(it))  # → 2",
        "print(operator.length_hint((x for x in range(5)), 10))  # → 10"
      ],
      "related": [
        "len",
        "__len__-__getitem__-__setitem__-__contai",
        "iter-next"
      ],
      "related_errors": []
    },
    {
      "id": "operator.lshift",
      "title": "operator.lshift",
      "kind": "function",
      "summary": {
        "ru": "Сдвиг влево: a << b (умножение на 2**b).",
        "en": "Left shift: a << b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.lshift(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.lshift",
      "version": "",
      "section": "Модуль operator",
      "subcat": "битовые",
      "color_group": "module",
      "aliases": [
        "сдвиг битов влево",
        "умножить на степень двойки"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.lshift(1, 3))   # → 8",
        "print(operator.lshift(3, 2) == 3 << 2)   # → True",
        "print(operator.lshift(5, 1) == 5 * 2)   # → True",
        "print(list(map(operator.lshift, [1, 2, 3], [4, 1, 0])))   # → [16, 4, 3]",
        "print(operator.lshift(1, -1))   # → ValueError"
      ],
      "related": [
        "operator.rshift",
        "operator.ilshift",
        "побитовые-операторы"
      ],
      "related_errors": []
    },
    {
      "id": "operator.lt",
      "title": "operator.lt",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a < b.",
        "en": "Return a < b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.lt(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.lt",
      "version": "",
      "section": "Модуль operator",
      "subcat": "сравнение",
      "color_group": "module",
      "aliases": [
        "строго меньше",
        "меньше чем"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.lt(1, 2))   # → True",
        "print(operator.lt(2, 2))   # → False",
        "print(operator.lt((1, 2), (1, 3)))   # → True",
        "nums = [4, 1, 7]",
        "print([n for n in nums if operator.lt(n, 5)])   # → [4, 1]",
        "print(operator.lt(None, 1))   # → TypeError"
      ],
      "related": [
        "operator.gt",
        "operator.le",
        "functools.total_ordering"
      ],
      "related_errors": []
    },
    {
      "id": "operator.matmul",
      "title": "operator.matmul",
      "kind": "function",
      "summary": {
        "ru": "Матричное умножение: a @ b (вызывает __matmul__; используется, например, в NumPy).",
        "en": "Matrix multiplication: a @ b (invokes __matmul__)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.matmul(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.matmul",
      "version": "",
      "section": "Модуль operator",
      "subcat": "матричное умножение",
      "color_group": "module",
      "aliases": [
        "умножение матриц",
        "матричное произведение"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "class M:",
        "    def __matmul__(self, other): return 'mm'",
        "print(operator.matmul(M(), M()))   # → mm"
      ],
      "related": [
        "operator.mul",
        "operator.imatmul",
        "__add__-__mul__-__eq__-__lt__-и-оператор"
      ],
      "related_errors": []
    },
    {
      "id": "operator.methodcaller",
      "title": "operator.methodcaller",
      "kind": "function",
      "summary": {
        "ru": "Фабрика: возвращает функцию, вызывающую у объекта метод с фиксированным именем и аргументами.",
        "en": "A factory returning a callable that calls a fixed method on its argument."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "f = operator.methodcaller('method', *args)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.methodcaller",
      "version": "",
      "section": "Модуль operator",
      "subcat": "вызов и доступ",
      "color_group": "module",
      "aliases": [
        "вызвать метод по имени",
        "ключ сортировки через метод объекта"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "g = operator.methodcaller('upper')",
        "print(g('abc'))   # → ABC",
        "print(operator.methodcaller('upper')('abc') == 'abc'.upper())   # → True",
        "print(operator.methodcaller('replace', 'a', 'z')('banana'))   # → bznznz",
        "print(list(map(operator.methodcaller('strip'), [' a ', ' b'])))   # → ['a', 'b']",
        "print(operator.methodcaller('upper')(5))   # → AttributeError"
      ],
      "related": [
        "operator.attrgetter",
        "operator.itemgetter",
        "operator.call",
        "map"
      ],
      "related_errors": [
        "AttributeError"
      ]
    },
    {
      "id": "operator.mod",
      "title": "operator.mod",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a % b — остаток от деления (эквивалент `%`).",
        "en": "Return a % b — the remainder (the functional form of `%`)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.mod(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.mod",
      "version": "",
      "section": "Модуль operator",
      "subcat": "арифметика",
      "color_group": "module",
      "aliases": [
        "остаток от деления как функция",
        "взять остаток функцией"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.mod(7, 3))   # → 1",
        "print(operator.mod(7, 3) == 7 % 3)   # → True",
        "print(operator.mod(-7, 3))   # → 2",
        "print(list(map(operator.mod, [10, 11, 12], [3, 3, 3])))   # → [1, 2, 0]",
        "print(operator.mod('%s-%s', ('a', 'b')))   # → a-b"
      ],
      "related": [
        "operator.floordiv",
        "divmod",
        "остаток",
        "operator.truediv"
      ],
      "related_errors": [
        "ZeroDivisionError"
      ]
    },
    {
      "id": "operator.mul",
      "title": "operator.mul",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a * b (функциональный эквивалент `*`).",
        "en": "Return a * b (the functional form of `*`)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.mul(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.mul",
      "version": "",
      "section": "Модуль operator",
      "subcat": "арифметика",
      "color_group": "module",
      "aliases": [
        "умножение как функция",
        "функция вместо оператора умножения"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "import functools",
        "print(operator.mul(3, 4))   # → 12",
        "print(operator.mul(3, 4) == 3 * 4)   # → True",
        "print(functools.reduce(operator.mul, range(1, 6)))   # → 120",
        "print(operator.mul('ab', 3))   # → ababab",
        "print(list(map(operator.mul, [1, 2, 3], [10, 10, 10])))   # → [10, 20, 30]"
      ],
      "related": [
        "operator.add",
        "operator.truediv",
        "умножение",
        "operator.matmul"
      ],
      "related_errors": []
    },
    {
      "id": "operator.ne",
      "title": "operator.ne",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a != b.",
        "en": "Return a != b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.ne(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.ne",
      "version": "",
      "section": "Модуль operator",
      "subcat": "сравнение",
      "color_group": "module",
      "aliases": [
        "не равно",
        "проверка на неравенство"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.ne(1, 2))   # → True",
        "print(operator.ne('a', 'a'))   # → False",
        "print(operator.ne(1, 1.0))   # → False",
        "values = [3, 0, 3, 7]",
        "print([v for v in values if operator.ne(v, 3)])   # → [0, 7]",
        "print(operator.ne(float('nan'), float('nan')))   # → True"
      ],
      "related": [
        "operator.eq",
        "operator.is_not",
        "операторы-сравнения"
      ],
      "related_errors": []
    },
    {
      "id": "operator.neg",
      "title": "operator.neg",
      "kind": "function",
      "summary": {
        "ru": "Возвращает −a (унарный минус).",
        "en": "Return −a (unary negation)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.neg(a)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.neg",
      "version": "",
      "section": "Модуль operator",
      "subcat": "арифметика",
      "color_group": "module",
      "aliases": [
        "унарный минус как функция",
        "сменить знак числа на противоположный"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.neg(3))   # → -3",
        "print(operator.neg(3) == -3)   # → True",
        "print(list(map(operator.neg, [1, -2, 3])))   # → [-1, 2, -3]",
        "print(sorted([3, 1, 2], key=operator.neg))   # → [3, 2, 1]",
        "print(operator.neg(True))   # → -1"
      ],
      "related": [
        "operator.pos",
        "operator.sub",
        "abs"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.not_",
      "title": "operator.not_",
      "kind": "function",
      "summary": {
        "ru": "Логическое НЕ: not a (возвращает bool).",
        "en": "Logical negation: not a."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.not_(a)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.not_",
      "version": "",
      "section": "Модуль operator",
      "subcat": "тождество и логика",
      "color_group": "module",
      "aliases": [
        "логическое НЕ",
        "логическое отрицание",
        "инвертировать условие"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.not_(False))   # → True",
        "print(operator.not_([]))   # → True",
        "print(operator.not_('текст'))   # → False",
        "print(list(map(operator.not_, [0, 1, '', 'ok'])))   # → [True, False, True, False]",
        "print(operator.not_(5), operator.inv(5))   # → False -6"
      ],
      "related": [
        "operator.truth",
        "and-or-not",
        "bool"
      ],
      "related_errors": []
    },
    {
      "id": "operator.or_",
      "title": "operator.or_",
      "kind": "function",
      "summary": {
        "ru": "Побитовое ИЛИ: a | b (подчёркивание — чтобы не совпасть с `or`).",
        "en": "Bitwise OR: a | b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.or_(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.or_",
      "version": "",
      "section": "Модуль operator",
      "subcat": "битовые",
      "color_group": "module",
      "aliases": [
        "побитовое ИЛИ",
        "объединить биты маской",
        "установить бит"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import functools",
        "import operator",
        "print(operator.or_(5, 2))   # → 7",
        "print(operator.or_(5, 2) == 5 | 2)   # → True",
        "print(functools.reduce(operator.or_, [1, 2, 4, 8]))   # → 15",
        "print(operator.or_({1, 2}, {2, 3}) == {1, 2, 3})   # → True",
        "print(operator.or_(2, 4) == (2 or 4))   # → False"
      ],
      "related": [
        "operator.and_",
        "operator.xor",
        "operator.ior",
        "побитовые-операторы"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.pos",
      "title": "operator.pos",
      "kind": "function",
      "summary": {
        "ru": "Возвращает +a (унарный плюс; для чисел — само значение, но может вызывать __pos__).",
        "en": "Return +a (unary plus)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.pos(a)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.pos",
      "version": "",
      "section": "Модуль operator",
      "subcat": "арифметика",
      "color_group": "module",
      "aliases": [
        "унарный плюс",
        "унарный плюс как функция"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.pos(-3))   # → -3",
        "print(operator.pos(-3) == +(-3))   # → True",
        "print(operator.pos(True))   # → 1",
        "print(list(map(operator.pos, [1, -2, 3])))   # → [1, -2, 3]",
        "print(operator.pos(\"a\"))   # → TypeError"
      ],
      "related": [
        "operator.neg",
        "operator.invert",
        "abs"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.rshift",
      "title": "operator.rshift",
      "kind": "function",
      "summary": {
        "ru": "Сдвиг вправо: a >> b (целочисленное деление на 2**b).",
        "en": "Right shift: a >> b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.rshift(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.rshift",
      "version": "",
      "section": "Модуль operator",
      "subcat": "битовые",
      "color_group": "module",
      "aliases": [
        "сдвиг битов вправо",
        "поделить на степень двойки"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.rshift(16, 2))   # → 4",
        "print(operator.rshift(16, 2) == 16 >> 2)   # → True",
        "print(list(map(operator.rshift, [8, 16, 32], [1, 2, 3])))   # → [4, 4, 4]",
        "print(operator.rshift(-9, 1) == -9 // 2)   # → True",
        "print(operator.rshift(1, -1))   # → ValueError"
      ],
      "related": [
        "operator.lshift",
        "operator.irshift",
        "побитовые-операторы"
      ],
      "related_errors": [
        "TypeError",
        "ValueError"
      ]
    },
    {
      "id": "operator.setitem",
      "title": "operator.setitem",
      "kind": "function",
      "summary": {
        "ru": "Присваивание по ключу/индексу: a[b] = c (мутирует объект).",
        "en": "Item assignment: a[b] = c."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.setitem(a, b, c)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.setitem",
      "version": "",
      "section": "Модуль operator",
      "subcat": "последовательности",
      "color_group": "module",
      "aliases": [
        "записать значение по индексу",
        "присвоить элемент по ключу"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "l = [0, 0]",
        "operator.setitem(l, 0, 9)",
        "print(l)   # → [9, 0]"
      ],
      "related": [
        "operator.getitem",
        "operator.delitem",
        "добавление-изменение-d-key-val"
      ],
      "related_errors": [
        "IndexError",
        "TypeError"
      ]
    },
    {
      "id": "operator.sub",
      "title": "operator.sub",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a − b (функциональный эквивалент `-`).",
        "en": "Return a − b (the functional form of `-`)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.sub(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.sub",
      "version": "",
      "section": "Модуль operator",
      "subcat": "арифметика",
      "color_group": "module",
      "aliases": [
        "вычитание как функция",
        "функция вместо оператора минус"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "import functools",
        "print(operator.sub(5, 2))   # → 3",
        "print(operator.sub(5, 2) == 5 - 2)   # → True",
        "print(list(map(operator.sub, [10, 20, 30], [1, 2, 3])))   # → [9, 18, 27]",
        "print(functools.reduce(operator.sub, [100, 20, 3]))   # → 77",
        "print(operator.sub(\"ab\", \"a\"))   # → TypeError"
      ],
      "related": [
        "operator.add",
        "operator.isub",
        "вычитание",
        "operator.neg"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "operator.truediv",
      "title": "operator.truediv",
      "kind": "function",
      "summary": {
        "ru": "Возвращает a / b — истинное деление с плавающей точкой (эквивалент `/`).",
        "en": "Return a / b — true division (the functional form of `/`)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.truediv(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.truediv",
      "version": "",
      "section": "Модуль operator",
      "subcat": "арифметика",
      "color_group": "module",
      "aliases": [
        "истинное деление",
        "деление как функция",
        "деление с дробным результатом"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.truediv(7, 2))   # → 3.5",
        "print(operator.truediv(7, 2) == 7 / 2)   # → True",
        "print(operator.truediv(6, 3))   # → 2.0",
        "print(list(map(operator.truediv, [10, 9], [4, 3])))   # → [2.5, 3.0]",
        "print(operator.truediv(1, 0))   # → ZeroDivisionError"
      ],
      "related": [
        "operator.floordiv",
        "operator.mod",
        "деление",
        "divmod"
      ],
      "related_errors": [
        "ZeroDivisionError",
        "TypeError"
      ]
    },
    {
      "id": "operator.truth",
      "title": "operator.truth",
      "kind": "function",
      "summary": {
        "ru": "Возвращает истинностное значение объекта как bool (эквивалент bool(a)).",
        "en": "Return the truth value of a as a bool."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.truth(a)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.truth",
      "version": "",
      "section": "Модуль operator",
      "subcat": "тождество и логика",
      "color_group": "module",
      "aliases": [
        "истинность объекта",
        "привести к логическому значению"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import operator",
        "print(operator.truth([1]))   # → True",
        "print(operator.truth(0), operator.truth(''))   # → False False",
        "print(operator.truth([0]))   # → True",
        "print(list(filter(operator.truth, [0, 1, '', 'ok', None, []])))   # → [1, 'ok']",
        "print(operator.truth(5) == bool(5), operator.truth is bool)   # → True False"
      ],
      "related": [
        "operator.not_",
        "bool",
        "and-or-not"
      ],
      "related_errors": []
    },
    {
      "id": "operator.xor",
      "title": "operator.xor",
      "kind": "function",
      "summary": {
        "ru": "Побитовое исключающее ИЛИ: a ^ b.",
        "en": "Bitwise XOR: a ^ b."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "operator.xor(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/operator.html#operator.xor",
      "version": "",
      "section": "Модуль operator",
      "subcat": "битовые",
      "color_group": "module",
      "aliases": [
        "исключающее ИЛИ",
        "сложение по модулю два",
        "переключить биты маской"
      ],
      "keywords": [],
      "tags": [
        "operator"
      ],
      "examples": [
        "import functools",
        "import operator",
        "print(operator.xor(5, 3))   # → 6",
        "print(operator.xor(5, 3) == 5 ^ 3)   # → True",
        "print(operator.xor(operator.xor(5, 3), 3))   # → 5",
        "print(functools.reduce(operator.xor, [4, 7, 4, 9, 7]))   # → 9",
        "print(operator.xor({1, 2}, {2, 3}) == {1, 3})   # → True"
      ],
      "related": [
        "operator.or_",
        "operator.and_",
        "operator.ixor",
        "побитовые-операторы"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "os-getenv",
      "title": "os.getenv()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает значение переменной окружения по имени. Если переменная не задана, возвращает default (по умолчанию None). Безопаснее, чем os.environ[key].",
        "en": "Returns the value of an environment variable by name. If the variable is not set it returns default (None by default). Safer than os.environ[key]."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getenv(key, default=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getenv",
      "version": "",
      "section": "Модуль os",
      "subcat": "переменные окружения",
      "color_group": "module",
      "aliases": [],
      "keywords": [
        "os.getenv"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import os",
        "db_host = os.getenv('DB_HOST', 'localhost')",
        "api_key  = os.getenv('API_KEY')  # None если не задана",
        "if api_key is None:",
        "    raise EnvironmentError('API_KEY не задана')"
      ],
      "related": [
        "os.environ",
        "dict.get",
        "os.unsetenv"
      ],
      "related_errors": []
    },
    {
      "id": "os-scandir",
      "title": "os.scandir()",
      "kind": "function",
      "summary": {
        "ru": "Эффективный итератор по содержимому директории. Возвращает объекты DirEntry с именем, путём и кешированными атрибутами — быстрее os.listdir() + os.stat().",
        "en": "An efficient iterator over the contents of a directory. It yields DirEntry objects with the name, the path and cached attributes — faster than os.listdir() + os.stat()."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "with os.scandir(path='.') as it:\n    for entry in it: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.scandir",
      "version": "",
      "section": "Модуль os",
      "subcat": "файловая система",
      "color_group": "module",
      "aliases": [],
      "keywords": [
        "os.scandir"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import os",
        "with os.scandir('/tmp') as it:",
        "for entry in it:",
        "if entry.is_file():",
        "print(entry.name, entry.stat().st_size)",
        "elif entry.is_dir():",
        "print(entry.name + '/')"
      ],
      "related": [
        "os.listdir",
        "os.DirEntry",
        "os.stat",
        ".iterdir"
      ],
      "related_errors": [
        "FileNotFoundError",
        "NotADirectoryError",
        "PermissionError"
      ]
    },
    {
      "id": "os.DirEntry",
      "title": "os.DirEntry",
      "kind": "term",
      "summary": {
        "ru": "Тип элемента, возвращаемого os.scandir(): даёт name, path и быстрые is_file()/is_dir()/stat() с кешем.",
        "en": "The item type yielded by os.scandir(): name, path, and cached is_file()/is_dir()/stat()."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "entry.name  # os.DirEntry",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.DirEntry",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловая система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "d = os.path.dirname(os.__file__)",
        "it = os.scandir(d)",
        "print(isinstance(next(it), os.DirEntry))   # → True",
        "it.close()"
      ],
      "related": [
        "os.scandir",
        "os.stat_result",
        "os.listdir"
      ],
      "related_errors": []
    },
    {
      "id": "os.WCOREDUMP",
      "title": "os.WCOREDUMP",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, оставил ли завершённый процесс дамп памяти (core). Доступно на Unix.",
        "en": "Test whether a terminated child produced a core dump. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.WCOREDUMP(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.WCOREDUMP",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — разбор статуса (W*)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pid = os.fork()  # → ? только Unix: 0 в дочернем процессе, pid потомка в родителе",
        "if pid == 0: os.abort()  # потомок аварийно падает по SIGABRT",
        "_, status = os.waitpid(pid, 0)  # родитель забирает статус завершения потомка",
        "print(os.WIFSIGNALED(status))  # → True — потомок убит сигналом, а не вышел сам",
        "print(os.WCOREDUMP(status))  # → True, если система разрешила записать core (ulimit -c unlimited), иначе False",
        "print(os.WCOREDUMP(0))  # → False — после штатного завершения дампа нет"
      ],
      "related": [
        "os.WIFSIGNALED",
        "os.WTERMSIG",
        "os.waitpid"
      ],
      "related_errors": []
    },
    {
      "id": "os.WEXITSTATUS",
      "title": "os.WEXITSTATUS",
      "kind": "function",
      "summary": {
        "ru": "Извлекает из статуса код возврата нормально завершившегося процесса. Доступно на Unix.",
        "en": "Return the exit code of a normally-terminated child. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.WEXITSTATUS(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.WEXITSTATUS",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — разбор статуса (W*)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "status = os.system('exit 7')  # → ? только Unix: статус в формате wait(), здесь 1792 (7 << 8)",
        "print(os.WEXITSTATUS(status))  # → 7 — код выхода команды",
        "print(os.WIFEXITED(status))  # → True — лишь при этом WEXITSTATUS осмыслен",
        "killed = os.system('kill -9 $$')  # → ? только Unix: оболочка убивает сама себя сигналом SIGKILL",
        "print(os.WIFEXITED(killed), os.WTERMSIG(killed))  # → ? только Unix: False 9 — здесь нужен WTERMSIG, WEXITSTATUS вернёт бессмыслицу",
        "print(os.waitstatus_to_exitcode(status))  # → 7 — современная однострочная замена паре WIFEXITED + WEXITSTATUS"
      ],
      "related": [
        "os.WIFEXITED",
        "os.waitstatus_to_exitcode",
        "os.waitpid"
      ],
      "related_errors": []
    },
    {
      "id": "os.WIFCONTINUED",
      "title": "os.WIFCONTINUED",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, был ли остановленный процесс возобновлён (SIGCONT). Доступно на Unix.",
        "en": "Test whether a stopped child was resumed (SIGCONT). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.WIFCONTINUED(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.WIFCONTINUED",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — разбор статуса (W*)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os, signal",
        "pid = os.fork()  # → ? только Unix: 0 в дочернем процессе, pid потомка в родителе",
        "if pid == 0: os.pause()  # потомок засыпает и ждёт сигналов",
        "os.kill(pid, signal.SIGSTOP)  # родитель останавливает потомка",
        "os.kill(pid, signal.SIGCONT)  # и сразу возобновляет его",
        "_, status = os.waitpid(pid, os.WCONTINUED)  # без флага WCONTINUED событие возобновления не придёт",
        "print(os.WIFCONTINUED(status))  # → True — потомок был возобновлён сигналом SIGCONT",
        "print(os.WIFCONTINUED(0))  # → False — статус штатного завершения возобновлением не считается"
      ],
      "related": [
        "os.WIFSTOPPED",
        "os.WSTOPSIG",
        "os.waitpid"
      ],
      "related_errors": []
    },
    {
      "id": "os.WIFEXITED",
      "title": "os.WIFEXITED",
      "kind": "function",
      "summary": {
        "ru": "Проверяет по статусу waitpid, завершился ли дочерний процесс нормально (через exit). Доступно на Unix.",
        "en": "Test whether a child exited normally (via exit). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.WIFEXITED(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.WIFEXITED",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — разбор статуса (W*)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pid = os.fork()  # → ? только Unix: 0 в дочернем процессе, pid потомка в родителе",
        "if pid == 0: os._exit(3)  # потомок завершается штатно с кодом 3",
        "_, status = os.waitpid(pid, 0)  # родитель дожидается потомка и получает его статус",
        "print(os.WIFEXITED(status))  # → True — потомок вышел сам, через exit",
        "print(os.WEXITSTATUS(status))  # → 3 — код возврата, читать его можно только при WIFEXITED",
        "print(os.WIFEXITED(os.system('kill -9 $$')))  # → False — процесс убит сигналом, а не завершился сам"
      ],
      "related": [
        "os.WEXITSTATUS",
        "os.WIFSIGNALED",
        "os.waitstatus_to_exitcode",
        "os.waitpid"
      ],
      "related_errors": []
    },
    {
      "id": "os.WIFSIGNALED",
      "title": "os.WIFSIGNALED",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, был ли процесс завершён сигналом. Доступно на Unix.",
        "en": "Test whether a child was terminated by a signal. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.WIFSIGNALED(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.WIFSIGNALED",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — разбор статуса (W*)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.WIFSIGNALED(9))   # → True — младшие 7 бит статуса хранят номер сигнала (9 = SIGKILL)",
        "print(os.WIFSIGNALED(0))   # → False — статус 0 означает нормальный выход с кодом 0",
        "pid, status = os.wait()   # → ? только Unix: дождаться любого дочернего процесса и получить его статус",
        "print(os.WTERMSIG(status) if os.WIFSIGNALED(status) else os.WEXITSTATUS(status))   # → ? только Unix: типовая развилка — номер убившего сигнала или код выхода",
        "print(os.WIFSIGNALED(0x137f))   # → False — процесс не завершён, а лишь остановлен; это случай WIFSTOPPED"
      ],
      "related": [
        "os.WTERMSIG",
        "os.WIFEXITED",
        "os.WCOREDUMP"
      ],
      "related_errors": []
    },
    {
      "id": "os.WIFSTOPPED",
      "title": "os.WIFSTOPPED",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, был ли процесс остановлен (не завершён). Доступно на Unix.",
        "en": "Test whether a child was stopped (not terminated). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.WIFSTOPPED(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.WIFSTOPPED",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — разбор статуса (W*)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.WIFSTOPPED(0x137f))   # → True — младший байт статуса равен 0x7f, это признак остановки",
        "print(os.WIFSTOPPED(9))   # → False — такой статус означает завершение сигналом, а не остановку",
        "pid, status = os.waitpid(-1, os.WUNTRACED)   # → ? только Unix: без флага WUNTRACED об остановке ребёнка узнать нельзя",
        "print(os.WIFSTOPPED(status))   # → True, если ребёнок остановлен (например по SIGSTOP), а не завершён",
        "print(os.WSTOPSIG(status) if os.WIFSTOPPED(status) else None)   # → ? только Unix: номер остановившего сигнала имеет смысл только при истинном WIFSTOPPED"
      ],
      "related": [
        "os.WSTOPSIG",
        "os.WIFCONTINUED",
        "os.waitpid"
      ],
      "related_errors": []
    },
    {
      "id": "os.WSTOPSIG",
      "title": "os.WSTOPSIG",
      "kind": "function",
      "summary": {
        "ru": "Извлекает номер сигнала, остановившего процесс. Доступно на Unix.",
        "en": "Return the signal number that stopped a child. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.WSTOPSIG(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.WSTOPSIG",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — разбор статуса (W*)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "status = 0x137f   # статус, каким его вернул os.waitpid() для процесса, остановленного сигналом SIGSTOP",
        "print(os.WSTOPSIG(status))   # → 19 — номер SIGSTOP на Linux, он лежит в старшем байте статуса",
        "print(os.WSTOPSIG(0x057f))   # → 5 — SIGTRAP, так процесс останавливает отладчик",
        "pid, status = os.waitpid(-1, os.WUNTRACED)   # → ? только Unix: дождаться ребёнка, в том числе остановленного, а не только завершённого",
        "print(os.WSTOPSIG(status) if os.WIFSTOPPED(status) else 0)   # → ? только Unix: на не-остановленном статусе результат WSTOPSIG бессмыслен, поэтому проверка WIFSTOPPED обязательна"
      ],
      "related": [
        "os.WIFSTOPPED",
        "os.WIFCONTINUED",
        "os.WTERMSIG"
      ],
      "related_errors": []
    },
    {
      "id": "os.WTERMSIG",
      "title": "os.WTERMSIG",
      "kind": "function",
      "summary": {
        "ru": "Извлекает номер сигнала, завершившего процесс. Доступно на Unix.",
        "en": "Return the signal number that terminated a child. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.WTERMSIG(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.WTERMSIG",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — разбор статуса (W*)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.WTERMSIG(9))   # → 9 — статус процесса, убитого сигналом SIGKILL",
        "print(os.WTERMSIG(15))   # → 15 — SIGTERM, обычное завершение по kill без -9",
        "pid, status = os.wait()   # → ? только Unix: дождаться дочернего процесса и получить его статус",
        "print(os.WTERMSIG(status) if os.WIFSIGNALED(status) else 0)   # → ? только Unix: номер сигнала, убившего ребёнка, или 0, если он завершился сам",
        "print(os.WTERMSIG(256))   # → 0 — сигнала не было, 256 это выход с кодом 1; сначала проверяют WIFSIGNALED"
      ],
      "related": [
        "os.WIFSIGNALED",
        "os.WCOREDUMP",
        "os.WSTOPSIG"
      ],
      "related_errors": []
    },
    {
      "id": "os._exit",
      "title": "os._exit",
      "kind": "function",
      "summary": {
        "ru": "Немедленно завершает процесс с кодом n, минуя финализаторы Python (для дочернего после fork).",
        "en": "Exit the process immediately with status n, skipping Python cleanup."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os._exit(n)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os._exit",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — аварийное завершение",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getpid())   # → ? идентификатор текущего процесса, например 12345",
        "pid = os.fork()   # → ? только Unix: 0 в дочернем процессе, pid ребёнка в родительском",
        "if pid == 0: os._exit(3)   # → ? дочерний процесс завершается сразу с кодом 3, минуя atexit, finally и сброс буферов",
        "print(os.waitstatus_to_exitcode(os.waitpid(pid, 0)[1]))   # → ? только Unix: 3 — родитель забирает код выхода ребёнка"
      ],
      "related": [
        "sys.exit",
        "os.abort",
        "os.fork"
      ],
      "related_errors": []
    },
    {
      "id": "os.abort",
      "title": "os.abort",
      "kind": "function",
      "summary": {
        "ru": "Немедленно завершает процесс сигналом SIGABRT (без очистки/финализаторов).",
        "en": "Terminate the process immediately with SIGABRT (no cleanup)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.abort()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.abort",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — аварийное завершение",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pid = os.fork()   # → ? только Unix: 0 в дочернем процессе, pid ребёнка в родительском",
        "if pid == 0: os.abort()   # → ? дочерний процесс мгновенно убит сигналом SIGABRT: finally и atexit не выполняются",
        "print(os.waitstatus_to_exitcode(os.waitpid(pid, 0)[1]))   # → ? только Unix: -6, процесс завершён сигналом SIGABRT",
        "print(os.abort(0))   # → TypeError"
      ],
      "related": [
        "os._exit",
        "sys.exit",
        "os.kill"
      ],
      "related_errors": []
    },
    {
      "id": "os.access",
      "title": "os.access",
      "kind": "function",
      "summary": {
        "ru": "Проверяет права доступа к пути (R_OK/W_OK/X_OK/F_OK) для реального uid/gid.",
        "en": "Check access permissions for a path (R_OK/W_OK/X_OK/F_OK)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.access(path, mode)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.access",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловая система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.access(os.__file__, os.R_OK))   # → True",
        "print(os.access('нет-такого-файла.txt', os.F_OK))   # → False — F_OK это просто проверка существования",
        "print(os.access(os.__file__, os.W_OK))   # → зависит от прав установки: True, если файл стандартной библиотеки доступен на запись",
        "lib_dir = os.path.dirname(os.__file__)",
        "print(os.access(lib_dir, os.R_OK | os.X_OK))   # → True — каталог можно читать и входить в него, режимы объединяются через |",
        "print(os.access('нет-такого-файла.txt', os.R_OK))   # → False — access не бросает исключение, в отличие от os.stat"
      ],
      "related": [
        "os.path.exists",
        "os.stat",
        "os.chmod"
      ],
      "related_errors": []
    },
    {
      "id": "os.chdir",
      "title": "os.chdir",
      "kind": "function",
      "summary": {
        "ru": "Меняет текущий рабочий каталог процесса.",
        "en": "Change the process's current working directory."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.chdir(path) -> None",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.chdir",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — каталоги",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "cwd = os.getcwd()",
        "os.chdir(cwd)",
        "print(os.getcwd() == cwd)   # → True",
        "start = os.getcwd()",
        "os.chdir('/tmp')",
        "print(os.getcwd())  # → /tmp",
        "os.chdir(start)  # вернуться в исходный каталог"
      ],
      "related": [
        "os.getcwd"
      ],
      "related_errors": [
        "FileNotFoundError",
        "NotADirectoryError",
        "PermissionError"
      ]
    },
    {
      "id": "os.chmod",
      "title": "os.chmod",
      "kind": "function",
      "summary": {
        "ru": "Меняет права доступа к файлу (режим). На Windows поддерживается лишь бит «только чтение».",
        "en": "Change a file's permission bits (mode)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.chmod(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.chmod",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — права и владелец",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "path = 'demo_perms.txt'",
        "open(path, 'w').close()  # → создаём временный файл для опытов",
        "os.chmod(path, 0o644)  # → владелец читает и пишет, остальные только читают",
        "print(oct(os.stat(path).st_mode & 0o777))  # → на Unix 0o644; на Windows 0o666 (биты группы и прочих не поддерживаются)",
        "os.chmod(path, 0o444)  # → снимаем право записи: на Windows это единственный работающий эффект — атрибут «только чтение»; вернуть запись можно тем же os.chmod(path, 0o644)",
        "os.chmod('нет-такого-файла', 0o644)  # → FileNotFoundError"
      ],
      "related": [
        "os.fchmod",
        "os.chown",
        "os.access",
        "os.umask"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.chown",
      "title": "os.chown",
      "kind": "function",
      "summary": {
        "ru": "Меняет владельца (uid) и группу (gid) файла. Доступно на Unix.",
        "en": "Change a file's owner (uid) and group (gid). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.chown(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.chown",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — права и владелец",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "path = 'demo_owner.txt'",
        "open(path, 'w').close()  # → ? создаём временный файл для опытов",
        "os.chown(path, os.getuid(), os.getgid())  # → ? только Unix: владельцем становится текущий пользователь и его основная группа",
        "os.chown(path, -1, os.getgid())  # → ? только Unix: -1 означает «не менять», поэтому правится лишь группа",
        "print(os.stat(path).st_uid == os.getuid())  # → ? True: st_uid показывает владельца, установленного выше (на Unix)",
        "os.chown(path, 0, 0)  # → ? только Unix: отдать файл root удастся лишь из-под суперпользователя, иначе PermissionError"
      ],
      "related": [
        "os.chmod",
        "os.fchown",
        "os.lchown",
        "os.getuid"
      ],
      "related_errors": [
        "PermissionError",
        "FileNotFoundError"
      ]
    },
    {
      "id": "os.chroot",
      "title": "os.chroot",
      "kind": "function",
      "summary": {
        "ru": "Меняет корневой каталог процесса (требует привилегий). Доступно на Unix.",
        "en": "Change the process's root directory (needs privilege). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.chroot(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.chroot",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — каталоги",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "os.chroot('/srv/jail')  # → ? только Unix: корнем процесса становится /srv/jail, функция ничего не возвращает",
        "os.chdir('/')  # → ? только Unix: обязательный шаг после chroot — иначе рабочий каталог остаётся вне «клетки»",
        "print(os.listdir('/'))  # → ? только Unix: видно уже содержимое /srv/jail, например ['bin', 'etc', 'lib']",
        "os.chroot('/srv/нет-такого')  # → FileNotFoundError: каталог нового корня должен существовать",
        "os.chroot('/srv/jail')  # → PermissionError, если процесс запущен не от root"
      ],
      "related": [
        "os.chdir",
        "os.unshare",
        "os.setuid"
      ],
      "related_errors": [
        "PermissionError",
        "FileNotFoundError"
      ]
    },
    {
      "id": "os.close",
      "title": "os.close",
      "kind": "function",
      "summary": {
        "ru": "Закрывает файловый дескриптор.",
        "en": "Close a file descriptor."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.close(fd)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.close",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()",
        "print(os.close(r) is None)   # → True",
        "os.close(w)"
      ],
      "related": [
        "os.closerange",
        "os.fdopen",
        "os.dup"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.closerange",
      "title": "os.closerange",
      "kind": "function",
      "summary": {
        "ru": "Закрывает все дескрипторы в диапазоне [fd_low, fd_high), игнорируя ошибки.",
        "en": "Close all file descriptors in the range [fd_low, fd_high), ignoring errors."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.closerange(fd_low, fd_high)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.closerange",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()   # → два дескриптора одного канала, например 3 и 4",
        "os.closerange(r, w + 1)   # → закрывает и r, и w: верхняя граница не входит в диапазон; тем же приёмом закрывают унаследованные fd перед exec",
        "os.close(r)   # → OSError: [Errno 9] Bad file descriptor — дескриптор уже закрыт",
        "os.closerange(r, w + 1)   # → повторный вызов не падает: ошибки закрытия молча игнорируются",
        "os.closerange(w, w)   # → пустой диапазон [w, w): не закрывает ничего"
      ],
      "related": [
        "os.close",
        "os.set_inheritable"
      ],
      "related_errors": []
    },
    {
      "id": "os.confstr",
      "title": "os.confstr",
      "kind": "function",
      "summary": {
        "ru": "Возвращает строковое системное конфигурационное значение по имени. Доступно на Unix.",
        "en": "Return a string system configuration value by name. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.confstr(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.confstr",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — конфигурация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.confstr('CS_PATH'))   # → ? только Unix: пути поиска стандартных утилит, например /bin:/usr/bin",
        "print(os.confstr('CS_GNU_LIBC_VERSION'))   # → ? только Unix с glibc: версия libc, например glibc 2.36",
        "print(os.confstr(os.confstr_names['CS_PATH']))   # → ? то же значение, что и по имени: принимается и числовой код из os.confstr_names",
        "print(os.confstr('CS_XBS5_ILP32_OFF32_CFLAGS'))   # → ? None: имя известно, но значение в системе не определено",
        "print(os.confstr('CS_НЕТ_ТАКОГО'))   # → ValueError: unrecognized configuration name"
      ],
      "related": [
        "os.sysconf",
        "os.pathconf",
        "os.uname"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "os.copy_file_range",
      "title": "os.copy_file_range",
      "kind": "function",
      "summary": {
        "ru": "Копирует диапазон байтов между файлами в ядре (без прохода через пользовательский буфер; Python 3.8+). Доступно на Linux.",
        "en": "Copy a range of bytes between files in the kernel (3.8+). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.copy_file_range(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.copy_file_range",
      "version": "3.8",
      "section": "Модуль os",
      "subcat": "os — эффективное копирование",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "src = os.open('/tmp/copy_src.txt', os.O_RDWR | os.O_CREAT | os.O_TRUNC)   # временный файл-источник",
        "print(os.write(src, b'hello world!'))   # → 12 — записали 12 байт без буферизации Python",
        "dst = os.open('/tmp/copy_dst.txt', os.O_WRONLY | os.O_CREAT | os.O_TRUNC)   # временный файл-приёмник",
        "print(os.copy_file_range(src, dst, 12, offset_src=0))   # → 12 — ядро скопировало байты, минуя буфер Python",
        "print(os.copy_file_range(src, dst, 12, offset_src=12))   # → 0 — за концом файла копировать нечего"
      ],
      "related": [
        "os.sendfile",
        "os.splice"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.cpu_count",
      "title": "os.cpu_count",
      "kind": "function",
      "summary": {
        "ru": "Возвращает число логических процессоров в системе (или None, если не определить).",
        "en": "Return the number of logical CPUs (or None if undetermined)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.cpu_count()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.cpu_count",
      "version": "3.4",
      "section": "Модуль os",
      "subcat": "os — система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.cpu_count())  # → ? число логических процессоров, например 8 (или None, если определить не удалось)",
        "cpus = os.cpu_count() or 1  # страховка: None заменяем на 1",
        "print(min(4, cpus))  # → ? размер пула воркеров: 4 на машине с 4+ ядрами",
        "print(len(os.sched_getaffinity(0)))  # → ? только Unix: сколько ядер реально доступно процессу (может быть меньше os.cpu_count())"
      ],
      "related": [
        "os.sched_getaffinity",
        "ProcessPoolExecutor",
        "os.getloadavg"
      ],
      "related_errors": []
    },
    {
      "id": "os.ctermid",
      "title": "os.ctermid",
      "kind": "function",
      "summary": {
        "ru": "Возвращает путь к управляющему терминалу процесса. Доступно на Unix.",
        "en": "Return the path of the controlling terminal. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.ctermid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.ctermid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — псевдотерминалы (pty)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.ctermid())   # → ? только Unix: /dev/tty — путь к управляющему терминалу",
        "path = os.ctermid()   # только Unix",
        "print(os.path.exists(path))   # → True, если у процесса есть управляющий терминал",
        "with open(path, 'w') as tty: tty.write('сообщение мимо перенаправленного stdout\\n')   # → ? только Unix: строка появится в терминале, даже если stdout перенаправлен в файл",
        "fd = os.open(path, os.O_RDONLY)   # → ? только Unix: OSError (ENXIO), если процесс запущен без терминала — демон, cron, systemd",
        "print(os.isatty(fd))   # → True — дескриптор действительно указывает на терминал"
      ],
      "related": [
        "os.ttyname",
        "os.isatty",
        "os.login_tty"
      ],
      "related_errors": []
    },
    {
      "id": "os.device_encoding",
      "title": "os.device_encoding",
      "kind": "function",
      "summary": {
        "ru": "Возвращает кодировку, связанную с устройством по файловому дескриптору (или None, если это не терминал).",
        "en": "Return the encoding of the device tied to a file descriptor, or None."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.device_encoding(fd)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.device_encoding",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — терминал и устройства",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "enc = os.device_encoding(0)",
        "print(enc is None or isinstance(enc, str))   # → True (в терминале это кодировка вроде 'UTF-8', в консоли Windows 'cp866'; None, если stdin перенаправлен из файла или пайпа)",
        "fd = os.open('demo.txt', os.O_CREAT | os.O_WRONLY)",
        "print(os.device_encoding(fd))   # → None (обычный файл не терминал)",
        "os.close(fd)",
        "print(os.device_encoding(999))   # → None (для неверного дескриптора исключения не будет)"
      ],
      "related": [
        "os.isatty",
        "io.text_encoding",
        "sys.stdin-sys.stdout-sys.stderr"
      ],
      "related_errors": []
    },
    {
      "id": "os.dup",
      "title": "os.dup",
      "kind": "function",
      "summary": {
        "ru": "Дублирует файловый дескриптор, возвращая новый.",
        "en": "Duplicate a file descriptor, returning a new one."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.dup(fd)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.dup",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "fd = os.open(os.__file__, os.O_RDONLY)",
        "fd2 = os.dup(fd)",
        "print(fd2 != fd)   # → True",
        "os.close(fd)",
        "os.close(fd2)"
      ],
      "related": [
        "os.dup2",
        "os.close",
        "os.set_inheritable"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.dup2",
      "title": "os.dup2",
      "kind": "function",
      "summary": {
        "ru": "Дублирует fd в fd2 (закрыв fd2 при необходимости) и возвращает fd2.",
        "en": "Duplicate fd onto fd2 (closing fd2 first if needed) and return fd2."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.dup2(fd, fd2)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.dup2",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "fd = os.open(os.__file__, os.O_RDONLY)",
        "fd2 = os.dup(fd)",
        "print(os.dup2(fd, fd2) == fd2)   # → True",
        "os.close(fd)",
        "os.close(fd2)"
      ],
      "related": [
        "os.dup",
        "os.pipe",
        "os.close"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.environ",
      "title": "os.environ",
      "kind": "term",
      "summary": {
        "ru": "Словарь переменных окружения. Поддерживает get(), setitem, pop(). Изменения влияют на дочерние процессы.",
        "en": "The dictionary of environment variables. It supports get(), item assignment and pop(). Changes are inherited by child processes."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.environ  # os._Environ (MutableMapping)\nos.environ.get(key, default=None)\nos.environ[key] = value",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.environ",
      "version": "",
      "section": "Модуль os",
      "subcat": "окружение",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import os",
        "print(os.environ.get('HOME'))  # → /home/user",
        "print(os.environ.get('MISSING', 'default'))  # → default",
        "path = os.environ['PATH']  # → строка PATH",
        "os.environ['MY_VAR'] = 'hello'  # → установка переменной",
        "print('MY_VAR' in os.environ)  # → True"
      ],
      "related": [
        "os-getenv",
        "os.unsetenv",
        "subprocess-run"
      ],
      "related_errors": []
    },
    {
      "id": "os.eventfd",
      "title": "os.eventfd",
      "kind": "function",
      "summary": {
        "ru": "Создаёт дескриптор-счётчик событий для межпоточной/межпроцессной сигнализации (Python 3.10+). Доступно на Linux.",
        "en": "Create an event-counter file descriptor for signalling (3.10+). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.eventfd(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.eventfd",
      "version": "3.10",
      "section": "Модуль os",
      "subcat": "os — события и таймеры (fd)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.eventfd(0)   # → ? только Linux: новый дескриптор-счётчик со стартовым значением 0",
        "os.eventfd_write(fd, 3)   # → ? только Linux: счётчик 0 → 3, fd становится готовым для чтения",
        "print(os.eventfd_read(fd))   # → 3 — чтение забирает значение и обнуляет счётчик",
        "os.close(fd)   # → ? только Linux: eventfd закрывается как обычный файловый дескриптор",
        "print(os.eventfd_read(os.eventfd(2, os.EFD_SEMAPHORE | os.EFD_NONBLOCK)))   # → ? только Linux: 1, в семафорном режиме чтение снимает по одной единице"
      ],
      "related": [
        "os.eventfd_read",
        "os.eventfd_write",
        "os.pipe",
        "os.set_blocking"
      ],
      "related_errors": []
    },
    {
      "id": "os.eventfd_read",
      "title": "os.eventfd_read",
      "kind": "function",
      "summary": {
        "ru": "Читает (и обнуляет) значение счётчика из eventfd-дескриптора. Доступно на Linux.",
        "en": "Read (and reset) the counter value from an eventfd. Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.eventfd_read(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.eventfd_read",
      "version": "3.10",
      "section": "Модуль os",
      "subcat": "os — события и таймеры (fd)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.eventfd(0, os.EFD_NONBLOCK)   # → ? только Linux: счётчик 0, чтение не будет блокировать",
        "os.eventfd_write(fd, 2)   # → ? только Linux: счётчик 0 → 2",
        "os.eventfd_write(fd, 3)   # → ? только Linux: счётчик 2 → 5",
        "print(os.eventfd_read(fd))   # → 5 — записи суммируются до первого чтения",
        "print(os.eventfd_read(fd))   # → BlockingIOError, счётчик пуст; без EFD_NONBLOCK вызов заблокировался бы навсегда"
      ],
      "related": [
        "os.eventfd_write",
        "os.eventfd",
        "os.read"
      ],
      "related_errors": []
    },
    {
      "id": "os.eventfd_write",
      "title": "os.eventfd_write",
      "kind": "function",
      "summary": {
        "ru": "Прибавляет значение к счётчику eventfd-дескриптора. Доступно на Linux.",
        "en": "Add a value to an eventfd counter. Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.eventfd_write(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.eventfd_write",
      "version": "3.10",
      "section": "Модуль os",
      "subcat": "os — события и таймеры (fd)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.eventfd(0, os.EFD_NONBLOCK)   # → ? только Linux: счётчик 0",
        "os.eventfd_write(fd, 1)   # → ? только Linux: счётчик 0 → 1, ожидающий читатель просыпается",
        "os.eventfd_write(fd, 4)   # → ? только Linux: счётчик 1 → 5, значения складываются, а не заменяют друг друга",
        "print(os.eventfd_read(fd))   # → 5",
        "os.eventfd_write(fd, -1)   # → OverflowError, значение счётчика беззнаковое"
      ],
      "related": [
        "os.eventfd_read",
        "os.eventfd",
        "os.write"
      ],
      "related_errors": []
    },
    {
      "id": "os.execl",
      "title": "os.execl",
      "kind": "function",
      "summary": {
        "ru": "Как execv, но аргументы передаются перечислением, а не списком. Доступно на Unix.",
        "en": "Like execv, but arguments are passed inline rather than as a list. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.execl(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.execl",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (exec)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "os.execl('echo', 'echo', 'привет')   # → FileNotFoundError: execl не ищет программу в PATH, нужен полный путь (по PATH ищет execlp)",
        "pid = os.fork()   # → ? только Unix: 0 в дочернем процессе, pid ребёнка — в родительском",
        "if pid == 0: os.execl('/bin/echo', 'echo', 'привет')   # → ? только Unix: ребёнок замещается программой echo и печатает «привет», возврата из execl не бывает",
        "os.execl('/bin/ls', 'МОЁ-ИМЯ', '-l')   # → ? только Unix: arg0 задаёт лишь имя процесса в ps, запускается /bin/ls -l и замещает текущий процесс"
      ],
      "related": [
        "os.execv",
        "os.execlp",
        "os.execle"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.execle",
      "title": "os.execle",
      "kind": "function",
      "summary": {
        "ru": "Как execl, но с окружением (последний аргумент — словарь env). Доступно на Unix.",
        "en": "Like execl, but the last argument is an environment dict. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.execle(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.execle",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (exec)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "env = {'GREETING': 'привет', 'PATH': '/usr/bin:/bin'}",
        "os.execle('/bin/sh', 'sh', '-c', 'echo $GREETING', env)   # → ? только Unix: процесс замещается на sh и печатает «привет» — переменная взята из переданного env",
        "os.execle('/usr/bin/env', 'env', {})   # → ? только Unix: новый процесс стартует с пустым окружением, env не печатает ни строки",
        "os.execle('/bin/sh', 'sh', '-c', 'echo $GREETING')   # → TypeError: последний аргумент execle обязан быть словарём окружения, а не аргументом команды",
        "os.execl('/bin/sh', 'sh', '-c', 'echo $GREETING')   # → ? только Unix: у execl своего env нет — программа наследует окружение текущего процесса"
      ],
      "related": [
        "os.execl",
        "os.execve",
        "os.execlpe"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.execlp",
      "title": "os.execlp",
      "kind": "function",
      "summary": {
        "ru": "Как execl, но программа ищется в каталогах PATH. Доступно на Unix.",
        "en": "Like execl, but the program is looked up in PATH. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.execlp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.execlp",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (exec)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "os.execlp('echo', 'echo', 'привет')   # → ? только Unix: программа найдена в каталогах PATH, процесс замещён, печатается «привет»",
        "os.execl('echo', 'echo', 'привет')   # → FileNotFoundError: версия без «p» по PATH не ищет, ей нужен полный путь /bin/echo",
        "os.execlp('такой-программы-нет', 'такой-программы-нет')   # → FileNotFoundError: ни в одном каталоге PATH исполняемый файл не найден",
        "pid = os.fork()   # → ? только Unix: 0 в дочернем процессе, pid ребёнка — в родительском",
        "if pid == 0: os.execlp('ls', 'ls', '-l')   # → ? только Unix: ребёнок превращается в `ls -l`, родитель продолжает работу и ждёт его через os.wait()"
      ],
      "related": [
        "os.execl",
        "os.execvp",
        "os.execlpe"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.execlpe",
      "title": "os.execlpe",
      "kind": "function",
      "summary": {
        "ru": "Как execlp, но с явным окружением (последний аргумент — словарь env). Доступно на Unix.",
        "en": "Like execlp, but with an explicit environment dict. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.execlpe(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.execlpe",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (exec)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "env = {'PATH': '/usr/bin:/bin', 'LANG': 'C'}   # словарь окружения — ПОСЛЕДНИЙ аргумент, после всех argv",
        "print(sorted(env))   # → ['LANG', 'PATH']",
        "print(os.execlpe('нет-такой-программы', 'нет-такой-программы', env))   # → FileNotFoundError: ни в одном каталоге PATH ничего не нашлось",
        "# os.execlpe('env', 'env', env)   # только Unix: аргументы перечислены подряд (l), программа ищется в PATH (p), окружение задано явно (e) — env напечатает только LANG и PATH"
      ],
      "related": [
        "os.execlp",
        "os.execle",
        "os.execvpe"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.execv",
      "title": "os.execv",
      "kind": "function",
      "summary": {
        "ru": "Заменяет текущий процесс новой программой (путь + список аргументов); при успехе не возвращается. Доступно на Unix.",
        "en": "Replace the current process with a new program (path + args list). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.execv(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.execv",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (exec)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "args = ['ls', '-l', '/tmp']   # args[0] — имя программы, каким её увидит сама программа",
        "print(args[0], len(args))   # → ls 3",
        "print(os.execv('ls', args))   # → FileNotFoundError: execv не ищет в PATH, нужен полный путь",
        "# os.execv('/bin/ls', args)   # только Unix: текущий процесс заменяется на ls, строки ниже уже не выполнятся"
      ],
      "related": [
        "os.execl",
        "os.execve",
        "os.execvp",
        "os.posix_spawn"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.execve",
      "title": "os.execve",
      "kind": "function",
      "summary": {
        "ru": "Как execv, но с явным словарём переменных окружения для новой программы. Доступно на Unix.",
        "en": "Like execv, but with an explicit environment dict. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.execve(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.execve",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (exec)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "env = {'PATH': '/usr/bin:/bin', 'LANG': 'C'}   # новая программа получит РОВНО эти переменные, os.environ не наследуется",
        "print(sorted(env))   # → ['LANG', 'PATH']",
        "print(os.execve('/нет/такой/программы', ['prog'], env))   # → FileNotFoundError",
        "# os.execve('/usr/bin/env', ['env'], env)   # только Unix: env напечатает LANG и PATH; с env={} программа стартует вообще без PATH и HOME"
      ],
      "related": [
        "os.execv",
        "os.execle",
        "os.execvpe",
        "os.environ"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.execvp",
      "title": "os.execvp",
      "kind": "function",
      "summary": {
        "ru": "Как execv, но программа ищется в каталогах PATH. Доступно на Unix.",
        "en": "Like execv, but the program is looked up in PATH. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.execvp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.execvp",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (exec)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "args = ['ls', '-l']   # аргументы списком (v), имя программы — без пути",
        "print(len(os.get_exec_path()) > 0)   # → True — именно в этих каталогах (PATH) execvp ищет программу",
        "print(os.execvp('нет-такой-программы', ['нет-такой-программы']))   # → FileNotFoundError: ни в одном каталоге PATH не нашлось",
        "# os.execvp('ls', args)   # только Unix: найдёт /bin/ls в PATH и заменит текущий процесс — строки ниже не выполнятся"
      ],
      "related": [
        "os.execv",
        "os.execlp",
        "os.execvpe"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.execvpe",
      "title": "os.execvpe",
      "kind": "function",
      "summary": {
        "ru": "Как execvp, но с явным окружением. Доступно на Unix.",
        "en": "Like execvp, but with an explicit environment dict. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.execvpe(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.execvpe",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (exec)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "env = {'PATH': '/usr/bin:/bin', 'LC_ALL': 'C'}   # PATH для поиска берётся из ЭТОГО словаря, а не из os.environ",
        "print(sorted(env))   # → ['LC_ALL', 'PATH']",
        "print(os.execvpe('нет-такой-программы', ['нет-такой-программы'], env))   # → FileNotFoundError: в PATH из env ничего не нашлось",
        "# os.execvpe('env', ['env'], env)   # только Unix: env найдётся в /usr/bin и напечатает LC_ALL и PATH; если в словаре нет PATH, поиск идёт по os.defpath"
      ],
      "related": [
        "os.execvp",
        "os.execve",
        "os.execlpe"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.fchdir",
      "title": "os.fchdir",
      "kind": "function",
      "summary": {
        "ru": "Меняет текущий каталог на тот, что задан открытым дескриптором. Доступно на Unix.",
        "en": "Change the working directory to the one given by an open fd. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fchdir(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fchdir",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — каталоги",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "back = os.open('.', os.O_RDONLY)   # → ? только Unix: дескриптор текущего каталога — «закладка», чтобы вернуться",
        "os.chdir('/tmp')   # → ? текущий каталог сменён на /tmp (обычный переход по пути)",
        "os.fchdir(back)   # → ? только Unix: возврат в исходный каталог по дескриптору, а не по строке пути",
        "os.close(back)   # → ? дескриптор освобождён; следующий os.fchdir(back) даст OSError: Bad file descriptor",
        "os.fchdir(os.open('/etc/hosts', os.O_RDONLY))   # → NotADirectoryError: нужен дескриптор каталога, а не файла"
      ],
      "related": [
        "os.chdir",
        "os.getcwd",
        "contextlib.chdir"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.fchmod",
      "title": "os.fchmod",
      "kind": "function",
      "summary": {
        "ru": "Как chmod, но по открытому файловому дескриптору. Доступно на Unix.",
        "en": "Like chmod, but by an open file descriptor. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fchmod(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fchmod",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — права и владелец",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "f = open('demo_fd.txt', 'w')  # → ? открываем файл и дальше работаем через его дескриптор",
        "os.fchmod(f.fileno(), 0o600)  # → ? только Unix: то же, что os.chmod(path, 0o600), но привязано к дескриптору, а не к имени файла",
        "print(oct(os.fstat(f.fileno()).st_mode & 0o777))  # → 0o600",
        "os.fchmod(f.fileno(), 0o644)  # → ? только Unix: открываем чтение остальным; файл могли переименовать — дескриптор всё равно указывает на него",
        "f.close()  # → ? закрываем файл, дескриптор становится недействительным",
        "os.fchmod(f.fileno(), 0o600)  # → OSError: Bad file descriptor — по закрытому дескриптору права менять нельзя"
      ],
      "related": [
        "os.chmod",
        "os.fchown",
        "os.fstat"
      ],
      "related_errors": [
        "PermissionError",
        "OSError"
      ]
    },
    {
      "id": "os.fchown",
      "title": "os.fchown",
      "kind": "function",
      "summary": {
        "ru": "Как chown, но по открытому файловому дескриптору. Доступно на Unix.",
        "en": "Like chown, but by an open file descriptor. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fchown(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fchown",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — права и владелец",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "f = open('demo_fchown.txt', 'w')  # → ? открываем файл и дальше работаем через его дескриптор",
        "os.fchown(f.fileno(), os.getuid(), os.getgid())  # → ? только Unix: владелец и группа — текущий пользователь",
        "os.fchown(f.fileno(), -1, os.getgid())  # → ? только Unix: -1 = «не менять», меняется только группа",
        "print(os.fstat(f.fileno()).st_uid == os.getuid())  # → ? True: os.fstat читает метаданные того же дескриптора (на Unix)",
        "os.fchown(f.fileno(), 0, 0)  # → PermissionError, если скрипт запущен не от root",
        "f.close()  # → ? закрываем файл; после этого os.fchown по этому дескриптору даст OSError"
      ],
      "related": [
        "os.chown",
        "os.fchmod",
        "os.fstat"
      ],
      "related_errors": [
        "OSError",
        "PermissionError"
      ]
    },
    {
      "id": "os.fdatasync",
      "title": "os.fdatasync",
      "kind": "function",
      "summary": {
        "ru": "Как fsync, но синхронизирует только данные, без части метаданных. Доступно на Unix.",
        "en": "Like fsync, but flushes only data, not all metadata. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fdatasync(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fdatasync",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — синхронизация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/tmp/data.log', os.O_WRONLY | os.O_CREAT)   # → ? дескриптор открытого файла, например 3",
        "print(os.write(fd, b'payload\\n'))   # → 8",
        "os.fdatasync(fd)   # → ? только Unix: None; данные гарантированно на диске, метаданные вроде времени изменения могут остаться в кеше",
        "os.fsync(fd)   # → ? None; в отличие от fdatasync сбрасывает и метаданные — надёжнее, но дороже",
        "os.fdatasync(999)   # → OSError: [Errno 9] Bad file descriptor — 999 не открытый дескриптор"
      ],
      "related": [
        "os.fsync",
        "os.sync",
        "file-flush"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.fdopen",
      "title": "os.fdopen",
      "kind": "function",
      "summary": {
        "ru": "Оборачивает файловый дескриптор в файловый объект Python (как open()).",
        "en": "Wrap a file descriptor in a Python file object (like open())."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fdopen(fd, mode)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fdopen",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "fd = os.open(os.__file__, os.O_RDONLY)",
        "f = os.fdopen(fd)",
        "print(hasattr(f, 'read'))   # → True",
        "f.close()"
      ],
      "related": [
        "open",
        "os.pipe",
        "os.close"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.fork",
      "title": "os.fork",
      "kind": "function",
      "summary": {
        "ru": "Порождает дочерний процесс копированием текущего: возвращает 0 в потомке и PID потомка в родителе. Доступно на Unix.",
        "en": "Fork a child process; returns 0 in the child and the child PID in the parent. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fork(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fork",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — создание процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "data = [1, 2, 3]",
        "pid = os.fork()   # → ? только Unix (на Windows — AttributeError): в потомке 0, в родителе PID потомка, например 12345",
        "data.append(pid)   # → ? память скопирована: у потомка список станет [1, 2, 3, 0], у родителя [1, 2, 3, 12345]",
        "if pid == 0: os._exit(0)   # → ? потомок завершается немедленно, минуя atexit-обработчики и сброс буферов",
        "print(os.waitpid(pid, 0))   # → ? только в родителе: (12345, 0) — второй элемент это сырой статус, а не код выхода (см. os.waitstatus_to_exitcode)"
      ],
      "related": [
        "os.forkpty",
        "os.waitpid",
        "os.execv",
        "os.register_at_fork"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.forkpty",
      "title": "os.forkpty",
      "kind": "function",
      "summary": {
        "ru": "Как fork, но потомок получает новый псевдотерминал (pty) как управляющий; возвращает (pid, fd). Доступно на Unix.",
        "en": "Like fork, but the child gets a new pseudo-terminal; returns (pid, fd). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.forkpty(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.forkpty",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — создание процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pid, fd = os.forkpty()   # → ? только Unix: в родителе (PID потомка, дескриптор мастер-конца pty), в потомке (0, -1)",
        "if pid == 0: os.execvp('ls', ['ls', '-1'])   # → ? потомок подменяется командой; в отличие от os.fork() её stdin/stdout — настоящий терминал, поэтому работают программы, требующие tty",
        "print(os.read(fd, 1024))   # → ? только в родителе: вывод потомка, например b'file1\\r\\nfile2\\r\\n' — в pty перевод строки приходит как \\r\\n",
        "print(os.waitpid(pid, 0))   # → ? (PID потомка, 0) — команда отработала и завершилась с кодом 0",
        "os.read(fd, 1024)   # → ? после завершения потомка на Linux: OSError [Errno 5] Input/output error, а не пустые байты"
      ],
      "related": [
        "os.fork",
        "os.openpty",
        "os.login_tty"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.fpathconf",
      "title": "os.fpathconf",
      "kind": "function",
      "summary": {
        "ru": "Как pathconf, но по открытому файловому дескриптору. Доступно на Unix.",
        "en": "Like pathconf, but by an open file descriptor. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fpathconf(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fpathconf",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — конфигурация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "f = open('demo_conf.txt', 'w')  # учебный файл — нужен только ради открытого дескриптора",
        "print(os.fpathconf(f.fileno(), 'PC_NAME_MAX'))  # → ? только Unix: предел длины имени файла на этой ФС, обычно 255",
        "print(os.fpathconf(f.fileno(), os.pathconf_names['PC_PATH_MAX']))  # → ? только Unix: то же можно спросить числовым кодом из os.pathconf_names, например 4096",
        "r, w = os.pipe()",
        "print(os.fpathconf(w, 'PC_PIPE_BUF'))  # → ? только Unix: сколько байт пишется в канал атомарно, обычно 4096 — у канала нет пути, поэтому os.pathconf тут неприменим",
        "print(os.fpathconf(f.fileno(), 'PC_UNKNOWN'))  # → ValueError: имя ограничения не распознано"
      ],
      "related": [
        "os.pathconf",
        "os.sysconf",
        "os.fstat"
      ],
      "related_errors": [
        "OSError",
        "ValueError"
      ]
    },
    {
      "id": "os.fsdecode",
      "title": "os.fsdecode",
      "kind": "function",
      "summary": {
        "ru": "Декодирует путь (bytes) в str из кодировки файловой системы.",
        "en": "Decode a path (bytes) to str using the filesystem encoding."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fsdecode(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fsdecode",
      "version": "3.2",
      "section": "Модуль os",
      "subcat": "os — кодирование путей",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.fsdecode(b'abc'))   # → abc",
        "print(os.fsdecode('abc'))   # → abc",
        "print(os.fsdecode(b'\\xd0\\xb4\\xd0\\xb0\\xd0\\xbd\\xd0\\xbd\\xd1\\x8b\\xd0\\xb5.txt'))   # → данные.txt",
        "print(sorted(os.fsdecode(n) for n in os.listdir(b'.')))   # → имена файлов текущего каталога как str, например ['data.txt', 'main.py']",
        "print(os.fsdecode(5))   # → TypeError: expected str, bytes or os.PathLike object, not int"
      ],
      "related": [
        "os.fsencode",
        "os.fspath",
        "os.getcwdb"
      ],
      "related_errors": []
    },
    {
      "id": "os.fsencode",
      "title": "os.fsencode",
      "kind": "function",
      "summary": {
        "ru": "Кодирует путь (str) в bytes в кодировке файловой системы.",
        "en": "Encode a path (str) to bytes using the filesystem encoding."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fsencode(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fsencode",
      "version": "3.2",
      "section": "Модуль os",
      "subcat": "os — кодирование путей",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import pathlib",
        "print(os.fsencode('abc'))   # → b'abc'",
        "print(os.fsencode(b'abc'))   # → b'abc'",
        "print(os.fsencode('данные.txt'))   # → b'\\xd0\\xb4\\xd0\\xb0\\xd0\\xbd\\xd0\\xbd\\xd1\\x8b\\xd0\\xb5.txt'",
        "print(os.fsencode(pathlib.PurePosixPath('/tmp/data.txt')))   # → b'/tmp/data.txt'",
        "print(os.fsdecode(os.fsencode('данные.txt')))   # → данные.txt"
      ],
      "related": [
        "os.fsdecode",
        "os.fspath",
        "os.getcwdb"
      ],
      "related_errors": []
    },
    {
      "id": "os.fspath",
      "title": "os.fspath",
      "kind": "function",
      "summary": {
        "ru": "Возвращает путь как str/bytes: для строки — её саму, для объекта с __fspath__ (напр. Path) — его результат.",
        "en": "Return the path as str/bytes, calling __fspath__ if present (e.g. Path)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fspath(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fspath",
      "version": "3.6",
      "section": "Модуль os",
      "subcat": "os — кодирование путей",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import pathlib",
        "print(os.fspath('a/b'))   # → a/b",
        "print(os.fspath(b'a/b'))   # → b'a/b'",
        "p = pathlib.PurePosixPath('/tmp/data.txt')",
        "print(os.fspath(p))   # → /tmp/data.txt",
        "print(os.path.basename(os.fspath(p)))   # → data.txt",
        "print(os.fspath(5))   # → TypeError: expected str, bytes or os.PathLike object, not int"
      ],
      "related": [
        "os.fsencode",
        "os.fsdecode",
        "path"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "os.fstat",
      "title": "os.fstat",
      "kind": "function",
      "summary": {
        "ru": "Возвращает os.stat_result для открытого файлового дескриптора.",
        "en": "Return an os.stat_result for an open file descriptor."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fstat(fd)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fstat",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "fd = os.open(os.__file__, os.O_RDONLY)",
        "print(os.fstat(fd).st_size > 0)   # → True",
        "os.close(fd)"
      ],
      "related": [
        "os.stat",
        "os.stat_result",
        "os.lstat"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.fstatvfs",
      "title": "os.fstatvfs",
      "kind": "function",
      "summary": {
        "ru": "Как statvfs, но по открытому файловому дескриптору. Доступно на Unix.",
        "en": "Like statvfs, but by an open file descriptor. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fstatvfs(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fstatvfs",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловая система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/', os.O_RDONLY)   # → ? только Unix: дескриптор каталога-точки монтирования",
        "st = os.fstatvfs(fd)   # → ? только Unix: os.statvfs_result — та же структура, что у os.statvfs",
        "print(st.f_bsize)   # → ? только Unix: предпочтительный размер блока ФС в байтах, например 4096",
        "print(st.f_bavail * st.f_frsize // 1024 // 1024)   # → ? только Unix: свободно мегабайт для обычного пользователя, например 15340",
        "print(os.fstatvfs(fd).f_blocks == os.statvfs('/').f_blocks)   # → ? только Unix: True — fstatvfs адресует ФС по дескриптору, statvfs по пути",
        "os.close(fd)   # → ? только Unix: дескриптор освобождён"
      ],
      "related": [
        "os.statvfs",
        "os.statvfs_result",
        "os.fstat"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.fsync",
      "title": "os.fsync",
      "kind": "function",
      "summary": {
        "ru": "Принудительно сбрасывает буферы файла (по дескриптору) на диск.",
        "en": "Flush a file descriptor's buffers to disk."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fsync(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fsync",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — синхронизация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "fd, p = tempfile.mkstemp()",
        "os.write(fd, b'x')",
        "print(os.fsync(fd) is None)   # → True",
        "os.close(fd)",
        "os.remove(p)"
      ],
      "related": [
        "os.fdatasync",
        "file-flush",
        "os.sync"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.ftruncate",
      "title": "os.ftruncate",
      "kind": "function",
      "summary": {
        "ru": "Усекает файл, открытый по дескриптору, до указанной длины.",
        "en": "Truncate the file behind a descriptor to a given length."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.ftruncate(fd, length)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.ftruncate",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "fd, p = tempfile.mkstemp()",
        "os.write(fd, b'hello')",
        "os.ftruncate(fd, 2)",
        "os.close(fd)",
        "print(os.path.getsize(p))   # → 2",
        "os.remove(p)"
      ],
      "related": [
        "os.truncate",
        "file-truncate",
        "os.lseek"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.fwalk",
      "title": "os.fwalk",
      "kind": "function",
      "summary": {
        "ru": "Как os.walk, но дополнительно выдаёт дескриптор каталога (безопаснее к гонкам). Доступно на Unix.",
        "en": "Like os.walk, but also yields a directory file descriptor. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.fwalk(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.fwalk",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — обход дерева",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "step = next(os.fwalk('/etc'))",
        "print(len(step))  # → 4 — (dirpath, dirnames, filenames, dirfd); у os.walk элементов 3",
        "print(step[0])  # → ? только Unix: /etc — обход начинается с корня дерева",
        "print(os.stat(step[2][0], dir_fd=step[3]).st_size)  # → ? только Unix: размер первого файла в /etc, полученный через дескриптор каталога, а не по пути — ради этого fwalk и берут",
        "print(list(os.fwalk('/нет-такого')))  # → [] — несуществующий каталог молча даёт пустой обход, как и os.walk"
      ],
      "related": [
        "os.walk",
        "os.scandir",
        "os.listdir"
      ],
      "related_errors": []
    },
    {
      "id": "os.get_blocking",
      "title": "os.get_blocking",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, находится ли дескриптор в блокирующем режиме ввода-вывода. Доступно на Unix.",
        "en": "Check whether a descriptor is in blocking I/O mode. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.get_blocking(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.get_blocking",
      "version": "3.5",
      "section": "Модуль os",
      "subcat": "os — режим дескриптора",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()",
        "print(os.get_blocking(r))   # → только Unix: True — новый канал открыт в блокирующем режиме",
        "os.set_blocking(r, False)   # → только Unix: переводим тот же дескриптор в неблокирующий режим",
        "print(os.get_blocking(r))   # → только Unix: False — get_blocking видит изменение флага O_NONBLOCK",
        "print(os.get_blocking(9999))   # → только Unix: OSError (EBADF, Bad file descriptor) — такого дескриптора нет"
      ],
      "related": [
        "os.set_blocking",
        "os.get_inheritable",
        "os.pipe"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.get_exec_path",
      "title": "os.get_exec_path",
      "kind": "function",
      "summary": {
        "ru": "Возвращает список каталогов для поиска исполняемых файлов (из PATH).",
        "en": "Return the list of directories searched for executables (from PATH)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.get_exec_path()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.get_exec_path",
      "version": "3.2",
      "section": "Модуль os",
      "subcat": "os — окружение",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.get_exec_path({'PATH': os.pathsep.join(['/usr/bin', '/bin'])}))   # → ['/usr/bin', '/bin']",
        "print(os.get_exec_path() == os.environ['PATH'].split(os.pathsep))   # → True — это просто PATH, разбитый по os.pathsep",
        "print(os.get_exec_path({'PATH': ''}))   # → [''] — пустой PATH даёт один пустой элемент, то есть текущий каталог",
        "print(os.get_exec_path({}))   # → если ключа PATH нет, берётся os.defpath, например ['', '/bin', '/usr/bin']"
      ],
      "related": [
        "os.environ",
        "os.execvp",
        "os.getenv"
      ],
      "related_errors": []
    },
    {
      "id": "os.get_handle_inheritable",
      "title": "os.get_handle_inheritable()",
      "kind": "function",
      "summary": {
        "ru": "Только Windows: возвращает флаг наследуемости (bool) для Windows-хендла; кросс-платформенный аналог для файловых дескрипторов — os.get_inheritable(). Python 3.4+.",
        "en": "Windows only: returns the \"inheritable\" flag (a bool) of the specified Windows handle; the cross-platform counterpart for file descriptors is os.get_inheritable(). Python 3.4+."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.get_handle_inheritable(handle, /)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.get_handle_inheritable",
      "version": "3.4",
      "section": "Модуль os",
      "subcat": "дескрипторы",
      "color_group": "module",
      "aliases": [
        "наследуется ли хендл",
        "флаг наследования хендла",
        "наследование дескриптора виндовс"
      ],
      "keywords": [
        "os.get_handle_inheritable",
        "get_handle_inheritable"
      ],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(hasattr(os, 'get_handle_inheritable') == (os.name == 'nt'))  # → True (функция есть только на Windows)",
        "r, w = os.pipe()",
        "print(os.get_inheritable(r))  # → False (PEP 446: новые дескрипторы не наследуются)",
        "handle = __import__('msvcrt').get_osfhandle(r) if os.name == 'nt' else None",
        "print(os.get_handle_inheritable(handle) == os.get_inheritable(r) if handle is not None else True)  # → True"
      ],
      "related": [
        "os.get_inheritable",
        "os.set_inheritable",
        "os.pipe",
        "os.dup"
      ],
      "related_errors": [
        "OSError",
        "AttributeError"
      ]
    },
    {
      "id": "os.get_inheritable",
      "title": "os.get_inheritable",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, наследуется ли дескриптор дочерними процессами.",
        "en": "Check whether a file descriptor is inheritable by child processes."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.get_inheritable(fd)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.get_inheritable",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()",
        "print(isinstance(os.get_inheritable(r), bool))   # → True",
        "os.close(r)",
        "os.close(w)"
      ],
      "related": [
        "os.set_inheritable",
        "os.dup",
        "os.pipe"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.get_terminal_size",
      "title": "os.get_terminal_size",
      "kind": "function",
      "summary": {
        "ru": "Возвращает размер терминала (columns, lines); бросает OSError, если вывод не привязан к терминалу.",
        "en": "Return the terminal size (columns, lines); raises OSError if not a terminal."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.get_terminal_size()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.get_terminal_size",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — терминал и устройства",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "size = os.get_terminal_size()",
        "print(size)   # → os.terminal_size(columns=80, lines=24) — конкретные числа зависят от размеров окна терминала",
        "print(size.columns)   # → ширина терминала в символах, например 80",
        "print('-' * size.columns)   # → разделитель во всю ширину окна — типовое применение",
        "print(os.get_terminal_size(0).lines)   # → высота терминала, привязанного к stdin (по умолчанию берётся stdout)",
        "print(os.get_terminal_size(999))   # → OSError — дескриптор не привязан к терминалу; та же ошибка при запуске из пайпа или CI, поэтому в утилитах берут shutil.get_terminal_size() с запасным значением"
      ],
      "related": [
        "os.terminal_size",
        "os.isatty",
        "os.device_encoding"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.getcwd",
      "title": "os.getcwd",
      "kind": "function",
      "summary": {
        "ru": "Возвращает текущий рабочий каталог как строку (str).",
        "en": "Return the current working directory as a str."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getcwd() -> str",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getcwd",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — рабочий каталог",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(isinstance(os.getcwd(), str))   # → True",
        "print(type(os.getcwd()))  # → <class 'str'>",
        "start = os.getcwd()  # запомнить текущий каталог"
      ],
      "related": [
        "os.chdir",
        "path.cwd"
      ],
      "related_errors": []
    },
    {
      "id": "os.getcwdb",
      "title": "os.getcwdb",
      "kind": "function",
      "summary": {
        "ru": "Возвращает текущий рабочий каталог как байты (bytes).",
        "en": "Return the current working directory as bytes."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getcwdb()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getcwdb",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — рабочий каталог",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(isinstance(os.getcwdb(), bytes))   # → True",
        "print(os.path.isabs(os.getcwdb().decode()))   # → True (это абсолютный путь текущего каталога)",
        "print(os.getcwdb().decode() == os.getcwd())  # → True (то же значение, только в str)",
        "print([n.decode() for n in os.listdir(os.getcwdb())] == os.listdir(os.getcwd()))  # → True (байтовый путь даёт байтовые имена)",
        "print(os.getcwdb() == os.getcwd())  # → False (bytes и str не равны никогда)"
      ],
      "related": [
        "os.getcwd",
        "os.fsencode",
        "os.chdir"
      ],
      "related_errors": []
    },
    {
      "id": "os.getegid",
      "title": "os.getegid",
      "kind": "function",
      "summary": {
        "ru": "Возвращает эффективный GID процесса. Доступно на Unix.",
        "en": "Return the process's effective group id. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getegid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getegid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getegid())  # → ? только Unix: эффективный GID процесса, например 1000",
        "print(type(os.getegid()))  # → ? только Unix: <class 'int'>",
        "print(os.getegid() == os.getgid())  # → ? только Unix: True, пока setgid-бит не сменил группу процесса",
        "print(os.getegid() == 0)  # → ? только Unix: True, если права процесса выданы по группе root"
      ],
      "related": [
        "os.getgid",
        "os.setegid",
        "os.geteuid"
      ],
      "related_errors": []
    },
    {
      "id": "os.getenv",
      "title": "os.getenv",
      "kind": "function",
      "summary": {
        "ru": "Возвращает значение переменной окружения или default, если её нет.",
        "en": "Return an environment variable's value, or default if unset."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getenv(key, default=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getenv",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — окружение",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "os.environ['APP_MODE'] = 'debug'",
        "print(os.getenv('APP_MODE'))   # → debug",
        "print(os.getenv('NO_SUCH_VAR_XYZ', 'default'))   # → default",
        "print(os.getenv('NO_SUCH_VAR_XYZ'))   # → None",
        "print(int(os.getenv('RETRIES', '3')) + 1)   # → 4 — из окружения приходят только строки, число нужно привести явно",
        "print(os.getenv('APP_MODE') == os.environ.get('APP_MODE'))   # → True — getenv это сокращение для os.environ.get"
      ],
      "related": [
        "os.environ",
        "os.putenv",
        "os.getenvb",
        "os.unsetenv"
      ],
      "related_errors": []
    },
    {
      "id": "os.getenvb",
      "title": "os.getenvb",
      "kind": "function",
      "summary": {
        "ru": "Как getenv, но ключ и значение — байты (bytes); только для Unix. Доступно на Unix.",
        "en": "Like getenv, but the key and value are bytes; Unix only. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getenvb(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getenvb",
      "version": "3.2",
      "section": "Модуль os",
      "subcat": "os — окружение",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.supports_bytes_environ)   # → ? True на Unix (там есть getenvb), False на Windows",
        "os.environb[b'GLOSSARY_DEMO'] = b'42'   # → ? только Unix: кладём переменную в байтовое окружение",
        "print(os.getenvb(b'GLOSSARY_DEMO'))   # → b'42' — и ключ, и значение остаются bytes",
        "print(os.getenvb(b'GLOSSARY_NO_SUCH', b'fallback'))   # → b'fallback' — второй аргумент это значение по умолчанию",
        "print(os.getenvb('GLOSSARY_DEMO'))   # → TypeError — str-ключ не подходит, для строк есть os.getenv"
      ],
      "related": [
        "os.getenv",
        "os.environ",
        "os.fsencode"
      ],
      "related_errors": []
    },
    {
      "id": "os.geteuid",
      "title": "os.geteuid",
      "kind": "function",
      "summary": {
        "ru": "Возвращает эффективный UID процесса (используется для проверки прав). Доступно на Unix.",
        "en": "Return the process's effective user id. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.geteuid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.geteuid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.geteuid())  # → ? только Unix: эффективный UID процесса, например 1000",
        "print(os.geteuid() == 0)  # → ? только Unix: True, если скрипт выполняется с правами root",
        "print(os.geteuid() == os.getuid())  # → ? только Unix: True; False только в setuid-программах вроде sudo",
        "print(str(os.geteuid()).isdigit())  # → ? только Unix: True — возвращается число, а не имя; имя даёт pwd.getpwuid(os.geteuid()).pw_name"
      ],
      "related": [
        "os.getuid",
        "os.seteuid",
        "os.getegid"
      ],
      "related_errors": []
    },
    {
      "id": "os.getgid",
      "title": "os.getgid",
      "kind": "function",
      "summary": {
        "ru": "Возвращает реальный идентификатор группы (GID) процесса. Доступно на Unix.",
        "en": "Return the process's real group id (GID). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getgid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getgid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getgid())  # → ? только Unix: реальный GID процесса, например 1000",
        "print(os.getgid() == os.getegid())  # → ? только Unix: True — реальный и эффективный GID обычно совпадают",
        "print(os.getgid() in os.getgroups())  # → ? только Unix: обычно True — основная группа входит в набор групп процесса"
      ],
      "related": [
        "os.getegid",
        "os.setgid",
        "os.getuid"
      ],
      "related_errors": []
    },
    {
      "id": "os.getgrouplist",
      "title": "os.getgrouplist",
      "kind": "function",
      "summary": {
        "ru": "Возвращает список групп, к которым принадлежит пользователь (по имени и базовому GID). Доступно на Unix.",
        "en": "Return the group ids a user belongs to (by name and base GID). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getgrouplist(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getgrouplist",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getgrouplist('root', 0))  # → ? только Unix: список GID групп пользователя root, например [0]",
        "print(27 in os.getgrouplist('root', 0))  # → True, если root состоит в группе с GID 27 (sudo в Ubuntu)",
        "print(len(os.getgrouplist('root', 0)) >= 1)  # → True — в отличие от os.getgroups(), спросить можно про любого пользователя, не только текущего",
        "print(os.getgrouplist('нет-такого-пользователя', 100))  # → ? только Unix: обычно [100] — неизвестное имя не ошибка, вернётся только базовый GID"
      ],
      "related": [
        "os.getgroups",
        "os.initgroups",
        "os.setgroups"
      ],
      "related_errors": []
    },
    {
      "id": "os.getgroups",
      "title": "os.getgroups",
      "kind": "function",
      "summary": {
        "ru": "Возвращает список дополнительных идентификаторов групп процесса. Доступно на Unix.",
        "en": "Return the list of supplemental group ids. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getgroups(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getgroups",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getgroups())  # → ? только Unix: список GID групп процесса, например [4, 24, 27, 1000]",
        "print(type(os.getgroups()))  # → ? только Unix: <class 'list'>",
        "print(27 in os.getgroups())  # → ? только Unix: True, если процесс входит в группу с GID 27 (sudo в Ubuntu)",
        "print(len(os.getgroups()))  # → ? только Unix: число групп; на macOS список обрезан (не более 16) и может расходиться с os.getgrouplist()"
      ],
      "related": [
        "os.setgroups",
        "os.getgrouplist",
        "os.initgroups"
      ],
      "related_errors": []
    },
    {
      "id": "os.getloadavg",
      "title": "os.getloadavg",
      "kind": "function",
      "summary": {
        "ru": "Возвращает среднюю загрузку системы за 1, 5 и 15 минут. Доступно на Unix.",
        "en": "Return the system load average over 1, 5 and 15 minutes. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getloadavg(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getloadavg",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getloadavg())  # → ? только Unix: кортеж из трёх float, например (0.52, 0.48, 0.41); на Windows — AttributeError",
        "load1, load5, load15 = os.getloadavg()  # → ? только Unix: средние за 1, 5 и 15 минут",
        "print(round(load1 / (os.cpu_count() or 1), 2))  # → ? только Unix: нагрузка на одно ядро, например 0.07",
        "print(load1 > load15)  # → True, если за последнюю минуту нагрузка выросла"
      ],
      "related": [
        "os.cpu_count",
        "os.times",
        "os.sysconf"
      ],
      "related_errors": []
    },
    {
      "id": "os.getlogin",
      "title": "os.getlogin",
      "kind": "function",
      "summary": {
        "ru": "Возвращает имя пользователя, вошедшего в управляющий терминал процесса. Доступно на Unix.",
        "en": "Return the name of the user logged in on the controlling terminal. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getlogin(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getlogin",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.getlogin())  # → имя пользователя управляющего терминала, например alex",
        "user = os.getlogin()",
        "print(os.path.join('/home', user))  # → путь к домашнему каталогу по имени входа, например /home/alex",
        "print(os.environ.get('USER') or os.getlogin())  # → то же имя; в cron или в демоне без терминала os.getlogin() бросает OSError, поэтому сначала смотрят переменную окружения",
        "print(os.getlogin() == os.environ.get('LOGNAME'))  # → на Unix обычно True, но getlogin() читает владельца терминала, а LOGNAME — переменную окружения, которую можно подменить"
      ],
      "related": [
        "os.getuid",
        "os.ctermid",
        "os.environ"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.getpgid",
      "title": "os.getpgid",
      "kind": "function",
      "summary": {
        "ru": "Возвращает идентификатор группы процессов (PGID) для заданного PID. Доступно на Unix.",
        "en": "Return the process-group id (PGID) of a given PID. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getpgid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getpgid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — сессии и группы процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pgid = os.getpgid(0)  # → ? только Unix: PGID текущего процесса, например 4711",
        "print(os.getpgid(os.getpid()) == pgid)  # → ? только Unix: True — pid=0 означает «текущий процесс»",
        "print(os.getpgid(0) == os.getpgrp())  # → ? только Unix: True — os.getpgrp() возвращает тот же PGID, но без аргумента",
        "print(os.getpgid(os.getppid()))  # → ? только Unix: PGID родителя (обычно shell), например 4390",
        "print(os.getpgid(999999))  # → ProcessLookupError: [Errno 3] No such process"
      ],
      "related": [
        "os.setpgid",
        "os.getpgrp",
        "os.getsid"
      ],
      "related_errors": [
        "ProcessLookupError"
      ]
    },
    {
      "id": "os.getpgrp",
      "title": "os.getpgrp",
      "kind": "function",
      "summary": {
        "ru": "Возвращает PGID текущего процесса. Доступно на Unix.",
        "en": "Return the current process's process-group id. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getpgrp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getpgrp",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — сессии и группы процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pgid = os.getpgrp()  # → ? только Unix: PGID текущего процесса, например 4711",
        "print(os.getpgrp() == os.getpgid(0))  # → ? только Unix: True — два способа узнать свой PGID",
        "print(os.getpgrp() == os.getpid())  # → ? только Unix: True, если процесс — лидер своей группы; при запуске в конвейере shell обычно False",
        "os.setpgrp()  # → ? только Unix: процесс становится лидером новой группы процессов",
        "print(os.getpgrp() == os.getpid())  # → ? только Unix: True — после setpgrp() PGID совпал с PID"
      ],
      "related": [
        "os.getpgid",
        "os.setpgrp",
        "os.getsid"
      ],
      "related_errors": []
    },
    {
      "id": "os.getpid",
      "title": "os.getpid()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает PID (идентификатор) текущего процесса. Полезно для логирования и создания временных файлов с уникальными именами.",
        "en": "Returns the PID (identifier) of the current process. Useful for logging and for giving temporary files unique names."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getpid() -> int",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getpid",
      "version": "",
      "section": "Модуль os",
      "subcat": "процесс",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import os",
        "print(os.getpid())  # → 12345  (PID текущего процесса)",
        "tmp_name = f'/tmp/app_{os.getpid()}.tmp'  # → уникальное временное имя",
        "print(type(os.getpid()))  # → <class 'int'>",
        "print(os.getpid() > 0)  # → True",
        "pid = os.getpid()",
        "print(f'Process ID: {pid}')  # → Process ID: 12345"
      ],
      "related": [
        "os.getppid",
        "os.kill",
        "subprocess-Popen"
      ],
      "related_errors": []
    },
    {
      "id": "os.getppid",
      "title": "os.getppid",
      "kind": "function",
      "summary": {
        "ru": "Возвращает PID родительского процесса.",
        "en": "Return the parent process id."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getppid()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getppid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — процесс",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getppid())   # → ? PID родительского процесса, например 4312 (для скрипта из терминала — это PID оболочки)",
        "print(os.getppid() == os.getpid())   # → ? False: getppid() возвращает родителя, а getpid() — сам процесс",
        "if os.fork() == 0: print(os.getppid())   # → ? только Unix: дочерний процесс печатает PID родителя — то же число, что вернул os.getpid() до fork",
        "ppid = os.getppid()   # → ? ловушка: если родитель уже завершился, процесс-сирота «усыновляется» — на Linux обычно 1 (init/systemd)"
      ],
      "related": [
        "os.getpid",
        "os.fork",
        "os.getpgrp"
      ],
      "related_errors": []
    },
    {
      "id": "os.getpriority",
      "title": "os.getpriority",
      "kind": "function",
      "summary": {
        "ru": "Возвращает приоритет планирования (nice) процесса/группы/пользователя. Доступно на Unix.",
        "en": "Return the scheduling priority (nice) of a process/group/user. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getpriority(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getpriority",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — сигналы и приоритет",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getpriority(os.PRIO_PROCESS, 0))            # → ? nice текущего процесса, обычно 0",
        "print(os.getpriority(os.PRIO_PROCESS, os.getpid()))  # → ? то же значение: who=0 означает «текущий процесс»",
        "os.setpriority(os.PRIO_PROCESS, 0, 10)               # → ? приоритет понижен; следующий getpriority вернёт 10",
        "print(os.getpriority(os.PRIO_PGRP, 0))               # → ? наименьшее (самое приоритетное) nice во всей группе процессов",
        "os.getpriority(os.PRIO_PROCESS, 999999)              # → ProcessLookupError: процесса с таким PID нет"
      ],
      "related": [
        "os.setpriority",
        "os.nice",
        "os.sched_getscheduler"
      ],
      "related_errors": [
        "ProcessLookupError"
      ]
    },
    {
      "id": "os.getrandom",
      "title": "os.getrandom",
      "kind": "function",
      "summary": {
        "ru": "Возвращает случайные байты напрямую от системного генератора (getrandom); может блокироваться до готовности энтропии. Доступно на Linux.",
        "en": "Return random bytes directly from the system getrandom source. Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getrandom(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getrandom",
      "version": "3.6",
      "section": "Модуль os",
      "subcat": "os — случайность",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(len(os.getrandom(16)))   # → ? только Linux: 16 (на Windows/macOS функции нет — переносимый аналог os.urandom)",
        "key = os.getrandom(32, os.GRND_NONBLOCK)   # → ? только Linux: BlockingIOError, если энтропия ещё не набрана",
        "print(len(key))   # → 32",
        "print(len(os.getrandom(0)))   # → ? только Linux: 0, граничный случай — пустой запрос",
        "print(os.getrandom(4) == os.getrandom(4))   # → ? только Linux: False, каждый вызов даёт новые байты"
      ],
      "related": [
        "os.urandom",
        "random.systemrandom",
        "blockingioerror"
      ],
      "related_errors": []
    },
    {
      "id": "os.getresgid",
      "title": "os.getresgid",
      "kind": "function",
      "summary": {
        "ru": "Возвращает тройку (реальный, эффективный, сохранённый) GID. Доступно на Unix.",
        "en": "Return the (real, effective, saved) group ids. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getresgid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getresgid",
      "version": "3.2",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getresgid())  # → ? только Unix: тройка (реальный, эффективный, сохранённый) GID, например (1000, 1000, 1000)",
        "rgid, egid, sgid = os.getresgid()",
        "print(egid == os.getegid())  # → ? True: средний элемент тройки — тот же эффективный GID, что возвращает os.getegid()",
        "print(rgid == egid)  # → ? True у обычного процесса; у программы с setgid-битом эффективный GID берётся у группы-владельца файла и отличается от реального",
        "print(sgid)  # → ? сохранённый GID: значение, к которому процесс может вернуться через os.setresgid() после временного понижения прав"
      ],
      "related": [
        "os.setresgid",
        "os.getresuid",
        "os.getegid"
      ],
      "related_errors": []
    },
    {
      "id": "os.getresuid",
      "title": "os.getresuid",
      "kind": "function",
      "summary": {
        "ru": "Возвращает тройку (реальный, эффективный, сохранённый) UID. Доступно на Unix.",
        "en": "Return the (real, effective, saved) user ids. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getresuid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getresuid",
      "version": "3.2",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getresuid())  # → ? только Unix: тройка (реальный, эффективный, сохранённый) UID, например (1000, 1000, 1000)",
        "ruid, euid, suid = os.getresuid()",
        "print(ruid == os.getuid())  # → ? True: первый элемент тройки — тот же реальный UID, что возвращает os.getuid()",
        "print(euid == 0)  # → True, если процесс работает с правами root (например, запущен через sudo), иначе False",
        "os.setresuid(ruid, ruid, ruid)  # → None: необратимый сброс всех трёх UID к реальному — после того как сохранённый UID перестал быть root, вернуть права уже нельзя"
      ],
      "related": [
        "os.setresuid",
        "os.getresgid",
        "os.geteuid"
      ],
      "related_errors": []
    },
    {
      "id": "os.getsid",
      "title": "os.getsid",
      "kind": "function",
      "summary": {
        "ru": "Возвращает идентификатор сессии (SID) для заданного PID. Доступно на Unix.",
        "en": "Return the session id (SID) of a given PID. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getsid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getsid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — сессии и группы процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "sid = os.getsid(0)  # → ? только Unix: SID сессии текущего процесса, например 4390",
        "print(os.getsid(os.getpid()) == sid)  # → ? только Unix: True — pid=0 означает «текущий процесс»",
        "print(os.getsid(os.getppid()) == sid)  # → ? только Unix: True — сессия наследуется от родителя, даже если группа процессов другая",
        "print(os.getsid(0) == os.getpgrp())  # → ? только Unix: True только у лидера сессии; у обычного процесса False — сессия шире группы",
        "print(os.getsid(999999))  # → ProcessLookupError: [Errno 3] No such process"
      ],
      "related": [
        "os.setsid",
        "os.getpgid",
        "os.getpgrp"
      ],
      "related_errors": [
        "ProcessLookupError"
      ]
    },
    {
      "id": "os.getuid",
      "title": "os.getuid",
      "kind": "function",
      "summary": {
        "ru": "Возвращает реальный идентификатор пользователя (UID) процесса. Доступно на Unix.",
        "en": "Return the process's real user id (UID). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getuid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getuid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getuid())  # → ? только Unix: реальный UID текущего процесса, например 1000",
        "print(os.getuid() == 0)  # → ? True, если скрипт запущен от root (например, через sudo), иначе False",
        "print(os.getuid() == os.geteuid())  # → ? True у обычной программы; у setuid-программы реальный и эффективный UID различаются",
        "home = os.path.expanduser('~')",
        "print(os.stat(home).st_uid == os.getuid())  # → ? True: домашний каталог принадлежит текущему пользователю"
      ],
      "related": [
        "os.geteuid",
        "os.setuid",
        "os.getgid"
      ],
      "related_errors": []
    },
    {
      "id": "os.getxattr",
      "title": "os.getxattr",
      "kind": "function",
      "summary": {
        "ru": "Возвращает значение расширенного атрибута файла по имени. Доступно на Linux.",
        "en": "Return the value of a file's extended attribute by name. Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.getxattr(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.getxattr",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — расширенные атрибуты (xattr)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "os.setxattr('note.txt', 'user.author', b'ivan')   # → ? только Linux: записать расширенный атрибут (файл должен существовать, а ФС — поддерживать xattr)",
        "print(os.getxattr('note.txt', 'user.author'))   # → b'ivan' — значение всегда возвращается как bytes, не как str",
        "print(os.getxattr('note.txt', 'user.author', follow_symlinks=False))   # → ? только Linux: b'ivan'; для симлинка атрибут читался бы у самой ссылки, а не у её цели",
        "print(os.listxattr('note.txt'))   # → ['user.author'] — так узнают, какие атрибуты вообще есть у файла",
        "print(os.getxattr('note.txt', 'user.nope'))   # → OSError (Errno 61, ENODATA) — у отсутствующего атрибута нет значения по умолчанию"
      ],
      "related": [
        "os.setxattr",
        "os.listxattr",
        "os.removexattr"
      ],
      "related_errors": [
        "FileNotFoundError",
        "OSError"
      ]
    },
    {
      "id": "os.grantpt",
      "title": "os.grantpt",
      "kind": "function",
      "summary": {
        "ru": "Изменяет владельца и права slave-устройства псевдотерминала (по master-дескриптору). Доступно на Unix.",
        "en": "Grant access to the slave pseudo-terminal (by master fd). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.grantpt(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.grantpt",
      "version": "3.13",
      "section": "Модуль os",
      "subcat": "os — псевдотерминалы (pty)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "master = os.posix_openpt(os.O_RDWR)   # только Unix: открываем master-устройство псевдотерминала",
        "print(os.grantpt(master))   # → None — владелец и права slave-устройства выставлены текущему пользователю (только Unix, Python 3.13+)",
        "os.unlockpt(master)   # только Unix: порядок обязателен — сначала grantpt, потом unlockpt",
        "print(os.ptsname(master))   # → ? только Unix: путь к slave, например /dev/pts/3",
        "slave = os.open(os.ptsname(master), os.O_RDWR)   # только Unix: после grantpt + unlockpt slave открывается; без grantpt упёрлись бы в права доступа",
        "print(os.grantpt(slave))   # → OSError — дескриптор должен указывать на master, а не на slave"
      ],
      "related": [
        "os.posix_openpt",
        "os.unlockpt",
        "os.ptsname"
      ],
      "related_errors": []
    },
    {
      "id": "os.initgroups",
      "title": "os.initgroups",
      "kind": "function",
      "summary": {
        "ru": "Инициализирует дополнительные группы процесса по имени пользователя и базовому GID. Доступно на Unix.",
        "en": "Initialize the supplemental groups from a username and base GID. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.initgroups(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.initgroups",
      "version": "3.2",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "import pwd",
        "os.initgroups('www-data', 33)  # → ? только Unix: None; процесс получает все дополнительные группы пользователя www-data плюс группу с GID 33",
        "print(os.getgroups())  # → ? список GID процесса после вызова, например [33, 4, 27]",
        "entry = pwd.getpwnam('www-data')",
        "os.initgroups(entry.pw_name, entry.pw_gid)  # → None: базовый GID берут из записи пользователя, а не подставляют числом вручную",
        "os.setuid(entry.pw_uid)  # → None: штатный порядок сброса привилегий демона — initgroups(), затем setgid(), и только в конце setuid(); после него вернуть права root уже нельзя",
        "os.initgroups('www-data', 33)  # → PermissionError, если скрипт запущен не от root: менять список групп процесса может только суперпользователь"
      ],
      "related": [
        "os.setgroups",
        "os.getgrouplist",
        "os.getgroups"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.isatty",
      "title": "os.isatty",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, привязан ли файловый дескриптор к терминалу (tty).",
        "en": "Check whether a file descriptor is attached to a terminal (tty)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.isatty(fd)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.isatty",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — терминал и устройства",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()",
        "print(os.isatty(r))   # → False",
        "os.close(r)",
        "os.close(w)"
      ],
      "related": [
        "os.get_terminal_size",
        "os.device_encoding",
        "sys.stdin-sys.stdout-sys.stderr"
      ],
      "related_errors": []
    },
    {
      "id": "os.kill",
      "title": "os.kill",
      "kind": "function",
      "summary": {
        "ru": "Посылает сигнал процессу по PID. Доступно на Unix.",
        "en": "Send a signal to a process by PID. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.kill(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.kill",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — сигналы и приоритет",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "import signal",
        "pid = os.fork()                        # → ? только Unix: 0 в дочернем процессе, PID ребёнка — в родительском",
        "if pid: os.kill(pid, signal.SIGTERM)   # → ? родитель вежливо просит ребёнка завершиться, возвращается None",
        "if pid: os.kill(pid, 0)                # → ? «нулевой» сигнал ничего не шлёт: так проверяют, жив ли процесс",
        "if pid: os.kill(pid, signal.SIGKILL)   # → ? только Unix: безусловное убийство, обработчик его перехватить не может",
        "os.kill(999999, signal.SIGTERM)        # → ProcessLookupError: процесса с таким PID нет"
      ],
      "related": [
        "os.killpg",
        "os.getpid",
        "os.waitpid",
        "processlookuperror"
      ],
      "related_errors": [
        "ProcessLookupError",
        "PermissionError"
      ]
    },
    {
      "id": "os.killpg",
      "title": "os.killpg",
      "kind": "function",
      "summary": {
        "ru": "Посылает сигнал всей группе процессов. Доступно на Unix.",
        "en": "Send a signal to a whole process group. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.killpg(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.killpg",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — сигналы и приоритет",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "import signal",
        "pgid = os.getpgid(0)   # → ? только Unix: id группы текущего процесса, например 12345",
        "os.killpg(pgid, 0)   # → ? только Unix: сигнал не посылается, проверяется лишь существование группы",
        "import subprocess; proc = subprocess.Popen(['sleep', '60'], start_new_session=True)   # → ? потомок запущен в собственной группе процессов",
        "os.killpg(os.getpgid(proc.pid), signal.SIGTERM)   # → ? только Unix: завершает всю группу потомка целиком (os.kill снял бы лишь один процесс)",
        "os.killpg(999999, signal.SIGTERM)   # → ProcessLookupError: группы процессов с таким id нет"
      ],
      "related": [
        "os.kill",
        "os.getpgid",
        "os.setpgid"
      ],
      "related_errors": [
        "ProcessLookupError",
        "PermissionError"
      ]
    },
    {
      "id": "os.lchmod",
      "title": "os.lchmod()",
      "kind": "function",
      "summary": {
        "ru": "Меняет права самой символической ссылки, не разыменовывая её; эквивалент os.chmod(path, mode, follow_symlinks=False). Есть на macOS/BSD и на Windows (3.13+), на Linux отсутствует.",
        "en": "Changes the mode of the symlink itself rather than its target; equivalent to os.chmod(path, mode, follow_symlinks=False). Available on macOS/BSD and on Windows (3.13+), not on Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.lchmod(path, mode)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.lchmod",
      "version": "",
      "section": "Модуль os",
      "subcat": "права доступа",
      "color_group": "module",
      "aliases": [
        "права символьной ссылки",
        "сменить права ссылки",
        "не менять права цели ссылки"
      ],
      "keywords": [
        "os.lchmod",
        "lchmod"
      ],
      "tags": [
        "os"
      ],
      "examples": [
        "import os, stat",
        "print(hasattr(os, 'chmod'))  # → True (os.chmod есть везде, os.lchmod — не везде)",
        "print(callable(getattr(os, 'lchmod', os.chmod)))  # → True (на Linux берём запасной os.chmod)",
        "print(oct(stat.S_IMODE(0o100644)))  # → 0o644 (mode задаётся восьмеричным числом)",
        "print(stat.S_IMODE(0o100755) == 0o755)  # → True",
        "# os.lchmod(path, 0o644) ≡ os.chmod(path, 0o644, follow_symlinks=False)"
      ],
      "related": [
        "os.chmod",
        "os.lchown",
        "os.symlink",
        "os.lstat"
      ],
      "related_errors": [
        "OSError",
        "PermissionError",
        "NotImplementedError",
        "AttributeError"
      ]
    },
    {
      "id": "os.lchown",
      "title": "os.lchown",
      "kind": "function",
      "summary": {
        "ru": "Как chown, но для самой символической ссылки, не следуя по ней. Доступно на Unix.",
        "en": "Like chown, but affects the symlink itself, not its target. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.lchown(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.lchown",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — права и владелец",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "os.symlink('target.txt', 'link.txt')  # → ? только Unix: создаём символическую ссылку для опытов",
        "os.lchown('link.txt', os.getuid(), os.getgid())  # → ? только Unix: меняется владелец САМОЙ ссылки, файл target.txt не тронут",
        "os.chown('link.txt', os.getuid(), os.getgid())  # → ? только Unix: в отличие от lchown идёт ПО ссылке и правит владельца target.txt",
        "print(os.lstat('link.txt').st_uid == os.getuid())  # → ? True: lstat, как и lchown, смотрит на саму ссылку, а не на цель",
        "os.lchown('нет-такой-ссылки', -1, -1)  # → FileNotFoundError (пара -1, -1 не меняет ничего, но путь всё равно проверяется)"
      ],
      "related": [
        "os.chown",
        "os.fchown",
        "os.lstat",
        "os.symlink"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.link",
      "title": "os.link",
      "kind": "function",
      "summary": {
        "ru": "Создаёт жёсткую ссылку на файл (второе имя того же inode).",
        "en": "Create a hard link to a file."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.link(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.link",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — ссылки",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "fd, p = tempfile.mkstemp()",
        "os.close(fd)",
        "p2 = p + '_l'",
        "os.link(p, p2)",
        "print(os.path.exists(p2))   # → True",
        "os.remove(p2)",
        "os.remove(p)"
      ],
      "related": [
        "os.symlink",
        "os.unlink",
        "os.path.samefile"
      ],
      "related_errors": [
        "FileNotFoundError",
        "FileExistsError",
        "PermissionError"
      ]
    },
    {
      "id": "os.listdir",
      "title": "os.listdir()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает список имён файлов и папок в указанной директории (без сортировки). По умолчанию — текущая директория.",
        "en": "Returns the list of file and folder names in the given directory (unsorted). The current directory by default."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.listdir(path='.') -> list[str]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.listdir",
      "version": "",
      "section": "Модуль os",
      "subcat": "директории",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import os",
        "files = os.listdir('.')  # → ['file.py', 'data.csv', ...]",
        "py_files = [f for f in os.listdir('.') if f.endswith('.py')]  # → все .py файлы",
        "print(os.listdir('/tmp'))  # → содержимое /tmp",
        "print(len(os.listdir('.')))  # → кол-во объектов в директории",
        "sorted_files = sorted(os.listdir('.'))  # → отсортированный список"
      ],
      "related": [
        "os-scandir",
        "os.walk",
        ".iterdir",
        "path.glob"
      ],
      "related_errors": [
        "FileNotFoundError",
        "NotADirectoryError",
        "PermissionError"
      ]
    },
    {
      "id": "os.listdrives",
      "title": "os.listdrives()",
      "kind": "function",
      "summary": {
        "ru": "Только Windows: возвращает список имён дисков системы (вида C:\\ , D:\\ ); доступность диска не проверяется. Python 3.12+.",
        "en": "Windows only: returns a list of drive names on the system (like C:\\ or D:\\ ); the function does not test drives for accessibility. Python 3.12+."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.listdrives()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.listdrives",
      "version": "3.12",
      "section": "Модуль os",
      "subcat": "файловая система",
      "color_group": "module",
      "aliases": [
        "список дисков",
        "перечислить диски системы",
        "какие диски есть в системе"
      ],
      "keywords": [
        "os.listdrives",
        "listdrives"
      ],
      "tags": [
        "os"
      ],
      "examples": [
        "import os, sys",
        "print(hasattr(os, 'listdrives') == (os.name == 'nt' and sys.version_info >= (3, 12)))  # → True (только Windows, 3.12+)",
        "drives = os.listdrives() if hasattr(os, 'listdrives') else []",
        "print(type(drives).__name__)  # → list",
        "print(all(isinstance(d, str) for d in drives))  # → True",
        "print(len(drives) > 0 or os.name != 'nt')  # → True (на Windows всегда есть хотя бы системный диск)"
      ],
      "related": [
        "os.listdir",
        "os.path.splitdrive",
        "os.path.ismount",
        "os.walk"
      ],
      "related_errors": [
        "OSError",
        "AttributeError"
      ]
    },
    {
      "id": "os.listmounts",
      "title": "os.listmounts()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает список точек монтирования тома; том задаётся GUID-путём из os.listvolumes(). Только Windows, Python 3.12+.",
        "en": "Returns the list of mount points for a volume; the volume is given as a GUID path from os.listvolumes(). Windows only, Python 3.12+."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.listmounts(volume)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.listmounts",
      "version": "3.12",
      "section": "Модуль os",
      "subcat": "os — файловая система",
      "color_group": "module",
      "aliases": [
        "точки монтирования тома",
        "список точек монтирования",
        "куда примонтирован том"
      ],
      "keywords": [
        "os.listmounts",
        "listmounts"
      ],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(not hasattr(os, 'listmounts') or callable(os.listmounts))   # → True",
        "print(hasattr(os, 'listmounts') == hasattr(os, 'listvolumes'))   # → True",
        "vols = os.listvolumes() if hasattr(os, 'listvolumes') else []",
        "mounts = [m for v in vols for m in os.listmounts(v)]",
        "print(all(isinstance(m, str) for m in mounts))   # → True"
      ],
      "related": [
        "os.path.ismount",
        "os.path.splitdrive",
        "os.listdir",
        "os.path.isdevdrive"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.listvolumes",
      "title": "os.listvolumes()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает список томов системы в виде GUID-путей; человекопонятные пути к ним даёт os.listmounts(). Только Windows, Python 3.12+.",
        "en": "Returns the list of volumes in the system as GUID paths; use os.listmounts() to get their mount points. Windows only, Python 3.12+."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.listvolumes()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.listvolumes",
      "version": "3.12",
      "section": "Модуль os",
      "subcat": "os — файловая система",
      "color_group": "module",
      "aliases": [
        "список томов системы",
        "перечислить тома в системе",
        "идентификаторы томов"
      ],
      "keywords": [
        "os.listvolumes",
        "listvolumes"
      ],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(not hasattr(os, 'listvolumes') or callable(os.listvolumes))   # → True",
        "print('listvolumes' in dir(os) or os.name != 'nt')   # → True",
        "vols = os.listvolumes() if hasattr(os, 'listvolumes') else []",
        "print(isinstance(vols, list))   # → True",
        "print(all(isinstance(v, str) for v in vols))   # → True"
      ],
      "related": [
        "os.path.splitdrive",
        "os.path.ismount",
        "os.listdir",
        "os.path.isdevdrive"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.listxattr",
      "title": "os.listxattr",
      "kind": "function",
      "summary": {
        "ru": "Возвращает список имён расширенных атрибутов файла. Доступно на Linux.",
        "en": "Return the list of a file's extended attribute names. Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.listxattr(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.listxattr",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — расширенные атрибуты (xattr)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "open('xattr_demo.txt', 'w').close()",
        "os.setxattr('xattr_demo.txt', 'user.author', b'anna')   # → ? только Linux: записали расширенный атрибут (имя обязано начинаться с user.)",
        "print(os.listxattr('xattr_demo.txt'))   # → ['user.author']",
        "print(os.listxattr())   # → ? только Linux: без аргумента смотрит текущий каталог, обычно []",
        "print(os.listxattr('нет-такого'))   # → FileNotFoundError"
      ],
      "related": [
        "os.getxattr",
        "os.setxattr",
        "os.removexattr"
      ],
      "related_errors": [
        "FileNotFoundError",
        "OSError"
      ]
    },
    {
      "id": "os.lockf",
      "title": "os.lockf",
      "kind": "function",
      "summary": {
        "ru": "Применяет или снимает POSIX-блокировку на область файла (по дескриптору). Доступно на Unix.",
        "en": "Apply or release a POSIX lock on a file region (by fd). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.lockf(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.lockf",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — блокировки",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/tmp/counter.dat', os.O_RDWR | os.O_CREAT)   # → ? только Unix: дескриптор файла, который будем блокировать",
        "os.lockf(fd, os.F_LOCK, 0)   # → ? только Unix: захватывает файл целиком (len=0), ждёт, пока его освободит другой процесс",
        "os.lockf(fd, os.F_ULOCK, 0)   # → ? только Unix: снимает блокировку, ничего не возвращает",
        "os.lockf(fd, os.F_TLOCK, 100)   # → ? только Unix: пробует занять 100 байт от текущей позиции без ожидания; занято другим процессом — BlockingIOError",
        "os.close(fd)   # → ? только Unix: закрытие дескриптора тоже снимает все блокировки процесса на этом файле"
      ],
      "related": [],
      "related_errors": []
    },
    {
      "id": "os.login_tty",
      "title": "os.login_tty",
      "kind": "function",
      "summary": {
        "ru": "Делает указанный дескриптор управляющим терминалом текущего процесса (для сессии). Доступно на Unix.",
        "en": "Make a file descriptor the controlling terminal of the process. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.login_tty(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.login_tty",
      "version": "3.11",
      "section": "Модуль os",
      "subcat": "os — псевдотерминалы (pty)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "master, slave = os.openpty()   # только Unix: пара дескрипторов псевдотерминала",
        "pid = os.fork()   # только Unix: 0 в потомке, pid потомка в родителе",
        "if pid == 0: os.login_tty(slave); print('hello'); os._exit(0)   # → ? только Unix: потомок стал лидером сессии, slave стал его управляющим терминалом и stdin/stdout/stderr (и сам дескриптор закрыт), печать уходит в псевдотерминал",
        "print(os.read(master, 1024))   # → b'hello\\r\\n' — родитель читает вывод потомка, терминал заменяет \\n на \\r\\n",
        "r, w = os.pipe()   # обычный канал, не терминал",
        "print(os.login_tty(r))   # → OSError — годится только дескриптор терминала"
      ],
      "related": [
        "os.openpty",
        "os.setsid",
        "os.ctermid"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.lseek",
      "title": "os.lseek",
      "kind": "function",
      "summary": {
        "ru": "Перемещает позицию чтения/записи дескриптора (SEEK_SET/CUR/END) и возвращает новую позицию.",
        "en": "Move a descriptor's read/write position and return the new offset."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.lseek(fd, pos, whence)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.lseek",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "fd = os.open(os.__file__, os.O_RDONLY)",
        "os.lseek(fd, 0, os.SEEK_END)",
        "print(os.lseek(fd, 0, os.SEEK_CUR) > 0)   # → True",
        "os.close(fd)"
      ],
      "related": [
        "os.read",
        "os.write",
        "file-tell"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.lstat",
      "title": "os.lstat",
      "kind": "function",
      "summary": {
        "ru": "Как stat, но не следует по символической ссылке (описывает саму ссылку).",
        "en": "Like stat, but does not follow symlinks (describes the link itself)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.lstat(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.lstat",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловая система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import stat",
        "print(os.lstat(os.__file__).st_size > 0)   # → True",
        "print(os.lstat(os.__file__).st_size == os.stat(os.__file__).st_size)   # → True — на обычном файле lstat и stat дают одно и то же",
        "os.symlink(os.__file__, 'ссылка-на-os.py')   # → создана символическая ссылка (на Windows нужны права разработчика)",
        "print(stat.S_ISLNK(os.lstat('ссылка-на-os.py').st_mode), stat.S_ISLNK(os.stat('ссылка-на-os.py').st_mode))   # → True False — lstat видит саму ссылку, stat идёт по ней до цели",
        "os.remove('ссылка-на-os.py')   # → временная ссылка удалена",
        "print(os.lstat('нет-такого-файла.txt'))   # → FileNotFoundError"
      ],
      "related": [
        "os.stat",
        "os.stat_result",
        "os.path.islink",
        "os.readlink"
      ],
      "related_errors": [
        "FileNotFoundError"
      ]
    },
    {
      "id": "os.major",
      "title": "os.major",
      "kind": "function",
      "summary": {
        "ru": "Извлекает старший номер (major) из идентификатора устройства (напр. из st_rdev). Доступно на Unix.",
        "en": "Extract the major device number from a raw device id. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.major(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.major",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — номера устройств",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "st = os.stat('/dev/null')",
        "print(os.major(st.st_rdev))  # → ? только Unix: старший номер устройства /dev/null, в Linux 1",
        "print(f'{os.major(st.st_rdev)}:{os.minor(st.st_rdev)}')  # → ? только Unix: пара номеров, как в колонке ls -l /dev/null, в Linux 1:3",
        "print(os.major(os.makedev(8, 1)))  # → 8 — major достаёт обратно то, что упаковала makedev",
        "print(os.major(os.stat('/tmp').st_rdev))  # → 0 — у обычного файла или каталога st_rdev нулевой: это не устройство"
      ],
      "related": [
        "os.minor",
        "os.makedev",
        "os.stat_result"
      ],
      "related_errors": []
    },
    {
      "id": "os.makedev",
      "title": "os.makedev",
      "kind": "function",
      "summary": {
        "ru": "Собирает идентификатор устройства из старшего и младшего номеров (обратное major/minor). Доступно на Unix.",
        "en": "Compose a raw device id from major and minor numbers. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.makedev(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.makedev",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — номера устройств",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "dev = os.makedev(8, 1)  # 8:1 — первый раздел первого SATA/SCSI-диска",
        "print(dev)  # → ? только Unix: упакованный идентификатор устройства; кодировка зависит от ядра (в Linux — 2049)",
        "print(os.major(dev), os.minor(dev))  # → ? 8 1 — makedev и major/minor строго обратны друг другу",
        "st = os.stat('/dev/null')",
        "print(os.makedev(os.major(st.st_rdev), os.minor(st.st_rdev)) == st.st_rdev)  # → True — разобрали и собрали настоящий st_rdev без потерь",
        "print(os.makedev(0, 0))  # → 0 — нулевой идентификатор: ровно такой st_rdev у обычных файлов, у которых устройства нет"
      ],
      "related": [
        "os.major",
        "os.minor",
        "os.mknod"
      ],
      "related_errors": []
    },
    {
      "id": "os.makedirs",
      "title": "os.makedirs",
      "kind": "function",
      "summary": {
        "ru": "Рекурсивно создаёт каталог и все промежуточные родительские.",
        "en": "Recursively create a directory and any missing parents."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.makedirs(name, mode=0o777, exist_ok=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.makedirs",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — каталоги",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "base = tempfile.mkdtemp()",
        "d = os.path.join(base, 'a', 'b')",
        "os.makedirs(d)",
        "print(os.path.isdir(d))   # → True",
        "os.removedirs(d)",
        "os.makedirs('a/b/c', exist_ok=True)  # → создаёт вложенные папки",
        "os.makedirs('new_dir', exist_ok=True)  # → не ошибка если папка уже есть"
      ],
      "related": [
        "os.rmdir",
        "os.removedirs",
        "path.mkdir"
      ],
      "related_errors": [
        "FileExistsError",
        "PermissionError"
      ]
    },
    {
      "id": "os.memfd_create",
      "title": "os.memfd_create",
      "kind": "function",
      "summary": {
        "ru": "Создаёт анонимный файл в памяти и возвращает его дескриптор (Python 3.8+). Доступно на Linux.",
        "en": "Create an anonymous in-memory file and return its descriptor (3.8+). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.memfd_create(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.memfd_create",
      "version": "3.8",
      "section": "Модуль os",
      "subcat": "os — спец. дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.memfd_create('buf')   # → ? только Linux: дескриптор анонимного файла в памяти, например 3",
        "print(os.write(fd, b'hello'))   # → 5",
        "print(os.pread(fd, 5, 0))   # → b'hello' — читаем с нулевого смещения, не двигая курсор",
        "print(os.fstat(fd).st_size)   # → 5 — ведёт себя как обычный файл, но живёт только в памяти",
        "os.close(fd)   # → ? только Linux: буфер освобождён; пути в файловой системе у memfd нет, на диске следов не остаётся"
      ],
      "related": [
        "os.pipe",
        "os.eventfd",
        "os.fdopen"
      ],
      "related_errors": []
    },
    {
      "id": "os.minor",
      "title": "os.minor",
      "kind": "function",
      "summary": {
        "ru": "Извлекает младший номер (minor) из идентификатора устройства. Доступно на Unix.",
        "en": "Extract the minor device number from a raw device id. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.minor(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.minor",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — номера устройств",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "info = os.stat('/dev/null')",
        "print(os.minor(info.st_rdev))  # → ? только Unix: младший номер устройства /dev/null, обычно 3",
        "print(os.major(info.st_rdev))  # → ? только Unix: старший номер того же устройства, обычно 1",
        "print(os.minor(os.makedev(8, 1)))  # → 1 — makedev собирает номер из (major, minor), minor разбирает обратно",
        "print(os.minor(os.stat('/tmp').st_rdev))  # → 0 — у обычного файла или каталога st_rdev нулевой, minor осмыслен лишь для файлов устройств"
      ],
      "related": [
        "os.major",
        "os.makedev",
        "os.stat_result"
      ],
      "related_errors": []
    },
    {
      "id": "os.mkdir",
      "title": "os.mkdir",
      "kind": "function",
      "summary": {
        "ru": "Создаёт один каталог по указанному пути.",
        "en": "Create a single directory at the given path."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.mkdir(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.mkdir",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — каталоги",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "base = tempfile.mkdtemp()",
        "d = os.path.join(base, 'sub')",
        "os.mkdir(d)",
        "print(os.path.isdir(d))   # → True",
        "os.rmdir(d)",
        "os.rmdir(base)"
      ],
      "related": [
        "os.makedirs",
        "os.rmdir",
        "path.mkdir"
      ],
      "related_errors": [
        "FileExistsError",
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.mkfifo",
      "title": "os.mkfifo",
      "kind": "function",
      "summary": {
        "ru": "Создаёт именованный канал (FIFO). Доступно на Unix.",
        "en": "Create a named pipe (FIFO). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.mkfifo(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.mkfifo",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — спецфайлы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os, stat",
        "os.mkfifo('demo.fifo')   # → ? только Unix: в файловой системе появился именованный канал demo.fifo",
        "print(stat.S_ISFIFO(os.stat('demo.fifo').st_mode))   # → True — это FIFO, а не обычный файл",
        "os.mkfifo('private.fifo', 0o600)   # → ? только Unix: канал, доступный на чтение и запись только владельцу",
        "fd = os.open('demo.fifo', os.O_RDONLY | os.O_NONBLOCK)   # → ? только Unix: обычный open() на чтение ждал бы, пока другой процесс не откроет канал на запись",
        "os.mkfifo('demo.fifo')   # → FileExistsError: [Errno 17] File exists: 'demo.fifo'"
      ],
      "related": [
        "os.mknod",
        "os.pipe",
        "open"
      ],
      "related_errors": [
        "FileExistsError",
        "PermissionError"
      ]
    },
    {
      "id": "os.mknod",
      "title": "os.mknod",
      "kind": "function",
      "summary": {
        "ru": "Создаёт узел файловой системы (спецфайл устройства, FIFO и т.п.). Доступно на Unix.",
        "en": "Create a filesystem node (device special file, FIFO, etc.). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.mknod(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.mknod",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — спецфайлы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os, stat",
        "os.mknod('plain.txt')   # → ? только Unix: создан пустой обычный файл с режимом по умолчанию 0o600",
        "os.mknod('node.fifo', stat.S_IFIFO | 0o600)   # → ? только Unix: тип узла задаётся битами режима — здесь получился FIFO, как от os.mkfifo",
        "print(stat.S_ISFIFO(os.stat('node.fifo').st_mode))   # → True",
        "os.mknod('blockdev', stat.S_IFBLK | 0o600, os.makedev(8, 0))   # → ? только Unix и только под root: узел блочного устройства major=8, minor=0; без прав — PermissionError",
        "os.mknod('node.fifo', stat.S_IFIFO | 0o600)   # → FileExistsError: [Errno 17] File exists: 'node.fifo'"
      ],
      "related": [
        "os.mkfifo",
        "os.makedev",
        "os.major"
      ],
      "related_errors": [
        "PermissionError",
        "FileExistsError"
      ]
    },
    {
      "id": "os.nice",
      "title": "os.nice",
      "kind": "function",
      "summary": {
        "ru": "Увеличивает «любезность» (nice) процесса, понижая его приоритет планирования. Доступно на Unix.",
        "en": "Increase a process's niceness, lowering its scheduling priority. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.nice(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.nice",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — сигналы и приоритет",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.nice(0))   # → ? только Unix: текущее значение nice, у обычного процесса 0",
        "print(os.nice(5))   # → 5 — приоритет понижен на 5 пунктов, возвращается новое значение",
        "print(os.nice(3))   # → 8 — инкременты складываются, значение не задаётся напрямую",
        "print(os.nice(19))   # → 19 — nice упирается в максимум, 8 + 19 не даёт 27",
        "os.nice(-1)   # → ? только Unix: PermissionError без прав root — вернуть приоритет обратно нельзя"
      ],
      "related": [
        "os.setpriority",
        "os.getpriority",
        "os.sched_setscheduler"
      ],
      "related_errors": []
    },
    {
      "id": "os.openpty",
      "title": "os.openpty",
      "kind": "function",
      "summary": {
        "ru": "Открывает новую пару псевдотерминала: возвращает дескрипторы (master, slave). Доступно на Unix.",
        "en": "Open a new pseudo-terminal pair; returns (master, slave) fds. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.openpty(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.openpty",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — псевдотерминалы (pty)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "master, slave = os.openpty()  # → ? только Unix: пара дескрипторов псевдотерминала, например (3, 4)",
        "print(os.isatty(slave))  # → True — для программы slave-конец выглядит настоящим терминалом",
        "print(os.ttyname(slave))  # → ? только Unix: путь slave-устройства, например /dev/pts/3",
        "os.write(slave, b'hello\\n')  # → 6 — «программа» напечатала строку в свой терминал",
        "print(os.read(master, 1024))  # → b'hello\\r\\n' — драйвер pty заменил \\n на \\r\\n"
      ],
      "related": [
        "os.forkpty",
        "os.posix_openpt",
        "os.login_tty",
        "os.ptsname"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.abspath",
      "title": "os.path.abspath",
      "kind": "function",
      "summary": {
        "ru": "Возвращает нормализованный абсолютный путь (относительный достраивается от текущего каталога).",
        "en": "Return a normalized absolute version of a path."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.abspath(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.abspath",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — абсолютизация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.isabs(os.path.abspath('x')))   # → True",
        "print(os.path.abspath('data/notes.txt').endswith(os.path.join('data', 'notes.txt')))   # → True (путь достроен от текущего каталога)",
        "print(os.path.abspath('.') == os.getcwd())   # → True",
        "print(os.path.abspath('a/b/../c') == os.path.join(os.getcwd(), 'a', 'c'))   # → True (abspath заодно нормализует '..')",
        "print(os.path.exists(os.path.abspath('нет-такого-файла')))   # → False (abspath строит строку и не проверяет, существует ли путь)"
      ],
      "related": [
        "os.path.realpath",
        "os.path.normpath",
        "os.path.isabs",
        "os.path.relpath"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.basename",
      "title": "os.path.basename",
      "kind": "function",
      "summary": {
        "ru": "Возвращает последний компонент пути (имя файла/каталога).",
        "en": "Return the final component of a path (the file/dir name)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.basename(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.basename",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — разбор пути",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.basename('/a/b/c.txt'))   # → c.txt",
        "path = '/home/user/file.txt'",
        "print(os.path.basename(path))  # → file.txt",
        "print(os.path.basename('/home/user/'))  # → '' (пустой хвост)"
      ],
      "related": [
        "os.path.dirname",
        "os.path.split"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.commonpath",
      "title": "os.path.commonpath",
      "kind": "function",
      "summary": {
        "ru": "Возвращает самый длинный общий подпуть списка путей — по компонентам (в отличие от commonprefix).",
        "en": "Return the longest common sub-path of a list — component-wise."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.commonpath(paths)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.commonpath",
      "version": "3.5",
      "section": "Модуль os",
      "subcat": "os.path — разбор пути",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.commonpath(['/usr/a', '/usr/b']).endswith('usr'))   # → True",
        "print(os.path.commonpath(['a/b/c', 'a/b/d']) == os.path.join('a', 'b'))   # → True",
        "files = ['/var/log/app/a.log', '/var/log/app/b.log', '/var/log/db/c.log']",
        "print(os.path.commonpath(files).endswith(os.path.join('var', 'log')))   # → True",
        "print(os.path.commonprefix(['/usr/lib', '/usr/local']))   # → /usr/l",
        "print(os.path.commonpath(['/usr', 'usr']))   # → ValueError"
      ],
      "related": [
        "os.path.commonprefix",
        "os.path.relpath",
        "os.path.normpath"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "os.path.commonprefix",
      "title": "os.path.commonprefix",
      "kind": "function",
      "summary": {
        "ru": "Возвращает самый длинный общий префикс строк списка — посимвольно (не по компонентам пути!).",
        "en": "Return the longest common prefix of a list of strings — character-wise, not path-aware."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.commonprefix(list)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.commonprefix",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — разбор пути",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.commonprefix(['abcd', 'abef']))   # → ab",
        "print(os.path.commonprefix(['flower', 'flow', 'flight']))   # → fl",
        "print(os.path.commonprefix(['/usr/lib', '/usr/lib64']))   # → /usr/lib",
        "print(os.path.commonpath(['/usr/lib', '/usr/lib64']).endswith('usr'))   # → True",
        "print(repr(os.path.commonprefix([])))   # → ''"
      ],
      "related": [
        "os.path.commonpath",
        "os.path.normpath",
        "str.startswith"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.dirname",
      "title": "os.path.dirname",
      "kind": "function",
      "summary": {
        "ru": "Возвращает путь без последнего компонента (родительский каталог).",
        "en": "Return the path without its final component (the parent directory)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.dirname(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.dirname",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — разбор пути",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.dirname('/a/b/c.txt'))   # → /a/b",
        "path = '/home/user/file.txt'",
        "print(os.path.dirname(path))  # → /home/user",
        "print(os.path.dirname('file.txt'))  # → '' (без каталога)"
      ],
      "related": [
        "os.path.basename",
        "os.path.split"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.exists",
      "title": "os.path.exists",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, существует ли путь (следуя символическим ссылкам).",
        "en": "Check whether a path exists (following symlinks)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.exists(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.exists",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — проверки существования и типа",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.exists(os.__file__))   # → True",
        "print(os.path.exists('/tmp'))  # → True",
        "print(os.path.exists('/nonexistent'))  # → False"
      ],
      "related": [
        "os.path.isfile",
        "os.path.isdir",
        "path.exists"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.expanduser",
      "title": "os.path.expanduser",
      "kind": "function",
      "summary": {
        "ru": "Заменяет ведущий `~` (или `~user`) на домашний каталог пользователя; без `~` путь не меняется.",
        "en": "Expand a leading `~` (or `~user`) to the user's home directory."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.expanduser(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.expanduser",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — абсолютизация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.expanduser('foo'))   # → foo",
        "print(os.path.expanduser('~'))   # → домашний каталог пользователя, например /home/user",
        "cfg = os.path.expanduser('~/.config/app.json')",
        "print(os.path.isabs(cfg))   # → True (типовой приём: путь к конфигу в домашнем каталоге вместо жёсткого /home/...)",
        "print(os.path.expanduser('/tmp/~/x'))   # → /tmp/~/x (раскрывается только ведущий '~', внутри пути он остаётся символом)",
        "print(os.path.expanduser('~несуществующий/x'))   # → только Unix: '~несуществующий/x' без изменений — такого пользователя нет, исключение не бросается"
      ],
      "related": [
        "os.path.expandvars",
        "path.home",
        "os.path.abspath"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.expandvars",
      "title": "os.path.expandvars",
      "kind": "function",
      "summary": {
        "ru": "Подставляет значения переменных окружения вида `$VAR`/`${VAR}` в строке пути.",
        "en": "Expand environment variables of the form `$VAR`/`${VAR}` in a path."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.expandvars(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.expandvars",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — абсолютизация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.expandvars('no vars here'))   # → no vars here",
        "os.environ['PROJECT'] = '/srv/app'",
        "print(os.path.expandvars('$PROJECT/logs'))   # → /srv/app/logs",
        "print(os.path.expandvars('${PROJECT}_backup'))   # → /srv/app_backup",
        "print(os.path.expandvars('$NO_SUCH_VAR/data'))   # → $NO_SUCH_VAR/data (неизвестная переменная остаётся в строке как есть)",
        "print(os.path.expandvars('%PROJECT%/logs'))   # → только Windows: /srv/app/logs; на Unix строка возвращается без изменений"
      ],
      "related": [
        "os.path.expanduser",
        "os.environ",
        "os.getenv"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.getatime",
      "title": "os.path.getatime",
      "kind": "function",
      "summary": {
        "ru": "Возвращает время последнего доступа к файлу (секунды с эпохи, float).",
        "en": "Return the last-access time of a file (seconds since the epoch)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.getatime(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.getatime",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — метаданные (stat)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import time",
        "print(os.path.getatime(os.__file__) > 0)   # → True",
        "print(os.path.getatime(os.__file__) == os.stat(os.__file__).st_atime)   # → True: getatime — это просто os.stat(path).st_atime",
        "print(time.ctime(os.path.getatime(os.__file__)))   # → время последнего доступа в читаемом виде, например Tue Jul 21 10:15:03 2026 (на ФС с noatime/relatime оно не обновляется при каждом чтении)",
        "print(os.path.getatime(os.__file__) <= time.time())   # → True: время доступа не может быть в будущем",
        "print(os.path.getatime('нет-такого-файла'))   # → FileNotFoundError"
      ],
      "related": [
        "os.path.getmtime",
        "os.path.getctime",
        "os.stat",
        "os.utime"
      ],
      "related_errors": [
        "FileNotFoundError"
      ]
    },
    {
      "id": "os.path.getctime",
      "title": "os.path.getctime",
      "kind": "function",
      "summary": {
        "ru": "Возвращает время последнего изменения метаданных (POSIX) или создания (Windows) файла.",
        "en": "Return the metadata-change time (POSIX) or creation time (Windows) of a file."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.getctime(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.getctime",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — метаданные (stat)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import time",
        "print(os.path.getctime(os.__file__) > 0)   # → True",
        "print(os.path.getctime(os.__file__) == os.stat(os.__file__).st_ctime)   # → True: getctime — это os.stat(path).st_ctime",
        "print(time.ctime(os.path.getctime(os.__file__)))   # → на Windows это время создания файла, на POSIX — время последнего изменения метаданных, например Tue Jul 21 10:15:03 2026",
        "print(os.path.getctime('нет-такого-файла'))   # → FileNotFoundError"
      ],
      "related": [
        "os.path.getmtime",
        "os.path.getatime",
        "os.stat"
      ],
      "related_errors": [
        "FileNotFoundError"
      ]
    },
    {
      "id": "os.path.getmtime",
      "title": "os.path.getmtime",
      "kind": "function",
      "summary": {
        "ru": "Возвращает время последнего изменения файла (секунды с эпохи, float).",
        "en": "Return the last-modification time of a file (seconds since the epoch)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.getmtime(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.getmtime",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — метаданные (stat)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import time",
        "print(os.path.getmtime(os.__file__) > 0)   # → True",
        "print(os.path.getmtime(os.__file__) == os.stat(os.__file__).st_mtime)   # → True: getmtime — это os.stat(path).st_mtime",
        "print(os.path.getmtime(os.__file__) <= time.time())   # → True: файл изменён не позже текущего момента",
        "print(time.strftime('%Y-%m-%d', time.localtime(os.path.getmtime(os.__file__))))   # → дата последнего изменения, например 2026-05-14",
        "print(os.path.getmtime('нет-такого-файла'))   # → FileNotFoundError"
      ],
      "related": [
        "os.path.getatime",
        "os.path.getctime",
        "os.stat",
        "os.utime"
      ],
      "related_errors": [
        "FileNotFoundError"
      ]
    },
    {
      "id": "os.path.getsize",
      "title": "os.path.getsize",
      "kind": "function",
      "summary": {
        "ru": "Возвращает размер файла в байтах.",
        "en": "Return the size of a file in bytes."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.getsize(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.getsize",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — метаданные (stat)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "print(os.path.getsize(os.__file__) > 0)   # → True",
        "print(os.path.getsize(os.__file__) == os.stat(os.__file__).st_size)   # → True: getsize — это os.stat(path).st_size",
        "tmp = os.path.join(tempfile.gettempdir(), 'grader_demo.txt')",
        "with open(tmp, 'w', encoding='utf-8') as f: f.write('hello')",
        "print(os.path.getsize(tmp))   # → 5",
        "print(round(os.path.getsize(os.__file__) / 1024, 1))   # → размер в килобайтах, например 42.3",
        "print(os.path.getsize(os.path.dirname(os.__file__)))   # → служебный размер записи каталога (на Windows обычно 0), а НЕ суммарный размер файлов внутри"
      ],
      "related": [
        "os.stat",
        "os.path.getmtime",
        "os.path.isfile"
      ],
      "related_errors": [
        "FileNotFoundError"
      ]
    },
    {
      "id": "os.path.isabs",
      "title": "os.path.isabs",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, является ли путь абсолютным.",
        "en": "Check whether a path is absolute."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.isabs(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.isabs",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — абсолютизация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.isabs('relative/path'))   # → False",
        "print(os.path.isabs(os.getcwd()))   # → True (текущий каталог всегда абсолютный)",
        "p = 'data/notes.txt'",
        "print(os.path.isabs(p), os.path.isabs(os.path.abspath(p)))   # → False True (типовая связка: проверил — и достроил через abspath)",
        "print(os.path.isabs(''))   # → False (пустая строка путём не считается)",
        "print(os.path.isabs('/usr/bin'))   # → на Unix True; на Windows False — там абсолютный путь начинается с буквы диска, 'C:\\\\usr\\\\bin'"
      ],
      "related": [
        "os.path.abspath",
        "os.path.relpath",
        "os.path.normpath"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.isdevdrive",
      "title": "os.path.isdevdrive",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, расположен ли путь на Dev Drive (функция Windows; на других ОС всегда False; Python 3.12+).",
        "en": "Check whether a path is on a Dev Drive (a Windows feature; False elsewhere; 3.12+)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.isdevdrive(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.isdevdrive",
      "version": "3.12",
      "section": "Модуль os",
      "subcat": "os.path — проверки существования и типа",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.isdevdrive('.'))   # → False",
        "print(os.path.isdevdrive('нет-такого-пути'))   # → False (существование пути не проверяется, исключения нет)",
        "build_dir = os.path.join(os.getcwd(), 'build')",
        "print('быстрый Dev Drive' if os.path.isdevdrive(build_dir) else 'обычный диск')   # → обычный диск (True бывает только на Windows-разделе Dev Drive)",
        "print(os.path.isdevdrive('/'))   # → False (на Linux/macOS функция возвращает False для любого пути)"
      ],
      "related": [
        "os.path.isjunction",
        "os.path.ismount",
        "os.path.splitdrive"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.isdir",
      "title": "os.path.isdir",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, существует ли путь и является ли он каталогом.",
        "en": "Check whether a path exists and is a directory."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.isdir(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.isdir",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — проверки существования и типа",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.isdir(os.path.dirname(os.__file__)))   # → True",
        "print(os.path.isdir('/tmp'))  # → True",
        "print(os.path.isdir('/etc/hosts'))  # → False (это файл)"
      ],
      "related": [
        "os.path.exists",
        "os.path.isfile",
        "path.is_dir"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.isfile",
      "title": "os.path.isfile",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, существует ли путь и является ли он обычным файлом.",
        "en": "Check whether a path exists and is a regular file."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.isfile(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.isfile",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — проверки существования и типа",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.isfile(os.__file__))   # → True",
        "print(os.path.isfile('/etc/hosts'))  # → True",
        "print(os.path.isfile('/tmp'))  # → False (это каталог)"
      ],
      "related": [
        "os.path.exists",
        "os.path.isdir",
        "path.is_file"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.isjunction",
      "title": "os.path.isjunction",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, является ли путь junction-точкой Windows (на других ОС всегда False; Python 3.12+).",
        "en": "Check whether a path is a Windows junction (always False on other OSes; 3.12+)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.isjunction(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.isjunction",
      "version": "3.12",
      "section": "Модуль os",
      "subcat": "os.path — проверки существования и типа",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.isjunction(os.__file__))   # → False",
        "print(os.path.isjunction('нет-такого-пути'))   # → False (несуществующий путь — не ошибка, просто False)",
        "d = os.path.dirname(os.__file__)",
        "print(os.path.isdir(d), os.path.islink(d), os.path.isjunction(d))   # → True False False (для junction на Windows было бы True False True)",
        "print('junction' if os.path.isjunction(os.path.join(os.getcwd(), 'data_link')) else 'обычный путь')   # → обычный путь (junction создаётся только на Windows: mklink /J)"
      ],
      "related": [
        "os.path.islink",
        "os.path.ismount",
        "os.path.isdevdrive"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.islink",
      "title": "os.path.islink",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, является ли путь символической ссылкой (False, если пути нет).",
        "en": "Check whether a path is a symbolic link (False if it does not exist)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.islink(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.islink",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — проверки существования и типа",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.islink('not_a_link_xyz'))   # → False",
        "print(os.path.islink(os.__file__))   # → False (обычный файл — не ссылка)",
        "root = os.path.dirname(os.__file__)",
        "print([n for n in os.listdir(root) if os.path.islink(os.path.join(root, n))])   # → список имён-ссылок в каталоге стандартной библиотеки, обычно []",
        "print(os.path.lexists(os.__file__), os.path.islink(os.__file__))   # → True False (lexists отвечает «путь есть», islink — «это ссылка»; у битой ссылки exists даёт False, а islink — True)"
      ],
      "related": [
        "os.path.lexists",
        "os.readlink",
        "os.path.realpath",
        "os.symlink"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.ismount",
      "title": "os.path.ismount",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, является ли путь точкой монтирования файловой системы.",
        "en": "Check whether a path is a filesystem mount point."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.ismount(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.ismount",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — проверки существования и типа",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.ismount('some_file.txt'))   # → False",
        "print(os.path.ismount('/'))   # → True",
        "print(os.path.ismount(os.path.dirname(os.__file__)))   # → False",
        "p = os.path.abspath(os.__file__)   # ищем файловую систему, в которой лежит файл",
        "while not os.path.ismount(p): p = os.path.dirname(p)",
        "print(os.path.ismount(p))   # → True"
      ],
      "related": [
        "os.path.isdir",
        "os.path.isjunction",
        "os.statvfs"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.isreserved",
      "title": "os.path.isreserved()",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, зарезервировано ли имя пути в системе: на Windows это NUL, CON, COM1, имена с двоеточием, подстановочными символами или точкой/пробелом в конце. Только Windows, Python 3.13+.",
        "en": "Checks whether a pathname is reserved on the current system: on Windows these are NUL, CON, COM1, names with colons, wildcards, or a trailing dot/space. Windows only, Python 3.13+."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.isreserved(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.isreserved",
      "version": "3.13",
      "section": "Модуль os",
      "subcat": "os.path — проверки существования и типа",
      "color_group": "module",
      "aliases": [
        "зарезервированное имя файла",
        "запрещённые имена файлов",
        "недопустимое имя файла"
      ],
      "keywords": [
        "os.path.isreserved",
        "isreserved"
      ],
      "tags": [
        "os"
      ],
      "examples": [
        "import os, ntpath",
        "print(not hasattr(os.path, 'isreserved') or callable(os.path.isreserved))   # → True",
        "print(ntpath.isreserved('NUL'))   # → True",
        "print(ntpath.isreserved('COM1'))   # → True",
        "print(ntpath.isreserved('report.'))   # → True",
        "print(ntpath.isreserved('report.txt'))   # → False"
      ],
      "related": [
        "os.path.isdevdrive",
        "os.path.normpath",
        "os.path.splitdrive",
        "os.path.join"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.join",
      "title": "os.path.join()",
      "kind": "function",
      "summary": {
        "ru": "Объединяет компоненты пути с учётом ОС. Абсолютный компонент сбрасывает предыдущие части.",
        "en": "Joins the components of a path the way the OS expects. An absolute component discards everything before it."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.join(path, *paths) -> str",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.join",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import os",
        "print(os.path.join('home', 'user', 'file.txt'))  # → home/user/file.txt",
        "print(os.path.join('/etc', 'nginx', 'conf'))  # → /etc/nginx/conf",
        "base = '/var/log'",
        "print(os.path.join(base, 'app.log'))  # → /var/log/app.log",
        "print(os.path.join('a/b', '../c'))  # → a/b/../c",
        "print(os.path.join('a', '/abs'))  # → /abs  (абсолютный сбрасывает)"
      ],
      "related": [
        "os.path.split",
        "оператор",
        "os.path.normpath",
        "os.sep-os.linesep-os.pathsep"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "os.path.lexists",
      "title": "os.path.lexists",
      "kind": "function",
      "summary": {
        "ru": "Как exists, но не следует по символической ссылке (True даже для «битой» ссылки).",
        "en": "Like exists, but does not follow symlinks (True even for a broken link)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.lexists(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.lexists",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — проверки существования и типа",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.lexists(os.__file__))   # → True",
        "print(os.path.lexists('нет-такого-файла.txt'))   # → False",
        "print(os.path.lexists(os.path.dirname(os.__file__)))   # → True",
        "print(os.path.exists(os.__file__), os.path.lexists(os.__file__))   # → True True",
        "# на Unix: os.symlink('нет-такой-цели', 'битая') — после этого os.path.exists('битая') даст False, а os.path.lexists('битая') даст True"
      ],
      "related": [
        "os.path.exists",
        "os.path.islink",
        "os.lstat"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.normcase",
      "title": "os.path.normcase",
      "kind": "function",
      "summary": {
        "ru": "Нормализует регистр и разделители пути; на POSIX возвращает строку без изменений, на Windows приводит к нижнему регистру и меняет / на \\.",
        "en": "Normalize path case and separators; a no-op on POSIX."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.normcase(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.normcase",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — разбор пути",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.normcase('abc'))   # → abc",
        "print(os.path.normcase('Dir/File.TXT'))   # → на POSIX без изменений: Dir/File.TXT; на Windows: dir\\file.txt",
        "print(os.path.normcase('C:/Temp/A.TXT') == os.path.normcase('c:\\\\temp\\\\a.txt'))   # → на Windows True, на POSIX False",
        "print(os.path.normcase('a/../b') != os.path.normpath('a/../b'))   # → True"
      ],
      "related": [
        "os.path.normpath",
        "os.path.samefile",
        "os.sep-os.linesep-os.pathsep"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.normpath",
      "title": "os.path.normpath",
      "kind": "function",
      "summary": {
        "ru": "Убирает избыточные разделители и переходы (`.`/`..`), приводя путь к каноничному виду.",
        "en": "Collapse redundant separators and up-level references (`.`/`..`)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.normpath(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.normpath",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — разбор пути",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.normpath('a/./'))   # → a",
        "print(os.path.normpath('a/b/..'))   # → a",
        "print(os.path.normpath('a/../нет-такого/../b'))   # → b",
        "print(os.path.normpath(''))   # → .",
        "print(os.path.normpath('link/..'))   # → . — схлопывание чисто текстовое: если link был симлинком, настоящего родителя даст только os.path.realpath"
      ],
      "related": [
        "os.path.abspath",
        "os.path.realpath",
        "os.path.normcase",
        "os.path.join"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.realpath",
      "title": "os.path.realpath",
      "kind": "function",
      "summary": {
        "ru": "Возвращает канонический путь, разрешая все символические ссылки по дороге.",
        "en": "Return the canonical path, resolving any symlinks."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.realpath(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.realpath",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — абсолютизация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.isabs(os.path.realpath('.')))   # → True",
        "lib = os.path.dirname(os.__file__)",
        "print(os.path.realpath(os.path.join(lib, 'json', '..', 'os.py')) == os.path.realpath(os.__file__))   # → True (лишние шаги и '..' схлопываются)",
        "print(os.path.abspath('.') == os.path.realpath('.'))   # → True (в обычном каталоге; False, если путь ведёт через символическую ссылку — abspath ссылки не разрешает)",
        "try:",
        "    os.path.realpath('нет-такого-файла', strict=True)",
        "except FileNotFoundError as e:",
        "    print(type(e).__name__)   # → FileNotFoundError (при strict=True путь обязан существовать)"
      ],
      "related": [
        "os.path.abspath",
        "os.path.islink",
        "os.readlink"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.relpath",
      "title": "os.path.relpath",
      "kind": "function",
      "summary": {
        "ru": "Возвращает путь к path относительно каталога start.",
        "en": "Return a path to `path` relative to the `start` directory."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.relpath(path, start)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.relpath",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — разбор пути",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.relpath('/a/b/c', '/a/b'))   # → c",
        "print(os.path.relpath('/a/b', '/a/b'))   # → .",
        "print(os.path.relpath('/a', '/a/b'))   # → .. — если цель выше start, путь строится через переходы вверх",
        "print(os.path.relpath('/нет-такого/файл.txt', '/нет-такого'))   # → файл.txt — файлы могут не существовать, relpath работает с текстом пути",
        "print(os.path.relpath('/a/b/c'))   # → путь к /a/b/c от текущего каталога (start по умолчанию — os.curdir), например ../../a/b/c"
      ],
      "related": [
        "os.path.abspath",
        "os.path.commonpath",
        "os.path.isabs",
        "os.getcwd"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.samefile",
      "title": "os.path.samefile",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, ссылаются ли два пути на один и тот же файл (по устройству и inode).",
        "en": "Check whether two paths refer to the same file (by device and inode)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.samefile(p1, p2)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.samefile",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — метаданные (stat)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.samefile(os.__file__, os.__file__))   # → True",
        "same = os.path.join(os.path.dirname(os.__file__), '.', os.path.basename(os.__file__))",
        "print(os.path.samefile(os.__file__, same))   # → True",
        "print(os.__file__ == same)   # → False (строки разные, а файл один и тот же)",
        "print(os.path.samefile(os.__file__, os.path.dirname(os.__file__)))   # → False (файл и его каталог — разные объекты ФС)",
        "print(os.path.samefile('нет-такого', os.__file__))   # → FileNotFoundError"
      ],
      "related": [
        "os.path.sameopenfile",
        "os.path.samestat",
        "os.stat",
        "os.path.realpath"
      ],
      "related_errors": [
        "FileNotFoundError"
      ]
    },
    {
      "id": "os.path.sameopenfile",
      "title": "os.path.sameopenfile",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, ссылаются ли два файловых дескриптора на один и тот же файл.",
        "en": "Check whether two file descriptors refer to the same file."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.sameopenfile(fd1, fd2)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.sameopenfile",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — метаданные (stat)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "fd = os.open(os.__file__, os.O_RDONLY)",
        "print(os.path.sameopenfile(fd, fd))   # → True",
        "os.close(fd)"
      ],
      "related": [
        "os.path.samefile",
        "os.path.samestat",
        "os.fstat"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.samestat",
      "title": "os.path.samestat",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, описывают ли два объекта os.stat_result один и тот же файл.",
        "en": "Check whether two os.stat_result objects describe the same file."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.samestat(s1, s2)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.samestat",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — метаданные (stat)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "s = os.stat(os.__file__)",
        "print(os.path.samestat(s, s))   # → True",
        "s2 = os.stat(os.__file__)",
        "print(os.path.samestat(s, s2))   # → True (два независимых stat одного файла)",
        "print((s.st_dev, s.st_ino) == (s2.st_dev, s2.st_ino))   # → True (samestat сравнивает ровно эту пару полей)",
        "d = os.stat(os.path.dirname(os.__file__))",
        "print(os.path.samestat(s, d))   # → False",
        "print(os.path.samestat(os.__file__, os.__file__))   # → AttributeError (нужны stat_result, а не строки-пути)"
      ],
      "related": [
        "os.path.samefile",
        "os.path.sameopenfile",
        "os.stat_result",
        "os.stat"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.split",
      "title": "os.path.split",
      "kind": "function",
      "summary": {
        "ru": "Разбивает путь на пару (родитель, имя) — dirname и basename вместе.",
        "en": "Split a path into a (head, tail) pair — dirname and basename together."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.split(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.split",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — разбор пути",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.split('/a/b/c'))   # → ('/a/b', 'c')",
        "path = '/home/user/file.txt'",
        "print(os.path.split(path))  # → ('/home/user', 'file.txt')"
      ],
      "related": [
        "os.path.dirname",
        "os.path.basename"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.splitdrive",
      "title": "os.path.splitdrive",
      "kind": "function",
      "summary": {
        "ru": "Разбивает путь на пару (диск, остаток); на не-Windows диск всегда пустой.",
        "en": "Split a path into a (drive, rest) pair; the drive is empty on non-Windows."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.splitdrive(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.splitdrive",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path — разбор пути",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.splitdrive('/a/b'))   # → ('', '/a/b')",
        "print(os.path.splitdrive('C:\\\\Users\\\\ivan'))   # → только Windows: ('C:', '\\\\Users\\\\ivan'); на Unix диск не выделяется — ('', 'C:\\\\Users\\\\ivan')",
        "drive, rest = os.path.splitdrive('/tmp/data.csv')",
        "print(drive + rest)   # → /tmp/data.csv — склейка частей всегда восстанавливает исходный путь",
        "print(os.path.splitdrive(''))   # → ('', '')",
        "print(os.path.splitroot('/a/b'))   # → ('', '/', 'a/b') — splitroot дополнительно отделяет корень, splitdrive оставляет его в остатке"
      ],
      "related": [
        "os.path.splitroot",
        "os.path.split",
        "os.path.splitext",
        "pathlib.PureWindowsPath"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.splitext",
      "title": "os.path.splitext()",
      "kind": "function",
      "summary": {
        "ru": "Разбивает путь на пару (root, ext), где ext — расширение файла с точкой. Удобно для смены расширения.",
        "en": "Splits a path into the pair (root, ext), where ext is the file extension including the dot. Handy for changing an extension."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.splitext(path) -> (root, ext)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.splitext",
      "version": "",
      "section": "Модуль os",
      "subcat": "os.path",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import os",
        "print(os.path.splitext('report.pdf'))  # → ('report', '.pdf')",
        "print(os.path.splitext('archive.tar.gz'))  # → ('archive.tar', '.gz')",
        "print(os.path.splitext('no_extension'))  # → ('no_extension', '')",
        "name, ext = os.path.splitext('data.csv')",
        "print(name + '.xlsx')  # → data.xlsx",
        "print(os.path.splitext('/home/user/file.py'))  # → ('/home/user/file', '.py')"
      ],
      "related": [
        "os.path.split",
        ".stem-.suffix-.suffixes-.name-.parent-.p",
        "os.path.basename",
        "os.path.splitdrive"
      ],
      "related_errors": []
    },
    {
      "id": "os.path.splitroot",
      "title": "os.path.splitroot",
      "kind": "function",
      "summary": {
        "ru": "Разбивает путь на тройку (диск, корень, остаток) (Python 3.12+).",
        "en": "Split a path into a (drive, root, tail) triple (3.12+)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.path.splitroot(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.path.html#os.path.splitroot",
      "version": "3.12",
      "section": "Модуль os",
      "subcat": "os.path — разбор пути",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.path.splitroot('/a/b'))   # → ('', '/', 'a/b')",
        "print(os.path.splitroot('a/b'))   # → ('', '', 'a/b') — пустой root означает относительный путь",
        "drive, root, tail = os.path.splitroot('/a/b/c.txt')",
        "print(drive + root + tail)   # → /a/b/c.txt — три части всегда склеиваются обратно в исходный путь",
        "print(os.path.splitroot(''))   # → ('', '', '')",
        "print(os.path.splitroot('C:/Users'))   # → только Windows: ('C:', '/', 'Users'); на Unix буква диска не выделяется — ('', '', 'C:/Users')"
      ],
      "related": [
        "os.path.splitdrive",
        "os.path.split",
        "os.path.isabs"
      ],
      "related_errors": []
    },
    {
      "id": "os.pathconf",
      "title": "os.pathconf",
      "kind": "function",
      "summary": {
        "ru": "Возвращает значение системного ограничения для пути (напр. максимальную длину имени). Доступно на Unix.",
        "en": "Return a system configuration limit for a path. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.pathconf(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.pathconf",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — конфигурация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.pathconf('/tmp', 'PC_NAME_MAX'))  # → ? только Unix: предел длины имени файла в /tmp, обычно 255",
        "print(os.pathconf('/tmp', os.pathconf_names['PC_PATH_MAX']))  # → ? только Unix: предел длины всего пути, например 4096 (имя ограничения можно заменить его кодом)",
        "name = 'отчёт_за_2026_год.txt'",
        "print(len(name.encode()) <= os.pathconf('/tmp', 'PC_NAME_MAX'))  # → ? только Unix: True — проверяем длину имени в байтах до создания файла",
        "print(os.pathconf('/нет-такого-каталога', 'PC_NAME_MAX'))  # → FileNotFoundError"
      ],
      "related": [
        "os.fpathconf",
        "os.sysconf",
        "os.confstr"
      ],
      "related_errors": []
    },
    {
      "id": "os.pidfd_open",
      "title": "os.pidfd_open",
      "kind": "function",
      "summary": {
        "ru": "Возвращает дескриптор, ссылающийся на процесс по PID (для надёжной посылки сигналов; Python 3.9+). Доступно на Linux.",
        "en": "Return a file descriptor referring to a process by PID (3.9+). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.pidfd_open(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.pidfd_open",
      "version": "3.9",
      "section": "Модуль os",
      "subcat": "os — спец. дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os, select, signal",
        "fd = os.pidfd_open(os.getpid())   # → ? только Linux: дескриптор, привязанный к текущему процессу, например 3",
        "signal.pidfd_send_signal(fd, 0)   # → None — сигнал 0 лишь проверяет, что процесс жив (реальный SIGTERM шлют так же, но дочернему процессу)",
        "print(select.select([fd], [], [], 0))   # → ([], [], []) — процесс ещё работает; когда завершится, fd станет готов к чтению",
        "os.kill(os.getpid(), 0)   # → ? только Unix: то же самое по PID — но освободившийся PID ОС может отдать другому процессу, а pidfd всегда указывает на тот самый",
        "os.close(fd)   # → ? только Linux: дескриптор закрыт; на сам процесс это никак не влияет"
      ],
      "related": [
        "os.kill",
        "os.waitid",
        "os.getpid"
      ],
      "related_errors": []
    },
    {
      "id": "os.pipe",
      "title": "os.pipe",
      "kind": "function",
      "summary": {
        "ru": "Создаёт канал (pipe): возвращает пару дескрипторов (для чтения, для записи).",
        "en": "Create a pipe: return a (read_fd, write_fd) pair."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.pipe()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.pipe",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()",
        "print(isinstance(r, int) and isinstance(w, int))   # → True",
        "os.close(r)",
        "os.close(w)"
      ],
      "related": [
        "os.pipe2",
        "os.fdopen",
        "subprocess-PIPE"
      ],
      "related_errors": []
    },
    {
      "id": "os.pipe2",
      "title": "os.pipe2",
      "kind": "function",
      "summary": {
        "ru": "Как pipe, но сразу задаёт флаги (напр. O_NONBLOCK/O_CLOEXEC) для обоих концов. Доступно на Linux.",
        "en": "Like pipe, but atomically sets flags (e.g. O_NONBLOCK/O_CLOEXEC). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.pipe2(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.pipe2",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — спец. дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC)   # → ? только Linux: пара дескрипторов, сразу неблокирующих и закрываемых при exec",
        "print(os.write(w, b'ping'))   # → 4",
        "print(os.read(r, 4))   # → b'ping'",
        "os.read(r, 4)   # → BlockingIOError — данных больше нет, но из-за O_NONBLOCK вызов не зависает",
        "r2, w2 = os.pipe()   # переносимый вариант без флагов: O_NONBLOCK/O_CLOEXEC пришлось бы доставлять отдельными вызовами fcntl, оставляя окно гонки при fork"
      ],
      "related": [
        "os.pipe",
        "os.set_blocking",
        "os.set_inheritable"
      ],
      "related_errors": []
    },
    {
      "id": "os.popen",
      "title": "os.popen",
      "kind": "function",
      "summary": {
        "ru": "Открывает канал к команде оболочки: возвращает файловый объект для чтения её вывода или записи ввода.",
        "en": "Open a pipe to a shell command as a file object."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.popen(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.popen",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — процессы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.popen('echo hello').read().strip())   # → hello",
        "stream = os.popen('echo hello')   # канал открыт на чтение вывода команды",
        "print(stream.read() == 'hello\\n')   # → True — в конце вывода остаётся перевод строки",
        "print(stream.close())   # → None — команда завершилась с кодом 0",
        "print(os.popen('exit 3').close())   # → ненулевой статус: на Unix 768 (3 << 8), на Windows 3"
      ],
      "related": [
        "os.system",
        "subprocess-Popen",
        "subprocess-run"
      ],
      "related_errors": []
    },
    {
      "id": "os.posix_fadvise",
      "title": "os.posix_fadvise",
      "kind": "function",
      "summary": {
        "ru": "Сообщает ядру о предполагаемом характере доступа к файлу (последовательный/случайный) для оптимизации кеша. Доступно на Unix.",
        "en": "Advise the kernel about the expected file access pattern. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.posix_fadvise(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.posix_fadvise",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — советы ядру",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/tmp/report.csv', os.O_RDONLY)   # → ? только Unix: небольшой номер дескриптора, например 3",
        "os.posix_fadvise(fd, 0, 0, os.POSIX_FADV_SEQUENTIAL)   # → ? только Unix: подсказка «читать будем подряд»; length=0 означает «до конца файла»",
        "os.posix_fadvise(fd, 0, 65536, os.POSIX_FADV_WILLNEED)   # → ? только Unix: ядро заранее подтянет первые 64 КБ файла в кеш",
        "os.posix_fadvise(fd, 0, 0, os.POSIX_FADV_DONTNEED)   # → ? только Unix: после однопроходного чтения просим освободить кеш под этот файл",
        "os.posix_fadvise(fd, 0, 0, 999)   # → ? только Unix: OSError [Errno 22] Invalid argument — 999 не является кодом совета"
      ],
      "related": [
        "os.posix_fallocate",
        "os.ftruncate",
        "os.fsync"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.posix_fallocate",
      "title": "os.posix_fallocate",
      "kind": "function",
      "summary": {
        "ru": "Заранее выделяет место на диске под файл заданного размера. Доступно на Unix.",
        "en": "Pre-allocate disk space for a file of a given size. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.posix_fallocate(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.posix_fallocate",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — советы ядру",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/tmp/big.bin', os.O_WRONLY | os.O_CREAT)   # → ? только Unix: дескриптор нового файла, например 3",
        "os.posix_fallocate(fd, 0, 1024 * 1024)   # → ? только Unix: на диске заранее выделен 1 МБ под файл",
        "print(os.fstat(fd).st_size)   # → 1048576 — файл сразу нужного размера, хотя не записано ни байта",
        "os.ftruncate(fd, 8 * 1024 * 1024)   # → ? размер вырастет до 8 МБ, но место НЕ резервируется — выйдет «дырявый» (sparse) файл",
        "os.posix_fallocate(fd, 0, 0)   # → ? только Unix: OSError [Errno 22] Invalid argument — длина должна быть больше нуля"
      ],
      "related": [
        "os.ftruncate",
        "os.truncate",
        "os.posix_fadvise"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.posix_openpt",
      "title": "os.posix_openpt",
      "kind": "function",
      "summary": {
        "ru": "Открывает master-устройство псевдотерминала и возвращает его дескриптор. Доступно на Unix.",
        "en": "Open a pseudo-terminal master device and return its fd. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.posix_openpt(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.posix_openpt",
      "version": "3.13",
      "section": "Модуль os",
      "subcat": "os — псевдотерминалы (pty)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "master = os.posix_openpt(os.O_RDWR | os.O_NOCTTY)  # → ? только Unix: дескриптор master-устройства, например 3",
        "print(os.ptsname(master))  # → ? только Unix: путь парного slave-устройства, например /dev/pts/4",
        "os.unlockpt(master)  # → ? только Unix: обязательный шаг, иначе slave открыть не дадут",
        "slave = os.open(os.ptsname(master), os.O_RDWR)  # → ? только Unix: второй конец пары открыт",
        "pair = os.openpty()  # → ? только Unix: те же три шага одним вызовом — openpty проще для учебных задач"
      ],
      "related": [
        "os.grantpt",
        "os.unlockpt",
        "os.ptsname",
        "os.openpty"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.posix_spawn",
      "title": "os.posix_spawn",
      "kind": "function",
      "summary": {
        "ru": "Эффективно запускает новую программу через системный вызов posix_spawn; возвращает PID (Python 3.8+). Доступно на Unix.",
        "en": "Launch a program via the posix_spawn syscall; returns the PID (3.8+). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.posix_spawn(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.posix_spawn",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (spawn)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pid = os.posix_spawn('/bin/echo', ['echo', 'привет'], os.environ)  # → ? только Unix: запускает /bin/echo, печатает «привет» и возвращает PID потомка, например 12345",
        "print(os.waitpid(pid, 0)[1])  # → ? только Unix: 0 — потомок завершился успешно",
        "pid = os.posix_spawn('/bin/sh', ['sh', '-c', 'exit 3'], {'PATH': '/bin:/usr/bin'})  # → ? только Unix: своё окружение вместо os.environ, потомок выйдет с кодом 3",
        "os.posix_spawn('echo', ['echo', 'привет'], os.environ)  # → FileNotFoundError: нужен полный путь, PATH не просматривается (для поиска в PATH — os.posix_spawnp)"
      ],
      "related": [
        "os.posix_spawnp",
        "subprocess-run",
        "os.execv"
      ],
      "related_errors": [
        "FileNotFoundError",
        "OSError"
      ]
    },
    {
      "id": "os.posix_spawnp",
      "title": "os.posix_spawnp",
      "kind": "function",
      "summary": {
        "ru": "Как posix_spawn, но программа ищется в PATH. Доступно на Unix.",
        "en": "Like posix_spawn, but the program is looked up in PATH. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.posix_spawnp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.posix_spawnp",
      "version": "3.8",
      "section": "Модуль os",
      "subcat": "os — запуск программ (spawn)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pid = os.posix_spawnp('echo', ['echo', 'привет'], os.environ)  # → ? только Unix: находит echo в PATH, возвращает PID потомка, например 12345",
        "print(os.waitpid(pid, 0)[1])  # → ? только Unix: 0 — потомок напечатал «привет» и завершился успешно",
        "pid = os.posix_spawnp('sh', ['sh', '-c', 'exit 3'], {'PATH': '/bin:/usr/bin'})  # → ? только Unix: PATH берётся из переданного env, потомок выйдет с кодом 3",
        "os.posix_spawnp('нет-такой-программы', ['нет-такой-программы'], os.environ)  # → FileNotFoundError: в PATH ничего не найдено"
      ],
      "related": [
        "os.posix_spawn",
        "os.execvp",
        "subprocess-run"
      ],
      "related_errors": [
        "FileNotFoundError",
        "OSError"
      ]
    },
    {
      "id": "os.pread",
      "title": "os.pread",
      "kind": "function",
      "summary": {
        "ru": "Читает n байтов из дескриптора с заданной позиции, не меняя текущее смещение. Доступно на Unix.",
        "en": "Read n bytes from a descriptor at a given offset without moving the position. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.pread(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.pread",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — позиционный ввод-вывод",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/tmp/pread_demo.txt', os.O_RDWR | os.O_CREAT)  # временный файл только для примера",
        "print(os.write(fd, b'Hello, world!'))  # → 13 — записали 13 байт, текущее смещение стало 13",
        "print(os.pread(fd, 5, 0))  # → b'Hello' — читаем 5 байт с позиции 0",
        "print(os.lseek(fd, 0, os.SEEK_CUR))  # → 13 — pread не сдвинул текущее смещение файла, в отличие от os.read",
        "print(os.pread(fd, 5, 1000))  # → b'' — чтение за концом файла возвращает пустые байты, а не ошибку",
        "os.close(fd)  # закрываем дескриптор"
      ],
      "related": [
        "os.pwrite",
        "os.preadv",
        "os.read",
        "os.lseek"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.preadv",
      "title": "os.preadv",
      "kind": "function",
      "summary": {
        "ru": "Как pread, но читает сразу в несколько буферов (scatter read) с заданной позиции. Доступно на Unix.",
        "en": "Like pread, but scatters into multiple buffers at an offset. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.preadv(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.preadv",
      "version": "3.7",
      "section": "Модуль os",
      "subcat": "os — позиционный ввод-вывод",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/tmp/preadv-demo.bin', os.O_RDWR | os.O_CREAT | os.O_TRUNC)  # временный файл для опытов",
        "print(os.pwrite(fd, b'HEADERBODY', 0))  # → 10 — подготовили данные, позиция fd осталась в начале",
        "bufs = [bytearray(6), bytearray(4)]  # scatter-чтение: заголовок и тело в разные буферы",
        "print(os.preadv(fd, bufs, 0), bytes(bufs[0]), bytes(bufs[1]))  # → ? только Unix: 10 b'HEADER' b'BODY'",
        "print(os.lseek(fd, 0, os.SEEK_CUR))  # → 0 — ни pwrite, ни preadv не сдвинули позицию fd (в отличие от os.readv)"
      ],
      "related": [
        "os.pread",
        "os.readv",
        "os.pwritev"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.ptsname",
      "title": "os.ptsname",
      "kind": "function",
      "summary": {
        "ru": "Возвращает путь к slave-устройству псевдотерминала (по master-дескриптору). Доступно на Unix.",
        "en": "Return the path of the slave pseudo-terminal (by master fd). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.ptsname(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.ptsname",
      "version": "3.13",
      "section": "Модуль os",
      "subcat": "os — псевдотерминалы (pty)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "master = os.posix_openpt(os.O_RDWR)  # → ? только Unix: master-дескриптор псевдотерминала",
        "print(os.ptsname(master))  # → ? только Unix: путь парного slave-устройства, например /dev/pts/4",
        "os.unlockpt(master)  # → ? только Unix: разблокировали slave перед открытием",
        "slave = os.open(os.ptsname(master), os.O_RDWR)  # → ? только Unix: именно по пути от ptsname и открывают slave",
        "print(os.ptsname(0))  # → OSError — дескриптор stdin не является master-устройством pty"
      ],
      "related": [
        "os.posix_openpt",
        "os.unlockpt",
        "os.grantpt",
        "os.ttyname"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.putenv",
      "title": "os.putenv",
      "kind": "function",
      "summary": {
        "ru": "Устанавливает переменную окружения на уровне ОС (для дочерних процессов); os.environ обновляется отдельно.",
        "en": "Set an environment variable at the OS level (for child processes)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.putenv(key, value)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.putenv",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — окружение",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "os.putenv('GLOSSARY_DEMO', '42')   # → значение ушло в окружение ОС: его унаследуют дочерние процессы",
        "print(os.environ.get('GLOSSARY_DEMO'))   # → None (putenv не обновляет словарь os.environ)",
        "os.environ['GLOSSARY_DEMO'] = '42'   # → штатный способ: присваивание в os.environ само вызывает putenv",
        "print(os.getenv('GLOSSARY_DEMO'))   # → 42",
        "os.putenv('GLOSSARY_DEMO', 42)   # → TypeError: значение обязано быть str или bytes, а не int"
      ],
      "related": [
        "os.environ",
        "os.unsetenv",
        "os.getenv"
      ],
      "related_errors": []
    },
    {
      "id": "os.pwrite",
      "title": "os.pwrite",
      "kind": "function",
      "summary": {
        "ru": "Пишет байты в дескриптор по заданной позиции, не меняя текущее смещение. Доступно на Unix.",
        "en": "Write bytes to a descriptor at a given offset without moving the position. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.pwrite(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.pwrite",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — позиционный ввод-вывод",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/tmp/pwrite-demo.bin', os.O_RDWR | os.O_CREAT | os.O_TRUNC)  # временный файл для опытов",
        "print(os.pwrite(fd, b'0123456789', 0))  # → 10 — записано байт начиная со смещения 0",
        "print(os.pwrite(fd, b'XY', 4))  # → 2 — точечная правка двух байтов в середине файла",
        "print(os.pread(fd, 10, 0))  # → b'0123XY6789'",
        "print(os.lseek(fd, 0, os.SEEK_CUR))  # → 0 — pwrite не двигает позицию fd, в отличие от os.write"
      ],
      "related": [
        "os.pread",
        "os.pwritev",
        "os.write",
        "os.lseek"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.pwritev",
      "title": "os.pwritev",
      "kind": "function",
      "summary": {
        "ru": "Как pwrite, но пишет сразу из нескольких буферов (gather write) по заданной позиции. Доступно на Unix.",
        "en": "Like pwrite, but gathers from multiple buffers at an offset. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.pwritev(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.pwritev",
      "version": "3.7",
      "section": "Модуль os",
      "subcat": "os — позиционный ввод-вывод",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/tmp/pwritev-demo.bin', os.O_RDWR | os.O_CREAT | os.O_TRUNC)  # временный файл для опытов",
        "parts = [b'HEADER', b'BODY']  # gather-запись: заголовок и тело лежат в разных буферах",
        "print(os.pwritev(fd, parts, 0))  # → 10 — обе части ушли одним системным вызовом",
        "print(os.pread(fd, 10, 0))  # → b'HEADERBODY' — буферы склеены подряд, без разделителей",
        "print(os.lseek(fd, 0, os.SEEK_CUR))  # → 0 — pwritev пишет по смещению и не двигает позицию fd (в отличие от os.writev)"
      ],
      "related": [
        "os.pwrite",
        "os.writev",
        "os.preadv"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.read",
      "title": "os.read",
      "kind": "function",
      "summary": {
        "ru": "Читает до n байтов из файлового дескриптора, возвращая bytes.",
        "en": "Read up to n bytes from a file descriptor, returning bytes."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.read(fd, n)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.read",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()",
        "os.write(w, b'hello')",
        "print(os.read(r, 5))   # → b'hello'",
        "os.close(r)",
        "os.close(w)"
      ],
      "related": [
        "os.write",
        "os.pread",
        "os.lseek",
        "file.read"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.readinto",
      "title": "os.readinto()",
      "kind": "function",
      "summary": {
        "ru": "Читает данные из файлового дескриптора в уже готовый изменяемый буфер (bytearray/memoryview) и возвращает число прочитанных байт, не создавая новый bytes. Python 3.14+.",
        "en": "Reads from a file descriptor into a pre-allocated mutable buffer (bytearray/memoryview) and returns the number of bytes read, without creating a new bytes object. Python 3.14+."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.readinto(fd, buffer, /)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.readinto",
      "version": "3.14",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [
        "чтение в готовый буфер",
        "читать из дескриптора в буфер",
        "прочитать байты в изменяемый буфер"
      ],
      "keywords": [
        "os.readinto",
        "readinto"
      ],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()",
        "os.write(w, b'hello world')",
        "buf = bytearray(5)",
        "print(os.readinto(r, buf))   # → 5 (сколько байт реально прочитано)",
        "print(bytes(buf))            # → b'hello'",
        "print(os.readinto(r, bytearray(0)))   # → 0 (буфер нулевой длины)",
        "os.close(r); os.close(w)"
      ],
      "related": [
        "os.read",
        "os.readv",
        "os.pread",
        "os.write"
      ],
      "related_errors": [
        "OSError",
        "TypeError"
      ]
    },
    {
      "id": "os.readlink",
      "title": "os.readlink",
      "kind": "function",
      "summary": {
        "ru": "Возвращает путь, на который указывает символическая ссылка.",
        "en": "Return the path a symbolic link points to."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.readlink(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.readlink",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — ссылки",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "os.symlink('data.txt', 'link.txt')   # → ? только Unix: создаёт ссылку link.txt на data.txt",
        "print(os.readlink('link.txt'))   # → ? data.txt — «сырая» цель ссылки, ровно как она записана",
        "print(os.path.realpath('link.txt'))   # → ? data.txt — realpath разворачивает цель в абсолютный путь",
        "print(os.path.islink('link.txt'))   # → True — проверка перед вызовом readlink",
        "print(os.readlink('data.txt'))   # → OSError (EINVAL) — обычный файл не является символической ссылкой",
        "print(os.readlink('нет-такой-ссылки'))   # → FileNotFoundError"
      ],
      "related": [
        "os.symlink",
        "os.path.islink",
        "os.path.realpath"
      ],
      "related_errors": [
        "FileNotFoundError",
        "OSError"
      ]
    },
    {
      "id": "os.readv",
      "title": "os.readv",
      "kind": "function",
      "summary": {
        "ru": "Читает из дескриптора сразу в несколько буферов (scatter read). Доступно на Unix.",
        "en": "Read from a descriptor into multiple buffers (scatter read). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.readv(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.readv",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — позиционный ввод-вывод",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/tmp/readv-demo.bin', os.O_RDWR | os.O_CREAT | os.O_TRUNC)  # временный файл для опытов",
        "print(os.pwrite(fd, b'HEADERBODY', 0))  # → 10 — подготовили данные, позиция fd осталась в начале",
        "bufs = [bytearray(6), bytearray(4)]  # scatter-чтение: заголовок и тело в разные буферы",
        "print(os.readv(fd, bufs), bytes(bufs[0]), bytes(bufs[1]))  # → ? только Unix: 10 b'HEADER' b'BODY'",
        "print(os.readv(fd, [bytearray(4)]))  # → 0 — данные кончились: readv читает с текущей позиции и сдвигает её"
      ],
      "related": [
        "os.writev",
        "os.preadv",
        "os.read"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.register_at_fork",
      "title": "os.register_at_fork",
      "kind": "function",
      "summary": {
        "ru": "Регистрирует функции-обработчики, вызываемые до/после os.fork() (в родителе и потомке). Доступно на Unix.",
        "en": "Register callables run before/after os.fork() in parent and child. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.register_at_fork(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.register_at_fork",
      "version": "3.7",
      "section": "Модуль os",
      "subcat": "os — создание процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "import random",
        "os.register_at_fork(after_in_child=lambda: print('потомок стартовал'))   # → ? только Unix: обработчик лишь запомнен, сейчас ничего не печатается",
        "os.register_at_fork(before=lambda: print('готовимся к fork'), after_in_parent=lambda: print('родитель продолжил'))   # → ? можно задать все три точки; before-обработчики вызываются в обратном порядке регистрации",
        "os.register_at_fork(after_in_child=lambda: random.seed())   # → ? типовой сценарий: потомок переинициализирует ГПСЧ, иначе оба процесса выдадут одинаковые «случайные» числа",
        "pid = os.fork()   # → ? напечатает «готовимся к fork», затем «родитель продолжил» в родителе и «потомок стартовал» в потомке",
        "os.register_at_fork(print)   # → TypeError — аргументы только именованные (before / after_in_parent / after_in_child); снять уже зарегистрированный обработчик нельзя"
      ],
      "related": [
        "os.fork",
        "os.forkpty"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "os.reload_environ",
      "title": "os.reload_environ()",
      "kind": "function",
      "summary": {
        "ru": "Перечитывает окружение процесса и обновляет os.environ/os.environb — например после os.putenv() или изменений извне. Python 3.14+, не потокобезопасна.",
        "en": "Reloads the process environment into os.environ/os.environb — e.g. after os.putenv() or changes made outside Python. Python 3.14+, not thread-safe."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.reload_environ()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.reload_environ",
      "version": "3.14",
      "section": "Модуль os",
      "subcat": "os — окружение",
      "color_group": "module",
      "aliases": [
        "обновить переменные окружения",
        "перечитать окружение процесса",
        "сбросить кеш переменных окружения"
      ],
      "keywords": [
        "os.reload_environ",
        "reload_environ"
      ],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "os.putenv('DEMO_VAR', '42')      # меняет окружение процесса мимо os.environ",
        "print('DEMO_VAR' in os.environ)   # → False",
        "os.reload_environ()               # перечитать окружение процесса",
        "print(os.environ['DEMO_VAR'])     # → 42",
        "print(os.getenv('DEMO_VAR'))      # → 42",
        "print(os.reload_environ() is None)   # → True (функция ничего не возвращает)"
      ],
      "related": [
        "os.environ",
        "os.putenv",
        "os.unsetenv",
        "os.getenv"
      ],
      "related_errors": []
    },
    {
      "id": "os.remove",
      "title": "os.remove",
      "kind": "function",
      "summary": {
        "ru": "Удаляет файл. Каталоги не удаляет — для них os.rmdir(), а для непустого дерева shutil.rmtree(). Полный синоним os.unlink().",
        "en": "Delete a file; directories need os.rmdir() (or shutil.rmtree() for a non-empty tree). Exact synonym of os.unlink()."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.remove(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.remove",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — удаление",
      "color_group": "module",
      "aliases": [],
      "keywords": [
        "os.remove",
        "remove"
      ],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "fd, p = tempfile.mkstemp()",
        "os.close(fd)",
        "os.remove(p)",
        "print(os.path.exists(p))   # → False",
        "if os.path.exists('temp.txt'):",
        "    os.remove('temp.txt')  # → безопасное удаление"
      ],
      "related": [
        "os.unlink",
        "os.rename",
        "os.rmdir"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError",
        "IsADirectoryError"
      ]
    },
    {
      "id": "os.removedirs",
      "title": "os.removedirs",
      "kind": "function",
      "summary": {
        "ru": "Удаляет каталог и вверх по дереву все ставшие пустыми родительские.",
        "en": "Remove a directory and any parents that become empty."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.removedirs(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.removedirs",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — каталоги",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "base = tempfile.mkdtemp()",
        "d = os.path.join(base, 'a', 'b')",
        "os.makedirs(d)",
        "os.removedirs(d)",
        "print(os.path.exists(base))   # → False"
      ],
      "related": [
        "os.rmdir",
        "os.makedirs",
        "os.renames"
      ],
      "related_errors": [
        "OSError",
        "FileNotFoundError"
      ]
    },
    {
      "id": "os.removexattr",
      "title": "os.removexattr",
      "kind": "function",
      "summary": {
        "ru": "Удаляет расширенный атрибут файла. Доступно на Linux.",
        "en": "Remove an extended attribute from a file. Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.removexattr(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.removexattr",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — расширенные атрибуты (xattr)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "open('xattr_demo.txt', 'w').close()",
        "os.setxattr('xattr_demo.txt', 'user.author', b'anna')   # → ? только Linux: подготовили атрибут, чтобы было что удалять",
        "os.removexattr('xattr_demo.txt', 'user.author')   # → ? только Linux: атрибут удалён, функция возвращает None",
        "print(os.listxattr('xattr_demo.txt'))   # → [] — атрибутов не осталось",
        "print(os.removexattr('xattr_demo.txt', 'user.author'))   # → OSError (ENODATA) — повторное удаление, атрибута уже нет"
      ],
      "related": [
        "os.setxattr",
        "os.getxattr",
        "os.listxattr"
      ],
      "related_errors": [
        "OSError",
        "FileNotFoundError"
      ]
    },
    {
      "id": "os.rename",
      "title": "os.rename",
      "kind": "function",
      "summary": {
        "ru": "Переименовывает или перемещает файл/каталог.",
        "en": "Rename or move a file/directory."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.rename(src, dst)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.rename",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — переименование/перемещение",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "fd, p = tempfile.mkstemp()",
        "os.close(fd)",
        "p2 = p + '_r'",
        "os.rename(p, p2)",
        "print(os.path.exists(p2) and not os.path.exists(p))   # → True",
        "os.remove(p2)",
        "open('old.txt', 'w').close()",
        "os.rename('old.txt', 'new.txt')   # переименование в том же каталоге",
        "print(os.path.exists('new.txt'))   # → True",
        "os.mkdir('folder_a')",
        "os.rename('folder_a', 'folder_b')   # каталоги переименовываются тем же вызовом",
        "print(os.path.isdir('folder_b'))   # → True"
      ],
      "related": [
        "os.remove",
        "path.rename"
      ],
      "related_errors": [
        "FileNotFoundError",
        "OSError",
        "PermissionError"
      ]
    },
    {
      "id": "os.renames",
      "title": "os.renames",
      "kind": "function",
      "summary": {
        "ru": "Как rename, но создаёт недостающие каталоги назначения и убирает опустевшие исходные.",
        "en": "Like rename, but creates missing target dirs and prunes empty source dirs."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.renames(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.renames",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — переименование/перемещение",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "base = tempfile.mkdtemp()",
        "fd, p = tempfile.mkstemp(dir=base)",
        "os.close(fd)",
        "p2 = os.path.join(base, 'sub', 'f')",
        "os.renames(p, p2)",
        "print(os.path.isfile(p2))   # → True",
        "os.remove(p2)",
        "os.rmdir(os.path.dirname(p2))",
        "os.rmdir(base)"
      ],
      "related": [
        "os.rename",
        "os.makedirs",
        "os.removedirs"
      ],
      "related_errors": [
        "FileNotFoundError",
        "OSError",
        "PermissionError"
      ]
    },
    {
      "id": "os.rmdir",
      "title": "os.rmdir",
      "kind": "function",
      "summary": {
        "ru": "Удаляет пустой каталог.",
        "en": "Remove an empty directory."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.rmdir(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.rmdir",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — каталоги",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "d = tempfile.mkdtemp()",
        "os.rmdir(d)",
        "print(os.path.exists(d))   # → False",
        "os.makedirs('a/b/c', exist_ok=True)",
        "os.rmdir('a/b/c')  # → удаляет только пустую c",
        "print(os.path.isdir('a/b'))  # → True"
      ],
      "related": [
        "os.makedirs",
        "os.removedirs"
      ],
      "related_errors": [
        "OSError",
        "FileNotFoundError",
        "NotADirectoryError"
      ]
    },
    {
      "id": "os.scandir",
      "title": "os.scandir",
      "kind": "function",
      "summary": {
        "ru": "Возвращает итератор os.DirEntry по элементам каталога — эффективнее listdir (кеширует stat).",
        "en": "Return an iterator of os.DirEntry for a directory — more efficient than listdir."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.scandir(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.scandir",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловая система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "d = os.path.dirname(os.__file__)",
        "with os.scandir(d) as it:",
        "    print(any(e.is_file() for e in it))   # → True"
      ],
      "related": [
        "os.DirEntry",
        "os.listdir",
        "os.walk",
        ".iterdir"
      ],
      "related_errors": [
        "FileNotFoundError",
        "NotADirectoryError",
        "PermissionError"
      ]
    },
    {
      "id": "os.sched_get_priority_max",
      "title": "os.sched_get_priority_max",
      "kind": "function",
      "summary": {
        "ru": "Возвращает максимальный приоритет для заданной политики планирования. Доступно на Unix.",
        "en": "Return the maximum priority for a scheduling policy. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sched_get_priority_max(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sched_get_priority_max",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — планировщик (sched)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.sched_get_priority_max(os.SCHED_FIFO))   # → ? только Unix: верхняя граница real-time приоритетов, на Linux 99",
        "print(os.sched_get_priority_max(os.SCHED_OTHER))   # → ? только Unix: на Linux 0 — обычной политике приоритет не назначают",
        "lo = os.sched_get_priority_min(os.SCHED_RR)   # → ? только Unix: нижняя граница того же диапазона",
        "print(lo, os.sched_get_priority_max(os.SCHED_RR))   # → ? только Unix: на Linux «1 99» — допустимый отрезок приоритетов SCHED_RR",
        "os.sched_get_priority_max(12345)   # → OSError: [Errno 22] Invalid argument — политики с таким номером нет"
      ],
      "related": [
        "os.sched_get_priority_min",
        "os.sched_setscheduler",
        "os.sched_param"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.sched_get_priority_min",
      "title": "os.sched_get_priority_min",
      "kind": "function",
      "summary": {
        "ru": "Возвращает минимальный приоритет для заданной политики планирования. Доступно на Unix.",
        "en": "Return the minimum priority for a scheduling policy. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sched_get_priority_min(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sched_get_priority_min",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — планировщик (sched)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.sched_get_priority_min(os.SCHED_FIFO))   # → ? только Unix: минимальный приоритет real-time политики, на Linux 1",
        "print(os.sched_get_priority_min(os.SCHED_OTHER))   # → ? только Unix: на Linux 0 — у обычной политики приоритеты не различаются",
        "param = os.sched_param(os.sched_get_priority_min(os.SCHED_RR))   # → ? только Unix: самый скромный real-time приоритет",
        "os.sched_setscheduler(0, os.SCHED_RR, param)   # → ? только Unix и только с правами root: переводит текущий процесс на политику SCHED_RR",
        "os.sched_get_priority_min(-1)   # → OSError: [Errno 22] Invalid argument — политики с номером -1 не существует"
      ],
      "related": [
        "os.sched_get_priority_max",
        "os.sched_setscheduler",
        "os.sched_param"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.sched_getaffinity",
      "title": "os.sched_getaffinity",
      "kind": "function",
      "summary": {
        "ru": "Возвращает множество ядер CPU, на которых разрешено выполняться процессу. Доступно на Unix.",
        "en": "Return the set of CPUs a process is allowed to run on. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sched_getaffinity(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sched_getaffinity",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — планировщик (sched)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.sched_getaffinity(0))   # → ? только Unix: множество разрешённых ядер текущего процесса, например {0, 1, 2, 3}",
        "cores = len(os.sched_getaffinity(0)); print(cores)   # → ? только Unix: сколько ядер реально доступно процессу, например 4",
        "print(os.cpu_count())   # → ? всего ядер в системе, например 8 — маску процесса не учитывает, в контейнере число завышено",
        "os.sched_setaffinity(0, {0}); print(os.sched_getaffinity(0))   # → ? только Unix: {0} — после сужения маски остаётся одно ядро",
        "print(os.sched_getaffinity(10 ** 7))   # → ProcessLookupError: процесса с таким pid нет"
      ],
      "related": [
        "os.sched_setaffinity",
        "os.cpu_count",
        "os.sched_getscheduler"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.sched_getparam",
      "title": "os.sched_getparam",
      "kind": "function",
      "summary": {
        "ru": "Возвращает параметры планирования процесса (приоритет реального времени). Доступно на Unix.",
        "en": "Return a process's scheduling parameters (RT priority). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sched_getparam(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sched_getparam",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — планировщик (sched)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "param = os.sched_getparam(0); print(param)   # → ? только Unix: posix.sched_param(sched_priority=0)",
        "print(param.sched_priority)   # → 0 — единственное поле структуры",
        "print(os.sched_getparam(os.getppid()).sched_priority)   # → ? только Unix: приоритет родительского процесса, обычно 0",
        "print(os.getpriority(os.PRIO_PROCESS, 0))   # → ? только Unix: nice-значение, например 0 — это НЕ sched_priority, а вежливость обычного процесса",
        "print(os.sched_getparam(10 ** 7))   # → ProcessLookupError: процесса с таким pid нет"
      ],
      "related": [
        "os.sched_setparam",
        "os.sched_param",
        "os.sched_getscheduler"
      ],
      "related_errors": []
    },
    {
      "id": "os.sched_getscheduler",
      "title": "os.sched_getscheduler",
      "kind": "function",
      "summary": {
        "ru": "Возвращает политику планирования процесса (напр. SCHED_OTHER/SCHED_FIFO). Доступно на Unix.",
        "en": "Return a process's scheduling policy (e.g. SCHED_OTHER/SCHED_FIFO). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sched_getscheduler(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sched_getscheduler",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — планировщик (sched)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "policy = os.sched_getscheduler(0); print(policy)   # → 0 — числовой код текущей политики планирования",
        "print(policy == os.SCHED_OTHER)   # → True — обычный процесс идёт по политике по умолчанию",
        "print(os.SCHED_OTHER, os.SCHED_FIFO, os.SCHED_RR)   # → ? только Unix: 0 1 2 — коды политик в Linux",
        "print(os.sched_getscheduler(os.getppid()) == os.SCHED_OTHER)   # → ? только Unix: True — родитель тоже под обычной политикой",
        "os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(10))   # → ? только Unix: PermissionError без прав root; с root процесс уходит в реальное время"
      ],
      "related": [
        "os.sched_setscheduler",
        "os.sched_getparam",
        "os.sched_rr_get_interval"
      ],
      "related_errors": []
    },
    {
      "id": "os.sched_param",
      "title": "os.sched_param",
      "kind": "term",
      "summary": {
        "ru": "Тип-контейнер параметров планирования (поле sched_priority) для sched_setscheduler/sched_setparam. Доступно на Unix.",
        "en": "A container type for scheduling parameters (sched_priority). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sched_param(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sched_param",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — планировщик (sched)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "p = os.sched_param(0); print(p)   # → ? только Unix: posix.sched_param(sched_priority=0)",
        "print(p.sched_priority)   # → 0 — единственное поле контейнера",
        "print(os.sched_get_priority_min(os.SCHED_RR), os.sched_get_priority_max(os.SCHED_RR))   # → ? только Unix: 1 99 — допустимый диапазон sched_priority в Linux",
        "os.sched_setscheduler(0, os.SCHED_RR, os.sched_param(10))   # → ? только Unix: PermissionError без прав root; с root процесс переходит в round-robin с приоритетом 10",
        "os.sched_setparam(0, os.sched_param(5))   # → ? только Unix: OSError [Errno 22] Invalid argument — под SCHED_OTHER приоритет обязан быть нулевым"
      ],
      "related": [
        "os.sched_setparam",
        "os.sched_setscheduler",
        "os.sched_getparam",
        "os.sched_get_priority_max"
      ],
      "related_errors": []
    },
    {
      "id": "os.sched_rr_get_interval",
      "title": "os.sched_rr_get_interval",
      "kind": "function",
      "summary": {
        "ru": "Возвращает квант времени round-robin для процесса (политика SCHED_RR). Доступно на Unix.",
        "en": "Return the round-robin time quantum for a process (SCHED_RR). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sched_rr_get_interval(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sched_rr_get_interval",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — планировщик (sched)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.sched_rr_get_interval(0))   # → ? только Unix: квант round-robin в секундах, например 0.1 — точное значение зависит от ядра",
        "interval = os.sched_rr_get_interval(0); print(round(interval * 1000))   # → ? только Unix: тот же квант в миллисекундах, например 100",
        "print(os.sched_getscheduler(0) == os.SCHED_RR)   # → False — процесс идёт под SCHED_OTHER, квант сообщается, но реально не применяется",
        "print(os.sched_rr_get_interval(os.getppid()))   # → ? только Unix: квант родительского процесса, обычно тот же, например 0.1",
        "print(os.sched_rr_get_interval(10 ** 7))   # → ProcessLookupError: процесса с таким pid нет"
      ],
      "related": [
        "os.sched_getscheduler",
        "os.sched_setscheduler",
        "os.sched_yield"
      ],
      "related_errors": []
    },
    {
      "id": "os.sched_setaffinity",
      "title": "os.sched_setaffinity",
      "kind": "function",
      "summary": {
        "ru": "Задаёт множество ядер CPU, на которых разрешено выполняться процессу (привязка к ядрам). Доступно на Unix.",
        "en": "Set the CPUs a process is allowed to run on (CPU affinity). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sched_setaffinity(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sched_setaffinity",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — планировщик (sched)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.sched_getaffinity(0))  # → ? только Unix: множество ядер, доступных текущему процессу (pid=0), например {0, 1, 2, 3}",
        "os.sched_setaffinity(0, {0})  # → ? только Unix: процесс закреплён за ядром 0",
        "print(len(os.sched_getaffinity(0)))  # → ? только Unix: 1 — честное число доступных ядер (точнее, чем os.cpu_count())",
        "os.sched_setaffinity(0, {0, 1})  # → ? только Unix: вернули процессу два ядра",
        "os.sched_setaffinity(0, set())  # → OSError: [Errno 22] Invalid argument — пустая маска запрещена"
      ],
      "related": [
        "os.sched_getaffinity",
        "os.cpu_count",
        "os.sched_setscheduler"
      ],
      "related_errors": [
        "OSError",
        "PermissionError"
      ]
    },
    {
      "id": "os.sched_setparam",
      "title": "os.sched_setparam",
      "kind": "function",
      "summary": {
        "ru": "Задаёт параметры планирования процесса. Доступно на Unix.",
        "en": "Set a process's scheduling parameters. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sched_setparam(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sched_setparam",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — планировщик (sched)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.sched_getparam(0).sched_priority)  # → 0 — обычный процесс работает с политикой SCHED_OTHER",
        "param = os.sched_param(0)  # 0 — единственный допустимый приоритет для SCHED_OTHER",
        "os.sched_setparam(0, param)  # → ? только Unix: параметры планирования текущего процесса (pid=0) переустановлены",
        "os.sched_setparam(0, os.sched_param(10))  # → OSError: [Errno 22] Invalid argument — ненулевой приоритет вне realtime-политик запрещён",
        "os.sched_setparam(99999, param)  # → ProcessLookupError — процесса с таким pid не существует"
      ],
      "related": [
        "os.sched_getparam",
        "os.sched_param",
        "os.sched_setscheduler"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.sched_setscheduler",
      "title": "os.sched_setscheduler",
      "kind": "function",
      "summary": {
        "ru": "Задаёт политику планирования и параметры процесса. Доступно на Unix.",
        "en": "Set a process's scheduling policy and parameters. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sched_setscheduler(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sched_setscheduler",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — планировщик (sched)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.sched_getscheduler(0))  # → ? только Unix: текущая политика, обычно 0 (os.SCHED_OTHER)",
        "os.sched_setscheduler(0, os.SCHED_OTHER, os.sched_param(0))  # → ? только Unix: процесс явно переведён на обычную политику разделения времени",
        "print(os.sched_get_priority_min(os.SCHED_FIFO), os.sched_get_priority_max(os.SCHED_FIFO))  # → ? только Unix: диапазон realtime-приоритетов, на Linux 1 99",
        "os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(1))  # → PermissionError — realtime-политика требует прав root/CAP_SYS_NICE",
        "os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(0))  # → OSError: [Errno 22] Invalid argument — приоритет 0 недопустим для SCHED_FIFO"
      ],
      "related": [
        "os.sched_getscheduler",
        "os.sched_param",
        "os.sched_setparam",
        "os.sched_get_priority_max"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.sched_yield",
      "title": "os.sched_yield",
      "kind": "function",
      "summary": {
        "ru": "Добровольно уступает процессор другим готовым к выполнению процессам. Доступно на Unix.",
        "en": "Voluntarily yield the CPU to other runnable processes. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sched_yield(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sched_yield",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — планировщик (sched)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.sched_yield())  # → None — функция ничего не возвращает",
        "for _ in range(3): os.sched_yield()  # → ? только Unix: трижды уступили процессор другим готовым процессам",
        "os.sched_yield()  # → ? только Unix: если готовых к выполнению процессов нет, управление возвращается немедленно",
        "print(os.sched_getaffinity(0))  # → ? только Unix: ядра, между которыми планировщик и распределяет процесс, например {0, 1, 2, 3}",
        "os.sched_yield(0)  # → TypeError — sched_yield вызывается без аргументов"
      ],
      "related": [
        "os.sched_rr_get_interval",
        "os.sched_setscheduler",
        "os.nice"
      ],
      "related_errors": []
    },
    {
      "id": "os.sendfile",
      "title": "os.sendfile",
      "kind": "function",
      "summary": {
        "ru": "Копирует данные между дескрипторами напрямую в ядре, без буфера в пространстве пользователя. Доступно на Unix.",
        "en": "Copy data between descriptors in the kernel, without a user-space buffer. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sendfile(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sendfile",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — эффективное копирование",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "src = os.open('/tmp/send_src.txt', os.O_RDWR | os.O_CREAT | os.O_TRUNC)   # временный файл-источник",
        "print(os.write(src, b'hello world!'))   # → 12 — подготовили 12 байт данных",
        "dst = os.open('/tmp/send_dst.txt', os.O_WRONLY | os.O_CREAT | os.O_TRUNC)   # приёмник; на практике сюда подставляют дескриптор сокета",
        "print(os.sendfile(dst, src, 0, 5))   # → 5 — ядро переслало b'hello' с нулевого смещения, минуя память процесса",
        "print(os.sendfile(dst, src, 5, 100))   # → 7 — отправлен остаток файла, count можно брать с запасом",
        "print(os.sendfile(dst, src, 12, 10))   # → 0 — смещение за концом файла, отправлять нечего"
      ],
      "related": [
        "os.copy_file_range",
        "os.splice",
        "os.write"
      ],
      "related_errors": []
    },
    {
      "id": "os.sep-os.linesep-os.pathsep",
      "title": "os.sep / os.linesep / os.pathsep",
      "kind": "term",
      "summary": {
        "ru": "Системные разделители: sep — разделитель пути ('/' или '\\\\'), linesep — перевод строки, pathsep — разделитель PATH (':' или ';').",
        "en": "The system separators: sep — the path separator ('/' or '\\\\'), linesep — the line terminator, pathsep — the PATH separator (':' or ';')."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sep      # '/' на Unix, '\\\\' на Windows\nos.linesep  # '\\n' на Unix, '\\r\\n' на Windows\nos.pathsep  # ':' на Unix, ';' на Windows",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sep",
      "version": "",
      "section": "Модуль os",
      "subcat": "константы",
      "color_group": "module",
      "aliases": [],
      "keywords": [
        "os.sep",
        "os.linesep",
        "os.pathsep"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import os",
        "print(repr(os.sep))  # → '/'",
        "print(repr(os.linesep))  # → '\\n'",
        "print(repr(os.pathsep))  # → ':'",
        "paths = os.environ['PATH'].split(os.pathsep)  # → список директорий PATH",
        "full_path = 'home' + os.sep + 'user'  # → 'home/user'"
      ],
      "related": [
        "os.path.join",
        "оператор",
        "str.splitlines"
      ],
      "related_errors": []
    },
    {
      "id": "os.set_blocking",
      "title": "os.set_blocking",
      "kind": "function",
      "summary": {
        "ru": "Переключает дескриптор между блокирующим и неблокирующим режимом. Доступно на Unix.",
        "en": "Set a descriptor's blocking/non-blocking I/O mode. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.set_blocking(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.set_blocking",
      "version": "3.5",
      "section": "Модуль os",
      "subcat": "os — режим дескриптора",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()",
        "os.set_blocking(r, False)   # → только Unix: канал переведён в неблокирующий режим, возвращает None",
        "print(os.get_blocking(r))   # → только Unix: False — режим действительно сменился",
        "print(os.read(r, 10))   # → только Unix: BlockingIOError — данных нет, но чтение не ждёт, а сразу падает",
        "os.set_blocking(r, True)   # → только Unix: вернули блокирующий режим, теперь os.read(r, 10) будет ждать данные"
      ],
      "related": [
        "os.get_blocking",
        "blockingioerror",
        "os.pipe2",
        "os.read"
      ],
      "related_errors": []
    },
    {
      "id": "os.set_handle_inheritable",
      "title": "os.set_handle_inheritable()",
      "kind": "function",
      "summary": {
        "ru": "Задаёт флаг наследуемости дескриптора Windows (HANDLE) дочерними процессами; доступна только на Windows, для обычных fd используйте os.set_inheritable().",
        "en": "Sets the inheritable flag of a Windows handle for child processes; Windows only — use os.set_inheritable() for regular file descriptors."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.set_handle_inheritable(handle, inheritable, /)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.set_handle_inheritable",
      "version": "3.4",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [
        "флаг наследования хендла",
        "наследование дескриптора дочерним процессом",
        "разрешить наследование хендла"
      ],
      "keywords": [
        "os.set_handle_inheritable",
        "set_handle_inheritable"
      ],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(hasattr(os, 'set_handle_inheritable') == (os.name == 'nt'))   # → True (функция есть только на Windows)",
        "if os.name == 'nt':",
        "    import msvcrt",
        "    fd = os.open(os.devnull, os.O_RDONLY)",
        "    os.set_handle_inheritable(msvcrt.get_osfhandle(fd), True)   # разрешить наследование хендла",
        "    os.close(fd)",
        "r, w = os.pipe()",
        "os.set_inheritable(r, True)    # кроссплатформенный аналог для обычных fd",
        "print(os.get_inheritable(r))   # → True",
        "os.close(r); os.close(w)"
      ],
      "related": [
        "os.set_inheritable",
        "os.get_inheritable",
        "os.pipe",
        "os.dup"
      ],
      "related_errors": [
        "OSError",
        "AttributeError"
      ]
    },
    {
      "id": "os.set_inheritable",
      "title": "os.set_inheritable",
      "kind": "function",
      "summary": {
        "ru": "Задаёт флаг наследуемости файлового дескриптора дочерними процессами.",
        "en": "Set whether a file descriptor is inheritable by child processes."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.set_inheritable(fd, inheritable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.set_inheritable",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()",
        "os.set_inheritable(r, True)",
        "print(os.get_inheritable(r))   # → True",
        "os.close(r)",
        "os.close(w)"
      ],
      "related": [
        "os.get_inheritable",
        "os.dup",
        "os.pipe"
      ],
      "related_errors": []
    },
    {
      "id": "os.setegid",
      "title": "os.setegid",
      "kind": "function",
      "summary": {
        "ru": "Устанавливает эффективный GID процесса. Доступно на Unix.",
        "en": "Set the process's effective group id. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setegid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setegid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "saved_gid = os.getegid()  # → ? только Unix: текущий эффективный GID, например 1000",
        "os.setegid(os.getgid())  # → ? только Unix: временно понижает права до реальной группы пользователя",
        "print(os.getegid() == os.getgid())  # → ? True: эффективный GID совпал с реальным",
        "os.setegid(saved_gid)  # → ? только Unix: возврат к прежнему эффективному GID",
        "os.setegid(0)  # → PermissionError, если процесс запущен не от root"
      ],
      "related": [
        "os.getegid",
        "os.setgid",
        "os.seteuid"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.seteuid",
      "title": "os.seteuid",
      "kind": "function",
      "summary": {
        "ru": "Устанавливает эффективный UID процесса. Доступно на Unix.",
        "en": "Set the process's effective user id. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.seteuid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.seteuid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "saved_uid = os.geteuid()  # → ? только Unix: текущий эффективный UID, например 1000 (у root — 0)",
        "os.seteuid(os.getuid())  # → ? только Unix: сбрасывает эффективный UID до реального",
        "print(os.geteuid() == os.getuid())  # → ? True: процесс работает с правами реального пользователя",
        "os.seteuid(saved_uid)  # → ? только Unix: возврат прежнего эффективного UID (не-root может вернуться лишь к реальному или сохранённому)",
        "os.seteuid(0)  # → ? только Unix: PermissionError без прав root; в отличие от os.setuid() меняется только эффективный UID"
      ],
      "related": [
        "os.geteuid",
        "os.setuid",
        "os.setegid"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.setgid",
      "title": "os.setgid",
      "kind": "function",
      "summary": {
        "ru": "Устанавливает реальный GID процесса. Доступно на Unix.",
        "en": "Set the process's real group id. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setgid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setgid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getgid())  # → ? только Unix: реальный GID процесса, например 1000",
        "os.setgid(os.getgid())  # → ? только Unix: установка того же GID проходит без ошибки",
        "os.setgid(65534)  # → ? только Unix: root навсегда переходит в группу nogroup — вернуться уже нельзя",
        "print(os.getgid(), os.getegid())  # → ? 65534 65534: у root setgid меняет и реальный, и эффективный GID",
        "os.setgid(0)  # → PermissionError, если процесс запущен не от root"
      ],
      "related": [
        "os.getgid",
        "os.setegid",
        "os.setuid"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.setgroups",
      "title": "os.setgroups",
      "kind": "function",
      "summary": {
        "ru": "Задаёт список дополнительных групп процесса. Доступно на Unix.",
        "en": "Set the process's supplemental group ids. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setgroups(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setgroups",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getgroups())  # → ? только Unix: список дополнительных GID, например [4, 27, 1000]",
        "os.setgroups([])  # → ? только Unix: root отбирает у процесса все дополнительные группы",
        "print(os.getgroups())  # → ? []: дополнительных групп не осталось",
        "os.setgroups([1000, 1001])  # → PermissionError, если процесс запущен не от root",
        "os.setgroups(1000)  # → TypeError: нужна последовательность GID, а не одно число"
      ],
      "related": [
        "os.getgroups",
        "os.initgroups",
        "os.setgid"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.setns",
      "title": "os.setns",
      "kind": "function",
      "summary": {
        "ru": "Присоединяет процесс к пространству имён (namespace) по дескриптору (Python 3.12+). Доступно на Linux.",
        "en": "Attach the process to a namespace by file descriptor (3.12+). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setns(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setns",
      "version": "3.12",
      "section": "Модуль os",
      "subcat": "os — пространства имён",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/proc/1/ns/net', os.O_RDONLY)   # → ? только Linux: дескриптор сетевого namespace процесса с PID 1",
        "os.setns(fd, os.CLONE_NEWNET)   # → ? только Linux: текущий поток переходит в этот сетевой namespace (нужны права CAP_SYS_ADMIN)",
        "os.setns(fd)   # → ? nstype=0 по умолчанию: тип namespace не проверяется, берётся из самого дескриптора",
        "ns = open('/proc/self/ns/uts')",
        "os.setns(ns, os.CLONE_NEWUTS)   # → ? только Linux: вместо int можно передать любой объект с методом fileno()",
        "os.setns(ns, os.CLONE_NEWNET)   # → OSError: [Errno 22] Invalid argument — дескриптор UTS не соответствует флагу CLONE_NEWNET"
      ],
      "related": [
        "os.unshare",
        "os.pidfd_open",
        "os.fork"
      ],
      "related_errors": [
        "PermissionError",
        "OSError"
      ]
    },
    {
      "id": "os.setpgid",
      "title": "os.setpgid",
      "kind": "function",
      "summary": {
        "ru": "Помещает процесс в указанную группу процессов. Доступно на Unix.",
        "en": "Place a process into a given process group. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setpgid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setpgid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — сессии и группы процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "os.setpgid(0, 0)  # → ? только Unix: pid=0 и pgid=0 означают «текущий процесс, своя новая группа»",
        "print(os.getpgrp() == os.getpid())  # → ? только Unix: True — процесс стал лидером собственной группы",
        "child = os.fork()  # → ? только Unix: 0 в дочернем процессе, PID потомка в родительском",
        "os.setpgid(child, child)  # → ? только Unix: идиома job control — вызывают оба процесса (в потомке child == 0, «сам себе группа»), это снимает гонку с exec",
        "os.setpgid(os.getppid(), 0)  # → ? только Unix: ProcessLookupError — менять группу можно себе и своим потомкам, но не родителю"
      ],
      "related": [
        "os.getpgid",
        "os.setpgrp",
        "os.setsid"
      ],
      "related_errors": []
    },
    {
      "id": "os.setpgrp",
      "title": "os.setpgrp",
      "kind": "function",
      "summary": {
        "ru": "Делает текущий процесс лидером новой группы процессов. Доступно на Unix.",
        "en": "Make the current process a process-group leader. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setpgrp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setpgrp",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — сессии и группы процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getpgrp() == os.getpid())  # → ? только Unix: False, если процесс запущен shell'ом как часть чужой группы",
        "os.setpgrp()  # → ? только Unix: вызывается без аргументов, процесс становится лидером новой группы",
        "print(os.getpgrp() == os.getpid())  # → ? только Unix: True — PGID новой группы равен PID процесса",
        "print(os.getpgrp() == os.getpgid(0))  # → ? только Unix: True — os.setpgrp() делает то же, что os.setpgid(0, 0)",
        "os.setsid()  # → PermissionError — процесс уже лидер группы, новую сессию так не создать"
      ],
      "related": [
        "os.setpgid",
        "os.getpgrp",
        "os.setsid"
      ],
      "related_errors": []
    },
    {
      "id": "os.setpriority",
      "title": "os.setpriority",
      "kind": "function",
      "summary": {
        "ru": "Устанавливает приоритет планирования (nice) процесса/группы/пользователя. Доступно на Unix.",
        "en": "Set the scheduling priority (nice) of a process/group/user. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setpriority(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setpriority",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — сигналы и приоритет",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "os.setpriority(os.PRIO_PROCESS, 0, 10)   # → ? только Unix: None; текущему процессу (who=0) назначен nice 10 — значение задаётся, а не прибавляется, как в os.nice",
        "print(os.getpriority(os.PRIO_PROCESS, 0))   # → ? только Unix: 10",
        "os.setpriority(os.PRIO_PGRP, 0, 15)   # → ? только Unix: nice 15 сразу всей группе процессов",
        "import subprocess; proc = subprocess.Popen(['sleep', '60'])   # → ? фоновый потомок запущен",
        "os.setpriority(os.PRIO_PROCESS, proc.pid, 19)   # → ? только Unix: тяжёлая фоновая задача понижена до минимального приоритета",
        "os.setpriority(os.PRIO_PROCESS, 0, -5)   # → ? только Unix: PermissionError без прав root — повышать приоритет нельзя"
      ],
      "related": [
        "os.getpriority",
        "os.nice",
        "os.sched_setscheduler"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.setregid",
      "title": "os.setregid",
      "kind": "function",
      "summary": {
        "ru": "Одновременно задаёт реальный и эффективный GID. Доступно на Unix.",
        "en": "Set both the real and effective group ids. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setregid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setregid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getgid(), os.getegid())  # → ? только Unix: реальный и эффективный GID, например 1000 1000",
        "os.setregid(-1, os.getgid())  # → ? только Unix: -1 оставляет реальный GID прежним, меняется только эффективный",
        "os.setregid(65534, 65534)  # → ? только Unix: root одним вызовом переводит процесс в группу nogroup",
        "print(os.getgid(), os.getegid())  # → ? 65534 65534: оба GID сменились за один вызов",
        "os.setregid(0, 0)  # → PermissionError, если процесс запущен не от root"
      ],
      "related": [
        "os.setgid",
        "os.setresgid",
        "os.setreuid"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.setresgid",
      "title": "os.setresgid",
      "kind": "function",
      "summary": {
        "ru": "Задаёт реальный, эффективный и сохранённый GID. Доступно на Unix.",
        "en": "Set the real, effective and saved group ids. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setresgid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setresgid",
      "version": "3.2",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getresgid())  # → ? только Unix: тройка (реальный, эффективный, сохранённый) GID, например (0, 0, 0) под root",
        "os.setresgid(1000, 1000, 0)  # → ? только Unix: реальный и эффективный GID становятся 1000, сохранённый остаётся 0",
        "os.setresgid(-1, 0, -1)  # → ? только Unix: -1 не трогает поле; вернуть egid=0 позволяет именно сохранённый 0",
        "os.setresgid(1000, 1000, 1000)  # → ? только Unix: группу сбрасывают ДО os.setresuid — после смены UID прав на это уже не останется",
        "os.setresgid(0, 0, 0)  # → PermissionError, после полного сброса группу root уже не вернуть"
      ],
      "related": [
        "os.getresgid",
        "os.setregid",
        "os.setresuid"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.setresuid",
      "title": "os.setresuid",
      "kind": "function",
      "summary": {
        "ru": "Задаёт реальный, эффективный и сохранённый UID. Доступно на Unix.",
        "en": "Set the real, effective and saved user ids. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setresuid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setresuid",
      "version": "3.2",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getresuid())  # → ? только Unix: тройка (реальный, эффективный, сохранённый) UID, например (0, 0, 0) под root",
        "os.setresuid(1000, 1000, 0)  # → ? только Unix: реальный и эффективный UID становятся 1000, сохранённый остаётся 0",
        "os.setresuid(-1, 0, -1)  # → ? только Unix: -1 оставляет поле нетронутым; вернуть euid=0 удаётся благодаря сохранённому 0",
        "os.setresuid(1000, 1000, 1000)  # → ? только Unix: все три UID равны 1000 — привилегии сброшены окончательно",
        "os.setresuid(0, 0, 0)  # → PermissionError, после полного сброса root уже не вернуть"
      ],
      "related": [
        "os.getresuid",
        "os.setreuid",
        "os.setresgid"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.setreuid",
      "title": "os.setreuid",
      "kind": "function",
      "summary": {
        "ru": "Одновременно задаёт реальный и эффективный UID. Доступно на Unix.",
        "en": "Set both the real and effective user ids. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setreuid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setreuid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getuid(), os.geteuid())  # → ? только Unix: реальный и эффективный UID, например 0 0 под root",
        "os.setreuid(-1, 1000)  # → ? только Unix: -1 оставляет реальный UID прежним, понижается только эффективный",
        "os.seteuid(0)  # → ? только Unix: root возвращается, потому что реальный UID остался равен 0",
        "os.setreuid(1000, 1000)  # → ? только Unix: оба UID становятся 1000, сохранённый тоже сбрасывается — вернуться в root нельзя",
        "os.setreuid(0, 0)  # → ? только Unix: PermissionError после безвозвратного сброса привилегий"
      ],
      "related": [
        "os.setuid",
        "os.setresuid",
        "os.setregid"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.setsid",
      "title": "os.setsid",
      "kind": "function",
      "summary": {
        "ru": "Создаёт новую сессию, делая процесс её лидером (отвязка от терминала). Доступно на Unix.",
        "en": "Create a new session with the process as leader (detach from terminal). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setsid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setsid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — сессии и группы процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pid = os.fork()                          # → ? только Unix: 0 в дочернем процессе, PID ребёнка — в родительском",
        "if pid == 0: print(os.setsid())          # → None — ребёнок стал лидером новой сессии и отвязался от терминала",
        "print(os.getsid(0) == os.getpid())       # → ? True в ребёнке после setsid: id сессии совпадает с его PID",
        "print(os.getpgrp() == os.getpid())       # → ? True: setsid заодно создаёт новую группу процессов с тем же id",
        "os.setsid()                              # → PermissionError: процесс уже лидер группы, повторный вызов запрещён"
      ],
      "related": [
        "os.getsid",
        "os.setpgrp",
        "os.fork"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.setuid",
      "title": "os.setuid",
      "kind": "function",
      "summary": {
        "ru": "Устанавливает реальный UID процесса (требует привилегий). Доступно на Unix.",
        "en": "Set the process's real user id (needs privilege). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setuid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setuid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — идентификаторы пользователя/группы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.getuid(), os.geteuid())  # → ? только Unix: реальный и эффективный UID, например 0 0 под root",
        "os.setgid(1000)  # → ? только Unix: при сбросе привилегий группу меняют ПЕРВОЙ — после setuid прав на setgid уже не будет",
        "os.setuid(1000)  # → ? только Unix: у root меняет разом реальный, эффективный и сохранённый UID на 1000",
        "print(os.getuid(), os.geteuid())  # → ? только Unix: теперь 1000 1000 — типовой сброс привилегий демона завершён",
        "os.setuid(0)  # → PermissionError — в отличие от os.seteuid, вернуть root после setuid уже нельзя"
      ],
      "related": [
        "os.getuid",
        "os.seteuid",
        "os.setgid"
      ],
      "related_errors": [
        "PermissionError"
      ]
    },
    {
      "id": "os.setxattr",
      "title": "os.setxattr",
      "kind": "function",
      "summary": {
        "ru": "Устанавливает расширенный атрибут файла. Доступно на Linux.",
        "en": "Set an extended attribute on a file. Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.setxattr(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.setxattr",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — расширенные атрибуты (xattr)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "open('xattr_demo.txt', 'w').close()",
        "os.setxattr('xattr_demo.txt', 'user.author', b'anna')   # → ? только Linux: атрибут записан, возвращает None (имя обязано начинаться с user.)",
        "print(os.getxattr('xattr_demo.txt', 'user.author'))   # → b'anna'",
        "print(os.setxattr('xattr_demo.txt', 'user.author', b'clara', os.XATTR_CREATE))   # → OSError (EEXIST) — с flags=0 значение просто перезаписалось бы",
        "print(os.setxattr('xattr_demo.txt', 'user.tag', 'важное'))   # → TypeError — значение должно быть bytes, а не str"
      ],
      "related": [
        "os.getxattr",
        "os.removexattr",
        "os.listxattr"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.spawnl",
      "title": "os.spawnl",
      "kind": "function",
      "summary": {
        "ru": "Как spawnv, но аргументы перечислением. Доступно на Unix.",
        "en": "Like spawnv, but with inline arguments. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.spawnl(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.spawnl",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (spawn)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.P_WAIT, os.P_NOWAIT)  # → ? 0 1 — режимы: дождаться потомка или сразу вернуть PID",
        "code = os.spawnl(os.P_WAIT, '/bin/echo', 'echo', 'привет')  # → ? на Unix печатает «привет», в code — код выхода потомка (0)",
        "pid = os.spawnl(os.P_NOWAIT, '/bin/sleep', 'sleep', '1')  # → ? на Unix не ждёт завершения и возвращает PID потомка, например 12345",
        "os.spawnl(os.P_WAIT, '/bin/echo', 'привет')  # → ? печатает пустую строку: первый аргумент после пути — это argv[0] (имя программы), а не данные"
      ],
      "related": [
        "os.spawnv",
        "os.spawnlp",
        "os.spawnle"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.spawnle",
      "title": "os.spawnle",
      "kind": "function",
      "summary": {
        "ru": "Как spawnl, но с окружением (последний аргумент — env). Доступно на Unix.",
        "en": "Like spawnl, but with an environment dict as the last argument. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.spawnle(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.spawnle",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (spawn)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "code = os.spawnle(os.P_WAIT, '/bin/sh', 'sh', '-c', 'echo $GREETING', {'GREETING': 'привет'})  # → ? на Unix печатает «привет», в code — 0",
        "code = os.spawnle(os.P_WAIT, '/bin/sh', 'sh', '-c', 'exit 3', {'PATH': '/bin:/usr/bin'})  # → ? на Unix в code — 3, код выхода потомка",
        "code = os.spawnle(os.P_WAIT, '/bin/sh', 'sh', '-c', 'echo $LANG', dict(os.environ, LANG='C'))  # → ? на Unix печатает C: копия os.environ с подменённой переменной",
        "code = os.spawnle(os.P_WAIT, '/bin/sh', 'sh', '-c', 'echo [$HOME]', {})  # → ? на Unix печатает [] — env заменяет окружение целиком, а не дополняет его"
      ],
      "related": [
        "os.spawnl",
        "os.spawnve",
        "os.spawnlpe"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.spawnlp",
      "title": "os.spawnlp",
      "kind": "function",
      "summary": {
        "ru": "Как spawnl, но программа ищется в PATH. Доступно на Unix.",
        "en": "Like spawnl, but the program is looked up in PATH. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.spawnlp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.spawnlp",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (spawn)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "code = os.spawnlp(os.P_WAIT, 'echo', 'echo', 'привет')  # → ? только Unix: «p» ищет echo в PATH, полный путь не нужен; в code — 0",
        "pid = os.spawnlp(os.P_NOWAIT, 'sleep', 'sleep', '1')  # → ? только Unix: с P_NOWAIT сразу возвращается PID потомка, например 12345",
        "print(os.waitpid(pid, 0)[1])  # → ? только Unix: 0 — дождались потомка, запущенного с P_NOWAIT",
        "code = os.spawnlp(os.P_WAIT, 'нет-такой-программы', 'нет-такой-программы')  # → ? только Unix: в code — 127, исключения не будет: потомок не смог запустить программу"
      ],
      "related": [
        "os.spawnl",
        "os.spawnvp",
        "os.spawnlpe"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.spawnlpe",
      "title": "os.spawnlpe",
      "kind": "function",
      "summary": {
        "ru": "Как spawnlp, но с явным окружением. Доступно на Unix.",
        "en": "Like spawnlp, but with an explicit environment dict. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.spawnlpe(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.spawnlpe",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (spawn)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "env = {**os.environ, 'LANG': 'C'}   # у e-форм окружение задаётся явно и передаётся последним позиционным аргументом",
        "print(env['LANG'])   # → ? C",
        "code = os.spawnlpe(os.P_WAIT, 'echo', 'echo', 'hi', env)   # → ? только Unix: печатает hi, P_WAIT ждёт конца, code == 0",
        "pid = os.spawnlpe(os.P_NOWAIT, 'sleep', 'sleep', '5', env)   # → ? только Unix: не ждёт, возвращает идентификатор процесса, например 12345",
        "print(('echo', 'hi')[0])   # → ? echo: в l-форме имя программы пишется дважды — как file и как argv[0]"
      ],
      "related": [
        "os.spawnlp",
        "os.spawnle",
        "os.spawnvpe"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.spawnv",
      "title": "os.spawnv",
      "kind": "function",
      "summary": {
        "ru": "Запускает новую программу (путь + список аргументов) в отдельном процессе; возвращает PID или код возврата в зависимости от режима. Доступно на Unix.",
        "en": "Run a new program in a new process (path + args list). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.spawnv(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.spawnv",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (spawn)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import sys",
        "args = ['python', '-c', 'pass']   # argv[0] — имя программы, дальше её аргументы",
        "print(os.spawnv(os.P_WAIT, sys.executable, args))   # → 0",
        "pid = os.spawnv(os.P_NOWAIT, sys.executable, args)",
        "print(pid > 0)   # → True (P_NOWAIT не ждёт: сразу отдаёт идентификатор запущенного процесса)",
        "print(os.spawnv(os.P_WAIT, sys.executable, []))   # → ValueError"
      ],
      "related": [
        "os.spawnl",
        "os.spawnve",
        "os.spawnvp",
        "os.execv"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.spawnve",
      "title": "os.spawnve",
      "kind": "function",
      "summary": {
        "ru": "Как spawnv, но с явным окружением. Доступно на Unix.",
        "en": "Like spawnv, but with an explicit environment dict. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.spawnve(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.spawnve",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (spawn)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import sys",
        "env = {**os.environ, 'GREETING': 'hello'}   # env заменяет окружение целиком, поэтому его копируют из os.environ",
        "print(env['GREETING'])   # → hello",
        "print(os.spawnv(os.P_WAIT, sys.executable, ['python', '-c', 'pass']))   # → 0 (spawnve отличается только тем, что берёт env последним аргументом)",
        "print('PATH' in os.environ)   # → True",
        "print('PATH' in {'GREETING': 'hello'})   # → False (с таким «голым» env дочерний процесс останется без PATH)"
      ],
      "related": [
        "os.spawnv",
        "os.spawnle",
        "os.spawnvpe"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.spawnvp",
      "title": "os.spawnvp",
      "kind": "function",
      "summary": {
        "ru": "Как spawnv, но программа ищется в PATH. Доступно на Unix.",
        "en": "Like spawnv, but the program is looked up in PATH. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.spawnvp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.spawnvp",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (spawn)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print('PATH' in os.environ)   # → ? True: именно в PATH spawnvp ищет программу по имени",
        "code = os.spawnvp(os.P_WAIT, 'echo', ['echo', 'hi'])   # → ? только Unix: печатает hi, code == 0",
        "pid = os.spawnvp(os.P_NOWAIT, 'sleep', ['sleep', '5'])   # → ? только Unix: не ждёт, возвращает идентификатор процесса, например 12345",
        "print(os.path.basename('/bin/echo'))   # → ? echo: spawnvp хватит имени, а spawnv потребовал бы полный путь"
      ],
      "related": [
        "os.spawnv",
        "os.spawnlp",
        "os.spawnvpe"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.spawnvpe",
      "title": "os.spawnvpe",
      "kind": "function",
      "summary": {
        "ru": "Как spawnvp, но с явным окружением. Доступно на Unix.",
        "en": "Like spawnvp, but with an explicit environment dict. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.spawnvpe(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.spawnvpe",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ (spawn)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "env = {**os.environ, 'LANG': 'C'}   # p-форма ищет программу в PATH, e-форма берёт окружение из env",
        "print(env['LANG'])   # → ? C",
        "code = os.spawnvpe(os.P_WAIT, 'env', ['env'], env)   # → ? только Unix: печатает переменные окружения, code == 0",
        "pid = os.spawnvpe(os.P_NOWAIT, 'sleep', ['sleep', '5'], env)   # → ? только Unix: сразу возвращает идентификатор процесса, например 12345",
        "print('PATH' in {'LANG': 'C'})   # → False: с таким env поиск пойдёт по os.defpath, а не по вашему PATH"
      ],
      "related": [
        "os.spawnvp",
        "os.spawnve",
        "os.execvpe",
        "subprocess-run"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.splice",
      "title": "os.splice",
      "kind": "function",
      "summary": {
        "ru": "Перемещает данные между дескрипторами через канал (pipe) без копирования в пространство пользователя (Python 3.10+). Доступно на Linux.",
        "en": "Move data between descriptors via a pipe without user-space copies (3.10+). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.splice(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.splice",
      "version": "3.10",
      "section": "Модуль os",
      "subcat": "os — эффективное копирование",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()",
        "fd = os.open('/tmp/example.bin', os.O_RDONLY)",
        "n = os.splice(fd, w, 4096)",
        "print(n)   # → ? только Linux: сколько байт ядро перенесло в канал, не больше 4096",
        "print(os.read(r, n))   # → ? только Linux: те же самые байты — при переносе они не копировались в память процесса",
        "print(os.splice(fd, w, 65536, offset_src=0))   # → ? только Linux: перенос с начала файла, позиция fd при этом не сдвигается",
        "dst = os.open('/tmp/copy.bin', os.O_WRONLY | os.O_CREAT)",
        "print(os.splice(fd, dst, 4096))   # → ? только Linux: OSError [Errno 22] Invalid argument — хотя бы один из дескрипторов обязан быть каналом (pipe)"
      ],
      "related": [
        "os.copy_file_range",
        "os.sendfile",
        "os.pipe"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.startfile",
      "title": "os.startfile()",
      "kind": "function",
      "summary": {
        "ru": "Открывает файл или папку в ассоциированном приложении — как двойной клик в Проводнике; возвращается сразу после запуска, дождаться завершения нельзя. Только Windows; параметры arguments/cwd/show_cmd — с Python 3.10.",
        "en": "Start a file or folder with its associated application, like double-clicking it in Explorer; returns as soon as the application is launched, with no way to wait for it. Availability: Windows; the arguments/cwd/show_cmd parameters were added in Python 3.10."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.startfile(path, operation, arguments, cwd, show_cmd)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.startfile",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — запуск программ",
      "color_group": "module",
      "aliases": [
        "открыть файл в программе по умолчанию",
        "открыть папку в проводнике",
        "запустить файл ассоциированным приложением"
      ],
      "keywords": [
        "os.startfile",
        "startfile"
      ],
      "tags": [
        "os"
      ],
      "examples": [
        "import os, sys",
        "print(hasattr(os, 'startfile') == (sys.platform == 'win32'))   # → True",
        "print(not hasattr(os, 'startfile') or callable(os.startfile))   # → True",
        "opener = getattr(os, 'startfile', None)   # None на Linux/macOS",
        "print(opener is None or 'startfile' in repr(opener))   # → True",
        "# os.startfile('report.pdf')   # Windows: открыть файл в ассоциированном приложении"
      ],
      "related": [
        "os.system",
        "os.popen",
        "subprocess-run",
        "os.spawnl"
      ],
      "related_errors": [
        "FileNotFoundError",
        "OSError",
        "AttributeError"
      ]
    },
    {
      "id": "os.stat",
      "title": "os.stat",
      "kind": "function",
      "summary": {
        "ru": "Возвращает os.stat_result с метаданными файла (размер, права, времена).",
        "en": "Return an os.stat_result with a file's metadata."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.stat(path)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.stat",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловая система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.stat(os.__file__).st_size > 0)   # → True",
        "info = os.stat(os.__file__)",
        "print(info.st_size == os.path.getsize(os.__file__))   # → True — os.path.getsize просто читает поле st_size",
        "print(oct(info.st_mode & 0o777))   # → права файла восьмеричным числом, например 0o644 (на Windows обычно 0o666)",
        "print(os.stat(os.__file__, follow_symlinks=False).st_size == os.lstat(os.__file__).st_size)   # → True — с follow_symlinks=False это ровно os.lstat",
        "print(os.stat('нет-такого-файла.txt'))   # → FileNotFoundError"
      ],
      "related": [
        "os.stat_result",
        "os.lstat",
        "os.fstat",
        "os.path.getsize"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.stat_result",
      "title": "os.stat_result",
      "kind": "term",
      "summary": {
        "ru": "Тип результата os.stat()/os.fstat(): объект с полями st_size, st_mode, st_mtime и др.",
        "en": "The result type of os.stat()/os.fstat(): has st_size, st_mode, st_mtime, etc."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.stat(path)  # → os.stat_result",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.stat_result",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловая система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(isinstance(os.stat(os.__file__), os.stat_result))   # → True",
        "res = os.stat(os.__file__)",
        "print(res.st_size > 0, res.st_mtime > 0)   # → True True — размер в байтах и время последней записи в секундах эпохи",
        "print(len(tuple(res)))   # → 10 — stat_result ведёт себя как кортеж из десяти классических полей",
        "print(res[6] == res.st_size)   # → True — шестой элемент кортежной части и есть размер",
        "res.st_size = 0   # → AttributeError: readonly attribute — поля stat_result неизменяемы"
      ],
      "related": [
        "os.stat",
        "os.fstat",
        "os.lstat",
        "os.path.getsize"
      ],
      "related_errors": []
    },
    {
      "id": "os.statvfs",
      "title": "os.statvfs",
      "kind": "function",
      "summary": {
        "ru": "Возвращает статистику файловой системы по пути (размер блока, свободное место). Доступно на Unix.",
        "en": "Return filesystem statistics for a path (block size, free space). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.statvfs(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.statvfs",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловая система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "st = os.statvfs('/')   # → ? только Unix: статистика файловой системы, содержащей путь '/'",
        "print(st.f_bsize)   # → ? только Unix: предпочтительный размер блока ФС, например 4096",
        "print(round(st.f_bavail * st.f_frsize / 1024**3, 1))   # → ? только Unix: свободно гигабайт обычному пользователю, например 37.4",
        "print(st.f_bfree >= st.f_bavail)   # → True — часть свободных блоков зарезервирована под root",
        "print(os.statvfs('/нет-такого-пути'))   # → ? только Unix: FileNotFoundError"
      ],
      "related": [
        "os.statvfs_result",
        "os.fstatvfs"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.statvfs_result",
      "title": "os.statvfs_result",
      "kind": "term",
      "summary": {
        "ru": "Тип результата os.statvfs(): поля f_bsize, f_blocks, f_bfree и др. Доступно на Unix.",
        "en": "The result type of os.statvfs(): f_bsize, f_blocks, f_bfree, etc. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.statvfs_result(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.statvfs_result",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловая система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "st = os.statvfs('/')   # → ? только Unix: возвращается объект типа os.statvfs_result",
        "print(type(st).__name__)   # → ? только Unix: statvfs_result",
        "print(st[0] == st.f_bsize)   # → True — поля доступны и по индексу, и по имени",
        "print(st.f_namemax)   # → ? только Unix: предельная длина имени файла в этой ФС, например 255",
        "print(bool(st.f_flag & os.ST_RDONLY))   # → False, если ФС смонтирована на запись"
      ],
      "related": [
        "os.statvfs",
        "os.fstatvfs",
        "os.stat_result"
      ],
      "related_errors": []
    },
    {
      "id": "os.strerror",
      "title": "os.strerror",
      "kind": "function",
      "summary": {
        "ru": "Возвращает текстовое описание кода ошибки errno.",
        "en": "Return the text message for an errno error code."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.strerror(code)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.strerror",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import errno",
        "print(os.strerror(2))  # → No such file or directory",
        "print(os.strerror(errno.EACCES))  # → Permission denied",
        "print(OSError(2, os.strerror(2), 'data.csv'))  # → [Errno 2] No such file or directory: 'data.csv'",
        "print(os.strerror(9999))  # → текст для неизвестного кода зависит от ОС, например Unknown error 9999 (на части платформ — ValueError)"
      ],
      "related": [
        "oserror",
        "иерархия-исключений",
        "filenotfounderror"
      ],
      "related_errors": []
    },
    {
      "id": "os.symlink",
      "title": "os.symlink",
      "kind": "function",
      "summary": {
        "ru": "Создаёт символическую ссылку на путь. Доступно на Unix.",
        "en": "Create a symbolic link to a path. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.symlink(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.symlink",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — ссылки",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "os.symlink('data.txt', 'data-link.txt')   # → ? только Unix (на Windows нужны права разработчика): создаёт символическую ссылку",
        "print(os.path.islink('data-link.txt'))   # → True",
        "print(os.readlink('data-link.txt'))   # → ? data.txt",
        "os.symlink('logs', 'logs-link', target_is_directory=True)   # → ? ссылка на каталог; флаг учитывается только на Windows",
        "os.symlink('data.txt', 'data-link.txt')   # → FileExistsError — существующий dst молча не перезаписывается",
        "os.remove('data-link.txt')   # → ? удаляет саму ссылку, файл data.txt остаётся на месте"
      ],
      "related": [
        "os.readlink",
        "os.link",
        "os.path.islink"
      ],
      "related_errors": [
        "FileExistsError",
        "PermissionError"
      ]
    },
    {
      "id": "os.sync",
      "title": "os.sync",
      "kind": "function",
      "summary": {
        "ru": "Сбрасывает на диск буферы всех файловых систем. Доступно на Unix.",
        "en": "Flush all filesystem buffers to disk. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sync(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sync",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — синхронизация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "os.sync()   # → None — буферы всех смонтированных файловых систем сброшены на диск",
        "fd = os.open('/tmp/report.txt', os.O_WRONLY | os.O_CREAT)   # → ? дескриптор открытого файла, например 3",
        "print(os.write(fd, b'done\\n'))   # → 5",
        "os.sync()   # → ? только Unix: записанная строка теперь точно на диске — так страхуются перед перезагрузкой или снятием питания",
        "os.fsync(fd)   # → ? None; сбрасывает только этот файл — точечнее и дешевле, чем синхронизация всей системы"
      ],
      "related": [
        "os.fsync",
        "os.fdatasync"
      ],
      "related_errors": []
    },
    {
      "id": "os.sysconf",
      "title": "os.sysconf",
      "kind": "function",
      "summary": {
        "ru": "Возвращает числовое системное конфигурационное значение по имени (напр. число процессоров). Доступно на Unix.",
        "en": "Return an integer system configuration value by name. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.sysconf(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.sysconf",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — конфигурация",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.sysconf('SC_NPROCESSORS_ONLN'))  # → ? только Unix: число доступных ядер, например 8 (переносимый аналог — os.cpu_count())",
        "print(os.sysconf('SC_PAGESIZE'))  # → ? только Unix: размер страницы памяти в байтах, обычно 4096",
        "total = os.sysconf('SC_PHYS_PAGES') * os.sysconf('SC_PAGESIZE')",
        "print(total // 1024 ** 3)  # → ? только Unix: объём ОЗУ в гигабайтах, например 16",
        "print(os.sysconf('SC_NO_SUCH_LIMIT'))  # → ValueError: имя параметра не распознано"
      ],
      "related": [
        "os.confstr",
        "os.pathconf",
        "os.cpu_count"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "os.system",
      "title": "os.system",
      "kind": "function",
      "summary": {
        "ru": "Выполняет команду в системной оболочке и возвращает её код завершения.",
        "en": "Run a command in the system shell and return its exit status."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.system(command)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.system",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — аварийное завершение",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(os.system('echo привет'))   # → сначала оболочка печатает «привет», потом 0 — код успешного завершения",
        "code = os.system('exit 3')",
        "print(code)   # → зависит от ОС: 3 в Windows, 768 (то есть 3 << 8) в Unix",
        "print(os.waitstatus_to_exitcode(code))   # → только Unix: 3 — статус ожидания распакован в настоящий код возврата",
        "print(os.system('такой-команды-нет'))   # → оболочка печатает своё сообщение об ошибке, код ≠ 0; исключение НЕ бросается"
      ],
      "related": [
        "subprocess-run",
        "os.popen",
        "subprocess-check_output"
      ],
      "related_errors": []
    },
    {
      "id": "os.tcgetpgrp",
      "title": "os.tcgetpgrp",
      "kind": "function",
      "summary": {
        "ru": "Возвращает группу процессов, управляющую терминалом (по дескриптору). Доступно на Unix.",
        "en": "Return the process group controlling a terminal (by fd). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.tcgetpgrp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.tcgetpgrp",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — сессии и группы процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/dev/tty', os.O_RDONLY)              # → ? только Unix: дескриптор управляющего терминала",
        "print(os.tcgetpgrp(fd))                            # → ? только Unix: id группы процессов переднего плана, например 12345",
        "print(os.tcgetpgrp(fd) == os.getpgrp())            # → ? True, если скрипт работает на переднем плане (запущен без &)",
        "os.tcgetpgrp(os.open(os.devnull, os.O_RDONLY))     # → OSError: [Errno 25] Inappropriate ioctl for device — /dev/null не терминал"
      ],
      "related": [
        "os.tcsetpgrp",
        "os.getpgrp",
        "os.ctermid"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.tcsetpgrp",
      "title": "os.tcsetpgrp",
      "kind": "function",
      "summary": {
        "ru": "Задаёт группу процессов, управляющую терминалом. Доступно на Unix.",
        "en": "Set the process group controlling a terminal. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.tcsetpgrp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.tcsetpgrp",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — сессии и группы процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/dev/tty', os.O_RDWR)                # → ? только Unix: дескриптор управляющего терминала",
        "print(os.tcsetpgrp(fd, os.getpgrp()))              # → ? только Unix: None — своя группа выведена на передний план",
        "print(os.tcgetpgrp(fd) == os.getpgrp())            # → ? True: tcsetpgrp/tcgetpgrp — пара «записать/прочитать» группу терминала",
        "os.tcsetpgrp(fd, 999999)                           # → OSError: группы с таким id в текущей сессии нет",
        "os.tcsetpgrp(fd, os.getpgrp())                     # → ? из фонового процесса ядро пришлёт SIGTTOU и остановит его"
      ],
      "related": [
        "os.tcgetpgrp",
        "os.setpgid",
        "os.ctermid"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.terminal_size",
      "title": "os.terminal_size",
      "kind": "term",
      "summary": {
        "ru": "Тип результата os.get_terminal_size(): именованный кортеж с полями columns и lines.",
        "en": "The result type of os.get_terminal_size(): a named tuple (columns, lines)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.terminal_size((cols, lines))",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.terminal_size",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — терминал и устройства",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "ts = os.terminal_size((80, 24))",
        "print(ts.columns)   # → 80",
        "print(ts.lines)   # → 24",
        "print(ts)   # → os.terminal_size(columns=80, lines=24)",
        "print(ts[0], ts[1])   # → 80 24 — это именованный кортеж, поля доступны и по индексу",
        "cols, lines = ts",
        "print(f'{cols}x{lines}')   # → 80x24"
      ],
      "related": [
        "os.get_terminal_size",
        "collections.namedtuple",
        "os.isatty"
      ],
      "related_errors": []
    },
    {
      "id": "os.timerfd_create",
      "title": "os.timerfd_create",
      "kind": "function",
      "summary": {
        "ru": "Создаёт дескриптор таймера, срабатывания которого можно читать как события (Python 3.13+). Доступно на Linux.",
        "en": "Create a timer file descriptor whose expirations are readable as events (3.13+). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.timerfd_create(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.timerfd_create",
      "version": "3.13",
      "section": "Модуль os",
      "subcat": "os — события и таймеры (fd)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os, time",
        "fd = os.timerfd_create(time.CLOCK_MONOTONIC)  # → ? только Linux: файловый дескриптор таймера, например 3",
        "os.timerfd_settime(fd, initial=1.0)  # → ? только Linux: одноразовый сигнал через 1 секунду",
        "print(int.from_bytes(os.read(fd, 8), 'little'))  # → 1 — чтение блокировалось до срабатывания таймера",
        "os.close(fd)  # → ? только Linux: таймер остановлен вместе с закрытием дескриптора",
        "nb = os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)  # → ? только Linux: чтение до срабатывания даст BlockingIOError, а не блокировку"
      ],
      "related": [
        "os.timerfd_settime",
        "os.timerfd_gettime",
        "os.eventfd"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.timerfd_gettime",
      "title": "os.timerfd_gettime",
      "kind": "function",
      "summary": {
        "ru": "Возвращает оставшееся время и период таймер-дескриптора (в секундах). Доступно на Linux.",
        "en": "Return a timerfd's remaining time and interval (in seconds). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.timerfd_gettime(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.timerfd_gettime",
      "version": "3.13",
      "section": "Модуль os",
      "subcat": "os — события и таймеры (fd)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os, time",
        "fd = os.timerfd_create(time.CLOCK_MONOTONIC)  # → ? только Linux: дескриптор таймера",
        "print(os.timerfd_gettime(fd))  # → (0.0, 0.0) — таймер создан, но ещё не заведён",
        "os.timerfd_settime(fd, initial=10.0, interval=2.0)  # → ? только Linux: старт через 10 с, дальше каждые 2 с",
        "print(os.timerfd_gettime(fd))  # → ? только Linux: примерно (9.99, 2.0) — остаток до срабатывания и период",
        "os.close(fd)  # → ? только Linux: после закрытия os.timerfd_gettime(fd) даст OSError [Errno 9] Bad file descriptor"
      ],
      "related": [
        "os.timerfd_settime",
        "os.timerfd_gettime_ns",
        "os.timerfd_create"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.timerfd_gettime_ns",
      "title": "os.timerfd_gettime_ns",
      "kind": "function",
      "summary": {
        "ru": "Как timerfd_gettime, но в наносекундах. Доступно на Linux.",
        "en": "Like timerfd_gettime, but in nanoseconds. Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.timerfd_gettime_ns(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.timerfd_gettime_ns",
      "version": "3.13",
      "section": "Модуль os",
      "subcat": "os — события и таймеры (fd)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os, time",
        "fd = os.timerfd_create(time.CLOCK_MONOTONIC)  # → ? только Linux: дескриптор таймера",
        "os.timerfd_settime_ns(fd, initial=2_000_000_000, interval=500_000_000)  # → ? только Linux: старт через 2 с, период 0.5 с",
        "print(os.timerfd_gettime_ns(fd))  # → ? только Linux: примерно (1999987654, 500000000) — остаток и период целыми наносекундами",
        "print(os.timerfd_gettime(fd)[1])  # → 0.5 — тот же период, но секундами (float)",
        "os.timerfd_settime_ns(fd, initial=0, interval=0)  # → ? только Linux: таймер выключен, дальше os.timerfd_gettime_ns(fd) вернёт (0, 0)"
      ],
      "related": [
        "os.timerfd_gettime",
        "os.timerfd_settime_ns",
        "os.timerfd_create"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.timerfd_settime",
      "title": "os.timerfd_settime",
      "kind": "function",
      "summary": {
        "ru": "Задаёт время старта и период таймер-дескриптора (в секундах, float). Доступно на Linux.",
        "en": "Arm or disarm a timerfd (start and interval in seconds). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.timerfd_settime(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.timerfd_settime",
      "version": "3.13",
      "section": "Модуль os",
      "subcat": "os — события и таймеры (fd)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os, time",
        "fd = os.timerfd_create(time.CLOCK_MONOTONIC)  # → ? только Linux: дескриптор таймера",
        "print(os.timerfd_settime(fd, initial=1.5, interval=0.5))  # → (0.0, 0.0) — прежних настроек не было; сигнал через 1.5 с, дальше каждые 0.5 с",
        "print(int.from_bytes(os.read(fd, 8), 'little'))  # → 1 — чтение ждало первого срабатывания",
        "print(os.timerfd_settime(fd, flags=os.TFD_TIMER_ABSTIME, initial=time.clock_gettime(time.CLOCK_MONOTONIC) + 3.0))  # → ? только Linux: примерно (0.5, 0.5) — прежние значения; новый срок задан абсолютным моментом часов",
        "print(os.timerfd_settime(fd, initial=0.0))  # → ? только Linux: примерно (2.9, 0.0) — initial=0.0 выключает таймер"
      ],
      "related": [
        "os.timerfd_gettime",
        "os.timerfd_settime_ns",
        "os.timerfd_create"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.timerfd_settime_ns",
      "title": "os.timerfd_settime_ns",
      "kind": "function",
      "summary": {
        "ru": "Как timerfd_settime, но в наносекундах (int). Доступно на Linux.",
        "en": "Like timerfd_settime, but in nanoseconds (int). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.timerfd_settime_ns(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.timerfd_settime_ns",
      "version": "3.13",
      "section": "Модуль os",
      "subcat": "os — события и таймеры (fd)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os, time",
        "fd = os.timerfd_create(time.CLOCK_MONOTONIC)  # → ? только Linux: дескриптор таймера",
        "print(os.timerfd_settime_ns(fd, initial=1_000_000_000, interval=250_000_000))  # → (0, 0) — прежние значения в наносекундах; старт через 1 с, период 0.25 с",
        "print(int.from_bytes(os.read(fd, 8), 'little'))  # → 1 — чтение ждало первого срабатывания",
        "print(os.timerfd_settime_ns(fd, initial=0, interval=0))  # → ? только Linux: примерно (250000000, 250000000) — прежние остаток и период; таймер выключен",
        "os.timerfd_settime_ns(fd, initial=0.5)  # → TypeError — нужны целые наносекунды, дробные секунды принимает os.timerfd_settime"
      ],
      "related": [
        "os.timerfd_settime",
        "os.timerfd_gettime_ns",
        "os.timerfd_create"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.times",
      "title": "os.times",
      "kind": "function",
      "summary": {
        "ru": "Возвращает именованный кортеж процессорного времени (user/system/…, 5 полей).",
        "en": "Return a named tuple of process times (user/system/…, 5 fields)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.times()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.times",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — процесс",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(len(os.times()))   # → 5",
        "print(os.times().user)   # → процессорное время в пользовательском режиме, в секундах, например 0.09",
        "c0 = os.times().user",
        "sum(range(10 ** 6))   # → нагружаем процессор, сам результат не нужен",
        "print(round(os.times().user - c0, 2))   # → прирост CPU-времени за цикл, например 0.03",
        "print(os.times().elapsed)   # → на Unix — секунды от некоторой точки в прошлом; на Windows известны только user и system, остальные поля равны 0.0"
      ],
      "related": [
        "os.times_result",
        "os.cpu_count",
        "os.getloadavg"
      ],
      "related_errors": []
    },
    {
      "id": "os.times_result",
      "title": "os.times_result",
      "kind": "term",
      "summary": {
        "ru": "Тип результата os.times(): именованный кортеж с полями user/system/children_user/children_system/elapsed.",
        "en": "The result type of os.times(): a named tuple (user/system/children_*/elapsed)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.times_result(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.times",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — процесс",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "tr = os.times_result((1.0, 2.0, 3.0, 4.0, 5.0))",
        "print(tr.elapsed)   # → 5.0",
        "print(tr.user, tr.system)   # → 1.0 2.0",
        "print(tuple(tr))   # → (1.0, 2.0, 3.0, 4.0, 5.0)",
        "print(isinstance(os.times(), os.times_result))   # → True: именно этот тип возвращает os.times()",
        "os.times_result((1.0, 2.0))   # → TypeError: конструктору нужна последовательность ровно из 5 элементов"
      ],
      "related": [
        "os.times",
        "os.getloadavg"
      ],
      "related_errors": []
    },
    {
      "id": "os.truncate",
      "title": "os.truncate",
      "kind": "function",
      "summary": {
        "ru": "Усекает файл по пути до указанной длины.",
        "en": "Truncate the file at a path to a given length."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.truncate(path, length)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.truncate",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "fd, p = tempfile.mkstemp()",
        "os.write(fd, b'hello')",
        "os.close(fd)",
        "os.truncate(p, 2)",
        "print(os.path.getsize(p))   # → 2",
        "os.remove(p)"
      ],
      "related": [
        "os.ftruncate",
        "file-truncate",
        "os.path.getsize"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.ttyname",
      "title": "os.ttyname",
      "kind": "function",
      "summary": {
        "ru": "Возвращает путь к терминалу, связанному с файловым дескриптором. Доступно на Unix.",
        "en": "Return the terminal path associated with a file descriptor. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.ttyname(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.ttyname",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — псевдотерминалы (pty)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.ttyname(0))  # → ? только Unix: имя терминала для stdin, например /dev/pts/2",
        "master, slave = os.openpty()  # → ? только Unix: своя пара псевдотерминала",
        "print(os.ttyname(slave))  # → ? только Unix: путь slave-конца, например /dev/pts/3",
        "print(os.ttyname(slave) == os.ptsname(master))  # → ? только Unix: True — ttyname и ptsname указывают на один и тот же slave",
        "print(os.ttyname(1) if os.isatty(1) else 'вывод перенаправлен')  # → ? только Unix: имя терминала при запуске в консоли; при перенаправлении в файл ttyname(1) бросил бы OSError"
      ],
      "related": [
        "os.isatty",
        "os.ctermid",
        "os.ptsname"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.umask",
      "title": "os.umask",
      "kind": "function",
      "summary": {
        "ru": "Устанавливает маску прав создаваемых файлов и возвращает предыдущее значение.",
        "en": "Set the file-creation mode mask and return the previous value."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.umask(mask)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.umask",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — процесс",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "old = os.umask(0o022)",
        "os.umask(old)",
        "print(isinstance(old, int))   # → True"
      ],
      "related": [
        "os.chmod",
        "os.mkdir",
        "os.access"
      ],
      "related_errors": []
    },
    {
      "id": "os.uname",
      "title": "os.uname",
      "kind": "function",
      "summary": {
        "ru": "Возвращает информацию об операционной системе (имя ядра, хост, версия, архитектура). Доступно на Unix.",
        "en": "Return information about the operating system (kernel, host, release, machine). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.uname(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.uname",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "import platform",
        "print(os.uname())  # → ? только Unix: posix.uname_result(sysname='Linux', nodename='myhost', release='6.8.0-45-generic', version='#45-Ubuntu SMP ...', machine='x86_64')",
        "print(os.uname().sysname)  # → ? только Unix: имя ядра, например Linux (на macOS — Darwin)",
        "sysname, nodename, release, version, machine = os.uname()  # → ? только Unix: результат распаковывается как кортеж из пяти строк",
        "print(f'{sysname} {release} на {machine}')  # → ? только Unix: например Linux 6.8.0-45-generic на x86_64",
        "print(platform.uname().system)  # → ? кроссплатформенная замена: Linux / Darwin / Windows (сам os.uname() на Windows бросает AttributeError)"
      ],
      "related": [
        "os.uname_result",
        "sys.version-sys.platform-sys.implementat",
        "os.sysconf"
      ],
      "related_errors": []
    },
    {
      "id": "os.uname_result",
      "title": "os.uname_result",
      "kind": "term",
      "summary": {
        "ru": "Тип результата os.uname(): поля sysname, nodename, release, version, machine. Доступно на Unix.",
        "en": "The result type of os.uname(): sysname, nodename, release, version, machine. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.uname_result(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.uname_result",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "info = os.uname()  # → ? только Unix: os.uname() возвращает экземпляр os.uname_result",
        "print(info.sysname, info.machine)  # → ? только Unix: имя ядра и архитектура, например Linux x86_64",
        "print(info[0] == info.sysname)  # → True — поля доступны и по индексу, и по имени",
        "print(isinstance(info, tuple))  # → True — uname_result наследуется от tuple, поэтому распаковывается"
      ],
      "related": [
        "os.uname",
        "os.stat_result",
        "collections.namedtuple"
      ],
      "related_errors": []
    },
    {
      "id": "os.unlink",
      "title": "os.unlink",
      "kind": "function",
      "summary": {
        "ru": "Удаляет файл (синоним os.remove).",
        "en": "Remove a file (a synonym of os.remove)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.unlink(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.unlink",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — удаление",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "fd, p = tempfile.mkstemp()",
        "os.close(fd)",
        "os.unlink(p)",
        "print(os.path.exists(p))   # → False"
      ],
      "related": [
        "os.remove",
        "path.unlink",
        "os.rmdir"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError",
        "IsADirectoryError"
      ]
    },
    {
      "id": "os.unlockpt",
      "title": "os.unlockpt",
      "kind": "function",
      "summary": {
        "ru": "Разблокирует slave-устройство псевдотерминала для открытия. Доступно на Unix.",
        "en": "Unlock the slave pseudo-terminal for opening. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.unlockpt(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.unlockpt",
      "version": "3.13",
      "section": "Модуль os",
      "subcat": "os — псевдотерминалы (pty)",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "master = os.posix_openpt(os.O_RDWR)  # → ? только Unix: master-дескриптор, slave пока заблокирован",
        "os.unlockpt(master)  # → ? только Unix: ничего не возвращает; os.openpty() делает этот шаг сам",
        "slave = os.open(os.ptsname(master), os.O_RDWR)  # → ? только Unix: теперь slave-конец открывается штатно",
        "m2 = os.posix_openpt(os.O_RDWR)  # → ? только Unix: вторая пара, unlockpt намеренно не вызываем",
        "os.open(os.ptsname(m2), os.O_RDWR)  # → ? только Unix: OSError (EIO) — без unlockpt slave открыть нельзя"
      ],
      "related": [
        "os.grantpt",
        "os.posix_openpt",
        "os.ptsname"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.unsetenv",
      "title": "os.unsetenv",
      "kind": "function",
      "summary": {
        "ru": "Удаляет переменную окружения на уровне ОС.",
        "en": "Unset an environment variable at the OS level."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.unsetenv(key)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.unsetenv",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — окружение",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "os.environ['GLOSSARY_DEMO'] = '42'   # → переменная попала и в словарь os.environ, и в окружение ОС",
        "os.unsetenv('GLOSSARY_DEMO')   # → в окружении ОС переменной больше нет: дочерние процессы её не увидят",
        "print(os.environ.get('GLOSSARY_DEMO'))   # → 42 (unsetenv не трогает словарь os.environ — рассинхрон)",
        "os.environ['GLOSSARY_DEMO'] = '42'   # → возвращаем переменную, чтобы удалить её штатным способом",
        "del os.environ['GLOSSARY_DEMO']   # → del сам вызывает os.unsetenv и чистит словарь — рассинхрона нет",
        "print(os.getenv('GLOSSARY_DEMO'))   # → None"
      ],
      "related": [
        "os.putenv",
        "os.environ",
        "os.getenv"
      ],
      "related_errors": []
    },
    {
      "id": "os.unshare",
      "title": "os.unshare",
      "kind": "function",
      "summary": {
        "ru": "Отсоединяет части исполнительного контекста процесса в новые пространства имён (Python 3.12+). Доступно на Linux.",
        "en": "Disassociate parts of the process's execution context into new namespaces (3.12+). Availability: Linux."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.unshare(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.unshare",
      "version": "3.12",
      "section": "Модуль os",
      "subcat": "os — пространства имён",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "os.unshare(os.CLONE_NEWUTS)   # → ? только Linux: процесс получает собственное UTS-пространство имён (нужны права CAP_SYS_ADMIN)",
        "os.unshare(os.CLONE_NEWNS | os.CLONE_NEWUTS)   # → ? только Linux: флаги комбинируются побитовым ИЛИ, всё отделяется одним вызовом",
        "os.unshare(os.CLONE_NEWNET)   # → ? у обычного пользователя без CAP_SYS_ADMIN: PermissionError: [Errno 1] Operation not permitted",
        "os.unshare(os.CLONE_NEWUSER)   # → ? только Linux: единственный флаг, доступный непривилегированному процессу; внутри нового user namespace становятся доступны и остальные",
        "os.unshare(os.CLONE_NEWPID)   # → ? ловушка: сам процесс остаётся в прежнем PID-namespace, в новый попадут только его потомки"
      ],
      "related": [
        "os.setns",
        "os.fork",
        "os.chroot"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": "os.urandom",
      "title": "os.urandom",
      "kind": "function",
      "summary": {
        "ru": "Возвращает n случайных байтов от криптографического генератора ОС.",
        "en": "Return n cryptographically strong random bytes from the OS."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.urandom(n)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.urandom",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — система",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "print(len(os.urandom(8)))   # → 8",
        "print(os.urandom(0))   # → b''",
        "token = os.urandom(16)",
        "print(len(token.hex()))   # → 32, по два hex-символа на байт — типовой способ получить токен",
        "print(os.urandom(4) == os.urandom(4))   # → False, каждый вызов даёт новые байты",
        "print(os.urandom(-1))   # → ValueError"
      ],
      "related": [
        "os.getrandom",
        "random.systemrandom",
        "random.randbytes"
      ],
      "related_errors": []
    },
    {
      "id": "os.utime",
      "title": "os.utime",
      "kind": "function",
      "summary": {
        "ru": "Устанавливает время доступа и изменения файла (atime, mtime).",
        "en": "Set a file's access and modification times (atime, mtime)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.utime(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.utime",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — метаданные",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "import tempfile",
        "fd, p = tempfile.mkstemp()",
        "os.close(fd)",
        "os.utime(p, (100, 200))",
        "print(os.path.getmtime(p))   # → 200.0",
        "os.remove(p)"
      ],
      "related": [
        "os.path.getmtime",
        "os.stat",
        "os.path.getatime"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "os.wait",
      "title": "os.wait",
      "kind": "function",
      "summary": {
        "ru": "Ждёт завершения любого дочернего процесса; возвращает (pid, статус). Доступно на Unix.",
        "en": "Wait for any child to finish; returns (pid, status). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.wait(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.wait",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — ожидание процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pid = os.fork()  # → ? только Unix: 0 в дочернем процессе, pid ребёнка — в родительском",
        "if pid == 0: os._exit(3)  # ребёнок завершается кодом 3 и не выполняет остальной скрипт",
        "child, status = os.wait()  # родитель блокируется здесь, пока не завершится любой из его детей",
        "print(child == pid, os.waitstatus_to_exitcode(status))  # → ? True 3 — дождались того самого ребёнка, из сырого статуса извлечён код выхода",
        "print(os.wait())  # → ChildProcessError — незавершённых детей больше нет, ждать некого"
      ],
      "related": [
        "os.waitpid",
        "os.wait3",
        "os.waitstatus_to_exitcode"
      ],
      "related_errors": [
        "ChildProcessError"
      ]
    },
    {
      "id": "os.wait3",
      "title": "os.wait3",
      "kind": "function",
      "summary": {
        "ru": "Как wait, но дополнительно возвращает информацию об использовании ресурсов (rusage). Доступно на Unix.",
        "en": "Like wait, but also returns resource-usage info (rusage). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.wait3(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.wait3",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — ожидание процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "if os.fork() == 0: os._exit(0)  # → ? только Unix: ребёнок завершается сразу, родителю будет кого дождаться",
        "child, status, rusage = os.wait3(0)  # options обязателен: 0 — блокирующее ожидание любого ребёнка",
        "print(child, os.waitstatus_to_exitcode(status))  # → ? только Unix: pid ребёнка и 0, например «12347 0»",
        "print(rusage.ru_maxrss)  # → ? только Unix: пиковая память ребёнка (на Linux — в килобайтах), например 3200 — этого os.wait не возвращает",
        "print(os.wait3(os.WNOHANG))  # → ChildProcessError — WNOHANG не блокирует, но и детей уже не осталось"
      ],
      "related": [
        "os.wait",
        "os.wait4",
        "os.waitpid"
      ],
      "related_errors": [
        "ChildProcessError"
      ]
    },
    {
      "id": "os.wait4",
      "title": "os.wait4",
      "kind": "function",
      "summary": {
        "ru": "Как waitpid, но дополнительно возвращает rusage. Доступно на Unix.",
        "en": "Like waitpid, but also returns rusage. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.wait4(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.wait4",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — ожидание процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "if os.fork() == 0: os._exit(7)  # → ? только Unix: ребёнок завершается кодом 7, родитель продолжает скрипт",
        "child, status, rusage = os.wait4(-1, 0)  # pid=-1 — дождаться любого ребёнка; options=0 — блокирующе",
        "print(child, os.waitstatus_to_exitcode(status))  # → ? только Unix: pid ребёнка и 7, например «12346 7»",
        "print(rusage.ru_utime)  # → ? только Unix: пользовательское процессорное время ребёнка в секундах, например 0.002 — os.waitpid такого не даёт",
        "print(os.wait4(-1, 0))  # → ChildProcessError — статус уже забран, второй раз его не получить"
      ],
      "related": [
        "os.waitpid",
        "os.wait3",
        "os.wait"
      ],
      "related_errors": [
        "ChildProcessError"
      ]
    },
    {
      "id": "os.waitid",
      "title": "os.waitid",
      "kind": "function",
      "summary": {
        "ru": "Гибко ждёт изменения состояния дочернего процесса; возвращает объект с подробностями. Доступно на Unix.",
        "en": "Flexibly wait for a child's state change; returns a details object. Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.waitid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.waitid",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — ожидание процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pid = os.fork()   # → ? только Unix: 0 в дочернем процессе, PID потомка в родительском",
        "if pid == 0: os._exit(3)   # → ? потомок сразу завершается с кодом возврата 3",
        "print(os.waitid(os.P_PID, pid, os.WEXITED | os.WNOHANG | os.WNOWAIT))   # → ? None, если потомок ещё не завершился; WNOWAIT не «съедает» статус — дождаться можно ещё раз",
        "info = os.waitid(os.P_PID, pid, os.WEXITED)   # → ? блокирующее ожидание именно этого потомка, вернёт os.waitid_result",
        "print(info.si_pid == pid, info.si_status)   # → ? True 3",
        "print(info.si_code == os.CLD_EXITED)   # → True — потомок завершился сам, а не был убит сигналом",
        "print(os.waitid(os.P_PID, pid, os.WEXITED))   # → ChildProcessError: этого потомка уже дождались, ждать больше некого"
      ],
      "related": [
        "os.waitid_result",
        "os.waitpid",
        "os.wait"
      ],
      "related_errors": [
        "ChildProcessError"
      ]
    },
    {
      "id": "os.waitid_result",
      "title": "os.waitid_result",
      "kind": "term",
      "summary": {
        "ru": "Тип результата os.waitid(): именованный кортеж (si_pid, si_uid, si_signo, si_status, si_code). Доступно на Unix.",
        "en": "The result type of os.waitid(): a named tuple (si_pid, si_uid, …). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.waitid_result(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.waitid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — ожидание процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pid = os.fork()   # → ? только Unix: 0 в дочернем процессе, PID потомка в родительском",
        "if pid == 0: os._exit(5)   # → ? потомок сразу завершается с кодом возврата 5",
        "info = os.waitid(os.P_PID, pid, os.WEXITED)   # → ? только Unix: объект os.waitid_result",
        "print(info.si_status, info.si_code == os.CLD_EXITED)   # → ? 5 True",
        "print(info.si_pid == pid, info.si_uid == os.getuid())   # → ? True True",
        "print(info[3])   # → 5 — это именованный кортеж: si_status доступен и по индексу",
        "print(info.si_signo)   # → ? номер сигнала SIGCHLD (на Linux 17) — waitid всегда сообщает именно о нём"
      ],
      "related": [
        "os.waitid",
        "os.waitpid"
      ],
      "related_errors": []
    },
    {
      "id": "os.waitpid",
      "title": "os.waitpid",
      "kind": "function",
      "summary": {
        "ru": "Ждёт завершения конкретного дочернего процесса по PID; возвращает (pid, статус). Доступно на Unix.",
        "en": "Wait for a specific child by PID; returns (pid, status). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.waitpid(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.waitpid",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — ожидание процессов",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "pid = os.fork()   # → ? только Unix: 0 в дочернем процессе, PID потомка в родительском",
        "if pid == 0: os._exit(7)   # → ? потомок сразу завершается с кодом возврата 7",
        "print(os.waitpid(pid, os.WNOHANG))   # → ? (0, 0), если потомок ещё не завершился — с WNOHANG вызов не блокирует",
        "pid_done, status = os.waitpid(pid, 0)   # → ? блокирует до завершения: (PID потомка, закодированный статус)",
        "print(os.waitstatus_to_exitcode(status))   # → 7 — код возврата, извлечённый из статуса",
        "print(os.WIFEXITED(status), os.WEXITSTATUS(status))   # → ? True 7",
        "print(os.waitpid(pid, 0))   # → ChildProcessError: этого потомка уже дождались, ждать больше некого"
      ],
      "related": [
        "os.wait",
        "os.waitstatus_to_exitcode",
        "os.fork",
        "os.wait4"
      ],
      "related_errors": [
        "ChildProcessError"
      ]
    },
    {
      "id": "os.waitstatus_to_exitcode",
      "title": "os.waitstatus_to_exitcode",
      "kind": "function",
      "summary": {
        "ru": "Преобразует «сырой» статус завершения (от waitpid) в код возврата процесса (Python 3.9+).",
        "en": "Convert a raw wait status (from waitpid) to an exit code (3.9+)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.waitstatus_to_exitcode(status)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.waitstatus_to_exitcode",
      "version": "3.9",
      "section": "Модуль os",
      "subcat": "os — процесс",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "print(os.waitstatus_to_exitcode(0))   # → 0",
        "print(os.waitstatus_to_exitcode(1 << 8))   # → 1",
        "status = 3 << 8   # так кодируется потомок, завершившийся с кодом 3",
        "print(os.waitstatus_to_exitcode(status))   # → 3",
        "print(os.waitstatus_to_exitcode(9))   # → -9 — потомок убит сигналом 9 (SIGKILL), результат отрицательный",
        "print(os.waitstatus_to_exitcode(127))   # → ValueError — процесс остановлен, а не завершён"
      ],
      "related": [
        "os.waitpid",
        "os.WEXITSTATUS",
        "os.WIFEXITED",
        "os.system"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "os.walk",
      "title": "os.walk()",
      "kind": "function",
      "summary": {
        "ru": "Генератор обхода дерева директорий. Для каждой папки возвращает (dirpath, dirnames, filenames).",
        "en": "A generator that walks a directory tree. For every folder it yields (dirpath, dirnames, filenames)."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.walk(top, topdown=True, onerror=None, followlinks=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.walk",
      "version": "",
      "section": "Модуль os",
      "subcat": "обход",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import os",
        "for root, dirs, files in os.walk('.'):",
        "print(root)  # → текущая папка",
        "all_py = []",
        "for root, dirs, files in os.walk('.'):",
        "for f in files:",
        "if f.endswith('.py'):",
        "all_py.append(os.path.join(root, f))  # → все .py файлы рекурсивно",
        "total = sum(len(files) for _, _, files in os.walk('.'))  # → общее число файлов",
        "print(total)  # → кол-во файлов"
      ],
      "related": [
        "os.listdir",
        "path.rglob",
        "os-scandir"
      ],
      "related_errors": []
    },
    {
      "id": "os.write",
      "title": "os.write",
      "kind": "function",
      "summary": {
        "ru": "Пишет байты в файловый дескриптор, возвращая число записанных байтов.",
        "en": "Write bytes to a file descriptor, returning the number written."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.write(fd, data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.write",
      "version": "",
      "section": "Модуль os",
      "subcat": "os — файловые дескрипторы",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os"
      ],
      "examples": [
        "import os",
        "r, w = os.pipe()",
        "print(os.write(w, b'hi'))   # → 2",
        "os.close(r)",
        "os.close(w)"
      ],
      "related": [
        "os.read",
        "os.pwrite",
        "file.write",
        "os.fsync"
      ],
      "related_errors": [
        "OSError",
        "TypeError"
      ]
    },
    {
      "id": "os.writev",
      "title": "os.writev",
      "kind": "function",
      "summary": {
        "ru": "Пишет в дескриптор сразу из нескольких буферов (gather write). Доступно на Unix.",
        "en": "Write to a descriptor from multiple buffers (gather write). Availability: Unix."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "os.writev(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/os.html#os.writev",
      "version": "3.3",
      "section": "Модуль os",
      "subcat": "os — позиционный ввод-вывод",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "os",
        "platform:posix"
      ],
      "examples": [
        "import os",
        "fd = os.open('/tmp/writev-demo.txt', os.O_RDWR | os.O_CREAT | os.O_TRUNC)  # временный файл для опытов",
        "print(os.writev(fd, [b'name;', b'age\\n']))  # → 9 — обе части записаны одним системным вызовом",
        "print(os.writev(fd, [b'Ann;', b'20\\n']))  # → 7 — writev пишет с текущей позиции, дописывая в конец",
        "print(os.pread(fd, 16, 0))  # → b'name;age\\nAnn;20\\n'",
        "print(os.writev(fd, []))  # → 0 — пустой список буферов ничего не пишет"
      ],
      "related": [
        "os.readv",
        "os.pwritev",
        "os.write"
      ],
      "related_errors": [
        "OSError"
      ]
    },
    {
      "id": ".iterdir",
      "title": ".iterdir()",
      "kind": "function",
      "summary": {
        "ru": "Итерирует по всем файлам и поддиректориям непосредственно в данной директории (не рекурсивно).",
        "en": "Iterates over every file and subdirectory directly inside the given directory (not recursively)."
      },
      "body": {
        "ru": "Возвращает одноразовый итератор, а не список, и порядок задаёт файловая система — для предсказуемого вывода оборачивайте в sorted(). Каждый элемент — полный Path вместе с родительскими каталогами, а не голое имя (имя берётся из .name). Вглубь iterdir() не спускается: для рекурсивного обхода нужен rglob('*'), а отбор по маске удобнее сделать через glob('*.txt').",
        "en": "You get a one-shot iterator, not a list, and the order comes from the filesystem — wrap it in sorted() when the output has to be stable. Each item is a full Path including the parent directories, not a bare filename; .name gives you the name. It never descends: use rglob('*') for a recursive walk, or glob('*.txt') when you only want a pattern."
      },
      "syntax": "for item in p.iterdir(): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.iterdir",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "обход",
      "color_group": "module",
      "aliases": [
        "список файлов в папке",
        "перебрать файлы в директории",
        "содержимое папки"
      ],
      "keywords": [
        "iterdir"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "p = Path('/tmp')",
        "for item in p.iterdir():",
        "print(item.name, item.is_dir())",
        "# Фильтрация:",
        "files = [x for x in p.iterdir() if x.is_file()]",
        "dirs = [x for x in p.iterdir() if x.is_dir()]",
        "len(list(p.iterdir())) >= 0 # → True"
      ],
      "related": [
        "path.glob",
        "path.rglob",
        "os.listdir"
      ],
      "related_errors": [
        "FileNotFoundError",
        "NotADirectoryError"
      ]
    },
    {
      "id": ".stem-.suffix-.suffixes-.name-.parent-.p",
      "title": ".stem / .suffix / .suffixes / .name / .parent / .parts",
      "kind": "term",
      "summary": {
        "ru": "Атрибуты объекта Path для доступа к компонентам: имя файла, расширение, родительская директория и т.д.",
        "en": "Attributes of a Path object that give access to its components: the file name, the extension, the parent directory and so on."
      },
      "body": {
        "ru": "Эти атрибуты только разбирают строку пути и на диск не ходят — они работают и для несуществующего файла. Главная ловушка: .suffix отдаёт лишь последнее расширение вместе с точкой, поэтому у 'file.tar.gz' .stem остаётся 'file.tar', а весь набор даёт .suffixes. У скрытых файлов вроде '.bashrc' расширения нет вообще: .suffix пустой, а ведущая точка считается частью имени.",
        "en": "These attributes are pure string arithmetic: nothing touches the disk, so they work for paths that do not exist. The usual trap is .suffix returning only the last extension, dot included, so 'file.tar.gz' has .stem 'file.tar' and you need .suffixes for the full list. A dotfile such as '.bashrc' has no suffix at all — the leading dot counts as part of the name."
      },
      "syntax": "p.name, p.stem, p.suffix, p.suffixes, p.parent, p.parts",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "части пути",
      "color_group": "module",
      "aliases": [
        "расширение файла",
        "имя файла без расширения",
        "родительская папка"
      ],
      "keywords": [
        "stem",
        "suffix",
        "suffixes",
        "name",
        "parent",
        "parts"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "p = Path('/home/user/data/file.tar.gz')",
        "p.name # → 'file.tar.gz'",
        "p.stem # → 'file.tar'",
        "p.suffix # → '.gz'",
        "p.suffixes # → ['.tar', '.gz']",
        "p.parent # → PosixPath('/home/user/data')",
        "p.parts # → ('/', 'home', 'user', 'data', 'file.tar.gz')"
      ],
      "related": [
        "os.path.splitext",
        "os.path.basename",
        "оператор",
        "path"
      ],
      "related_errors": []
    },
    {
      "id": "path",
      "title": "Path()",
      "kind": "function",
      "summary": {
        "ru": "Создаёт объект пути из строки или компонентов. На Windows — WindowsPath, на Unix — PosixPath.",
        "en": "Creates a path object from a string or from components. On Windows it is a WindowsPath, on Unix a PosixPath."
      },
      "body": {
        "ru": "Конструктор ничего не читает с диска: объект спокойно создаётся и для несуществующего пути, существование проверяют отдельно через exists() или is_file(). При склейке компонентов абсолютный кусок отбрасывает всё, что было слева, — типичная ловушка, когда второй компонент приходит из пользовательского ввода. Собирайте пути оператором / вместо конкатенации строк: разделитель подставится под текущую ОС, а на Windows прямые слэши во входной строке всё равно будут поняты.",
        "en": "The constructor never touches the filesystem — a Path for a nonexistent location is created just fine, and existence is a separate question for exists() or is_file(). When components are joined, an absolute component throws away everything to its left, which bites when that component comes from user input. Build paths with the / operator rather than string concatenation: the separator follows the host OS, and on Windows forward slashes in the input are still understood."
      },
      "syntax": "from pathlib import Path\np = Path('/some/path')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "создание",
      "color_group": "module",
      "aliases": [
        "объект пути",
        "работа с путями",
        "путь к файлу"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "p = Path('/home/user/file.txt')",
        "str(p) # → '/home/user/file.txt'",
        "Path('a', 'b', 'c') # → PosixPath('a/b/c')",
        "Path('/home') / 'user' / 'docs' # → PosixPath('/home/user/docs')",
        "Path('.') # → PosixPath('.')"
      ],
      "related": [
        "оператор",
        "pathlib.PurePath",
        "path.cwd",
        ".stem-.suffix-.suffixes-.name-.parent-.p"
      ],
      "related_errors": []
    },
    {
      "id": "path.cwd",
      "title": "Path.cwd()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает текущий рабочий каталог как Path — pathlib-аналог `os.getcwd()`, который отдаёт строку.",
        "en": "Return the current working directory as a Path — the pathlib counterpart of `os.getcwd()`, which returns a string."
      },
      "body": {
        "ru": "Текущий каталог — это место, откуда запустили программу, а не то, где лежит сам .py-файл; запустите скрипт из другой директории, и все относительные пути поедут. Для файлов, лежащих рядом со скриптом, отталкивайтесь от __file__, а не от Path.cwd(). Значение не заморожено: os.chdir меняет его на весь процесс, и относительные пути начинают разрешаться уже от нового места.",
        "en": "The current directory is wherever the program was launched from, not where the .py file lives — run the same script from another folder and every relative path shifts with it. For data sitting next to the script, derive the path from __file__ instead of Path.cwd(). The value is not fixed either: os.chdir changes it process-wide, and relative paths start resolving against the new location."
      },
      "syntax": "Path.cwd()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.cwd",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "создание",
      "color_group": "module",
      "aliases": [
        "текущая рабочая папка",
        "текущий каталог",
        "рабочая директория"
      ],
      "keywords": [
        "cwd",
        "Path.cwd"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "Path.cwd() # → PosixPath('/current/dir')",
        "Path.cwd().name # → имя текущего каталога",
        "Path.cwd().is_absolute()  # → True",
        "len(list(Path.cwd().glob('*.py')))  # → число .py-файлов в текущем каталоге",
        "Path('data.txt').absolute() == Path.cwd() / 'data.txt'  # → True"
      ],
      "related": [
        "path.home"
      ],
      "related_errors": []
    },
    {
      "id": "path.exists",
      "title": ".exists()",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, существует ли путь (файл, каталог или что-то ещё). Для несуществующего пути возвращает False, не поднимая исключение.",
        "en": "Check whether the path exists (file, directory or anything else); returns False instead of raising for a missing path."
      },
      "body": {
        "ru": "Ответ устаревает в момент получения: между проверкой и открытием файл может исчезнуть или появиться, поэтому в рабочем коде обычно проще открыть файл и поймать FileNotFoundError, чем спрашивать заранее. Симлинки метод разыменовывает — битая ссылка, ведущая в никуда, даёт False, хотя сама ссылка на диске есть; отличить помогает is_symlink() или exists(follow_symlinks=False) начиная с Python 3.12.",
        "en": "The answer is stale the moment you get it: the file can appear or disappear between the check and the open, so production code usually just opens the file and catches FileNotFoundError instead of asking first. The method follows symlinks — a broken link pointing nowhere reports False even though the link itself is on disk; use is_symlink(), or exists(follow_symlinks=False) since Python 3.12, to tell them apart."
      },
      "syntax": "p.exists()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.exists",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "проверки",
      "color_group": "module",
      "aliases": [
        "проверить существование файла",
        "существует ли путь",
        "есть ли такой файл"
      ],
      "keywords": [
        "exists",
        "Path.exists"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "Path('/tmp').exists() # → True",
        "Path('/nonexistent').exists() # → False",
        "Path.cwd().exists()  # → True (текущий каталог существует всегда)",
        "Path('~/notes.txt').exists()  # → False: тильда не раскрывается, нужен .expanduser()",
        "Path.cwd().exists(), Path.cwd().is_file()  # → (True, False)"
      ],
      "related": [
        "path.is_file",
        "path.is_dir"
      ],
      "related_errors": []
    },
    {
      "id": "path.glob",
      "title": ".glob()",
      "kind": "function",
      "summary": {
        "ru": "Ищет файлы по шаблону внутри каталога и возвращает ленивый генератор путей. Шаблон `**/` включает рекурсивный обход.",
        "en": "Match files against a pattern inside the directory, yielding paths lazily; the `**/` pattern walks recursively."
      },
      "body": {
        "ru": "Возвращает генератор, а не список: len() к нему не применить и второй раз проитерировать не выйдет — если результат нужен дважды, оборачивайте в list(). Порядок выдачи задаёт файловая система, он не отсортирован, так что для стабильного вывода берите sorted(). Шаблон сопоставляется с именем на каждом уровне отдельно, а '**' по большому дереву может обходиться очень дорого по времени.",
        "en": "You get a generator, not a list: len() does not work on it and you cannot iterate it twice — wrap it in list() if you need the results more than once. The order comes from the filesystem and is not sorted, so use sorted() when output must be reproducible. Each pattern component is matched against one path segment, and a '**' pattern over a large tree can take a very long time."
      },
      "syntax": "p.glob('*.py')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "поиск",
      "color_group": "module",
      "aliases": [
        "поиск файлов по маске",
        "найти файлы по шаблону",
        "найти все файлы с расширением"
      ],
      "keywords": [
        "glob",
        "Path.glob"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "p = Path('/tmp')",
        "list(p.glob('*.txt')) # → все .txt в /tmp",
        "list(p.glob('*/*.py')) # → .py на уровень глубже",
        "list(Path('.').glob('**/*.py')) # → все .py рекурсивно"
      ],
      "related": [
        "path.rglob"
      ],
      "related_errors": []
    },
    {
      "id": "path.home",
      "title": "Path.home()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает домашний каталог пользователя как Path. Классовый метод — вызывается на самом Path, не на экземпляре.",
        "en": "Return the user's home directory as a Path; a classmethod called on Path itself rather than on an instance."
      },
      "body": {
        "ru": "Домашний каталог вычисляется по окружению (HOME на Unix, USERPROFILE на Windows), поэтому в контейнере, в CI или под сервисным пользователем он легко окажется не тем, что вы видите в своей консоли; если определить его не удалось, поднимется RuntimeError. И учтите: сам pathlib тильду не раскрывает — путь с ~ внутри строки останется буквальным именем каталога, для раскрытия есть отдельный метод expanduser().",
        "en": "The home directory comes from the environment (HOME on Unix, USERPROFILE on Windows), so inside a container, in CI or under a service account it can easily differ from what you see in your own shell; when it cannot be determined at all, RuntimeError is raised. Note also that pathlib never expands a tilde on its own — a ~ written inside a path string stays a literal directory name, and expanduser() is the method that resolves it."
      },
      "syntax": "Path.home()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.home",
      "version": "3.5",
      "section": "Модуль pathlib",
      "subcat": "создание",
      "color_group": "module",
      "aliases": [
        "домашняя папка",
        "домашний каталог пользователя"
      ],
      "keywords": [
        "home",
        "Path.home"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "Path.home() # → PosixPath('/home/username')",
        "Path.home() / 'Downloads' # → PosixPath('/home/username/Downloads')",
        "Path.home().is_absolute()  # → True",
        "Path.home().is_dir()  # → True",
        "Path('~/notes.txt').expanduser() == Path.home() / 'notes.txt'  # → True"
      ],
      "related": [
        "path.cwd"
      ],
      "related_errors": []
    },
    {
      "id": "path.is_dir",
      "title": ".is_dir()",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что путь существует и указывает на каталог. Для файла и для несуществующего пути — False.",
        "en": "Check that the path exists and points to a directory; False for a file or a missing path."
      },
      "body": {
        "ru": "is_dir() не отличает «пути нет вовсе» от «путь есть, но это файл» — в обоих случаях просто False; если разница важна, спрашивайте exists() отдельно. Проверка идёт по назначению симлинка: ссылка на каталог считается каталогом. И помните про гонку между проверкой и действием — для реальной работы с каталогом надёжнее выполнить операцию и поймать OSError, чем полагаться на предварительный if.",
        "en": "A False answer merges two different situations — the path is missing, or it exists but is a file — so ask exists() separately when you need to tell them apart. Symlinks are followed: a link pointing at a directory counts as a directory. Also beware the check-then-act race; for real work it is safer to attempt the operation and handle OSError than to guard it with an if."
      },
      "syntax": "p.is_dir()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_dir",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "проверки",
      "color_group": "module",
      "aliases": [
        "проверить что это папка",
        "это каталог или файл"
      ],
      "keywords": [
        "is_dir",
        "Path.is_dir"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "Path('/tmp').is_dir() # → True",
        "Path('/etc/hosts').is_dir() # → False (это файл)",
        "Path.cwd().is_dir()  # → True",
        "Path('no_such_dir_12345').is_dir()  # → False: несуществующий путь тоже даёт False, без исключения",
        "[p.name for p in Path.cwd().iterdir() if p.is_dir()]  # → список имён подкаталогов текущего каталога"
      ],
      "related": [
        "path.exists",
        "path.is_file"
      ],
      "related_errors": []
    },
    {
      "id": "path.is_file",
      "title": ".is_file()",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что путь существует и указывает на обычный файл. Для каталога и для несуществующего пути — False.",
        "en": "Check that the path exists and points to a regular file; False for a directory or a missing path."
      },
      "body": {
        "ru": "«Обычный файл» здесь означает именно обычный: сокеты, именованные каналы и файлы устройств существуют, но is_file() для них вернёт False. Симлинк прослеживается — ссылка на файл считается файлом. Не стройте логику на отрицании: not p.is_file() ещё не значит «каталог», это может быть и просто несуществующий путь.",
        "en": "\"Regular file\" is meant literally: sockets, FIFOs and device nodes do exist, yet is_file() reports False for them. Symlinks are resolved, so a link to a file counts as a file. Don't branch on the negation — not p.is_file() does not imply a directory, it may simply be a path that isn't there."
      },
      "syntax": "p.is_file()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_file",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "проверки",
      "color_group": "module",
      "aliases": [
        "проверить что это файл",
        "это обычный файл"
      ],
      "keywords": [
        "is_file",
        "Path.is_file"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "import sys",
        "Path('/etc/hosts').is_file() # → True",
        "Path('/tmp').is_file() # → False (это каталог)",
        "print(Path(sys.executable).is_file())   # → True (сам интерпретатор — обычный файл)",
        "print(Path(sys.executable).parent.is_file())   # → False (каталог, хотя exists() → True)",
        "print(Path('no_such_file_42.txt').is_file())   # → False (несуществующий путь, а не исключение)"
      ],
      "related": [
        "path.exists",
        "path.is_dir"
      ],
      "related_errors": []
    },
    {
      "id": "path.mkdir",
      "title": ".mkdir()",
      "kind": "function",
      "summary": {
        "ru": "Создаёт каталог. `parents=True` доводит промежуточные каталоги, `exist_ok=True` не считает ошибкой уже существующий.",
        "en": "Create a directory; `parents=True` also creates missing parents and `exist_ok=True` tolerates an existing one."
      },
      "body": {
        "ru": "Без parents=True отсутствующий родитель даёт FileNotFoundError, а не FileExistsError — ошибки разные, и при отладке их легко перепутать. exist_ok=True прощает только уже существующий каталог; если по этому пути лежит обычный файл, FileExistsError всё равно прилетит. Проверять exists() перед вызовом не нужно и даже хуже: между проверкой и созданием каталог может появиться, а exist_ok=True закрывает эту гонку.",
        "en": "Without parents=True a missing parent raises FileNotFoundError, not FileExistsError — two different errors that are easy to confuse while debugging. exist_ok=True only forgives an existing directory; if a regular file sits at that path you still get FileExistsError. Don't test exists() first — exist_ok=True is shorter and race-free, since the directory may appear between the check and the call."
      },
      "syntax": "p.mkdir(parents=True, exist_ok=True)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "операции",
      "color_group": "module",
      "aliases": [
        "создать папку",
        "создать каталог",
        "создать вложенные папки"
      ],
      "keywords": [
        "mkdir",
        "Path.mkdir"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "Path('new_dir').mkdir(exist_ok=True)",
        "print(Path('new_dir').is_dir())   # → True",
        "Path('a/b/c').mkdir(parents=True, exist_ok=True)",
        "print(Path('a/b/c').is_dir())   # → True (parents=True создаёт всю цепочку)",
        "try:",
        "    Path('new_dir').mkdir()",
        "except FileExistsError as e:",
        "    print(type(e).__name__)   # → FileExistsError (без exist_ok=True)",
        "try:",
        "    Path('x/y/z').mkdir()",
        "except FileNotFoundError as e:",
        "    print(type(e).__name__)   # → FileNotFoundError (без parents=True)"
      ],
      "related": [
        "path.unlink",
        "path.rename",
        "path.replace"
      ],
      "related_errors": [
        "FileExistsError",
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "path.read_bytes",
      "title": ".read_bytes()",
      "kind": "function",
      "summary": {
        "ru": "Читает файл целиком в объект bytes — бинарный аналог `.read_text()`, без декодирования и без параметра encoding.",
        "en": "Read the whole file into a bytes object — the binary counterpart of `.read_text()`, with no decoding and no encoding parameter."
      },
      "body": {
        "ru": "Файл читается целиком в память одним куском, поэтому для больших файлов берите open(..., 'rb') и читайте порциями. В отличие от .read_text(), здесь ничего не декодируется и переводы строк не нормализуются: CRLF остаётся CRLF — именно поэтому хеши и бинарные форматы считают через read_bytes.",
        "en": "The entire file lands in memory at once, so switch to open(..., 'rb') with chunked reads when the file is large. Unlike .read_text(), nothing is decoded and newlines are left untouched — CRLF stays CRLF — which is exactly why checksums and binary formats go through read_bytes."
      },
      "syntax": "p.read_bytes()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.read_bytes",
      "version": "3.5",
      "section": "Модуль pathlib",
      "subcat": "чтение/запись",
      "color_group": "module",
      "aliases": [
        "прочитать двоичный файл",
        "прочитать файл в байты"
      ],
      "keywords": [
        "read_bytes",
        "Path.read_bytes"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "p = Path('/tmp/test_pathlib.bin')",
        "p.write_bytes(b'\\x00\\x01')",
        "p.read_bytes() # → b'\\x00\\x01'"
      ],
      "related": [
        "path.read_text",
        "path.write_text",
        "path.write_bytes"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError",
        "IsADirectoryError"
      ]
    },
    {
      "id": "path.read_text",
      "title": ".read_text()",
      "kind": "function",
      "summary": {
        "ru": "Читает файл целиком в строку и сам закрывает его — open() не нужен. Кодировку стоит задавать явно: `encoding='utf-8'`.",
        "en": "Read the whole file into a string and close it — no open() needed; pass `encoding='utf-8'` explicitly."
      },
      "body": {
        "ru": "Без явного encoding кодировка берётся из локали ОС — на Windows это обычно cp1251, и UTF-8 файл прочитается кракозябрами или упадёт с UnicodeDecodeError. Файл читается целиком в память: для многогигабайтного лога это не подходит, там открывают файл и идут по строкам в цикле.",
        "en": "With no encoding argument Python falls back to the OS locale encoding — on Windows that is typically cp1251 or cp1252, so a UTF-8 file comes back as mojibake or blows up with UnicodeDecodeError. The entire file is pulled into memory at once, so for multi-gigabyte logs open the file and iterate over lines instead."
      },
      "syntax": "p.read_text(encoding='utf-8')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.read_text",
      "version": "3.5",
      "section": "Модуль pathlib",
      "subcat": "чтение/запись",
      "color_group": "module",
      "aliases": [
        "прочитать файл целиком",
        "прочитать текст из файла",
        "прочитать файл одной строкой"
      ],
      "keywords": [
        "read_text",
        "Path.read_text"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "p = Path('/tmp/test_pathlib.txt')",
        "p.write_text('hello world', encoding='utf-8')",
        "p.read_text() # → 'hello world'",
        "len(p.read_text().splitlines()) # → 1"
      ],
      "related": [
        "path.write_text",
        "path.read_bytes",
        "path.write_bytes"
      ],
      "related_errors": [
        "FileNotFoundError",
        "UnicodeDecodeError",
        "PermissionError"
      ]
    },
    {
      "id": "path.rename",
      "title": ".rename()",
      "kind": "function",
      "summary": {
        "ru": "Переименовывает (перемещает) путь и возвращает новый Path. Поведение при уже существующей цели зависит от ОС — на Windows будет ошибка.",
        "en": "Rename (move) the path and return the new Path; behaviour when the target exists is platform-dependent and raises on Windows."
      },
      "body": {
        "ru": "Относительный target отсчитывается от текущей рабочей директории, а не от каталога самого p — p.rename('backup.txt') положит файл туда, откуда запущен скрипт. Затирание существующей цели зависит от ОС: на POSIX перезапишет молча, на Windows кинет FileExistsError, поэтому за одинаковым поведением идите в replace(); через границу файловых систем rename() вообще не работает (OSError) — там нужен shutil.move(). Сам объект p после вызова не меняется, работать дальше надо с возвращённым Path.",
        "en": "A relative target is resolved against the current working directory, not against p's own directory, so p.rename('backup.txt') lands the file wherever the script was launched from. Overwriting an existing target is platform-dependent — silent on POSIX, FileExistsError on Windows — so use replace() when you want one behaviour everywhere, and shutil.move() across filesystem boundaries, where rename() fails with OSError. p itself is unchanged; keep working with the returned Path."
      },
      "syntax": "p.rename(target)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.rename",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "операции",
      "color_group": "module",
      "aliases": [
        "переименовать файл",
        "переместить файл",
        "перенести файл в другую папку"
      ],
      "keywords": [
        "rename",
        "Path.rename"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "f = Path('old_path.txt')",
        "f.write_text('data')",
        "new = f.rename('new_path.txt')",
        "new.name # → 'new_path.txt'"
      ],
      "related": [
        "path.mkdir",
        "path.unlink",
        "path.replace"
      ],
      "related_errors": [
        "FileNotFoundError",
        "FileExistsError",
        "PermissionError"
      ]
    },
    {
      "id": "path.replace",
      "title": ".replace()",
      "kind": "function",
      "summary": {
        "ru": "Переименовывает путь, безусловно перезаписывая цель, если она существует. В отличие от `.rename()` ведёт себя одинаково на всех платформах.",
        "en": "Rename the path, unconditionally replacing the target if it exists — unlike `.rename()`, behaves the same on every platform."
      },
      "body": {
        "ru": "Перезапись безусловная: если по целевому пути уже лежал файл, он исчезнет молча, без вопросов и без корзины. Через границу файловых систем (другой диск, другая точка монтирования) вызов падает с OSError — туда переносят через shutil.move(). Зато внутри одной файловой системы замена делается одним шагом, поэтому приём «записать во временный файл рядом и заменить им боевой» — стандартный способ не оставить после сбоя обрезанный файл.",
        "en": "The overwrite is unconditional: whatever sat at the target path is gone silently, no prompt and no recycle bin. Across filesystem boundaries (a different drive or mount point) the call raises OSError — use shutil.move() for that. Within one filesystem the swap happens in a single step, which is why \"write a temp file next to it, then replace the real one\" is the standard way to avoid leaving a half-written file behind after a crash."
      },
      "syntax": "p.replace(target)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.replace",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "операции",
      "color_group": "module",
      "aliases": [
        "переместить с заменой",
        "переименовать с перезаписью",
        "заменить существующий файл"
      ],
      "keywords": [
        "replace",
        "Path.replace"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "src = Path('src.txt'); src.write_text('new')",
        "dst = Path('dst.txt'); dst.write_text('old')",
        "src.replace(dst) # → dst перезаписан",
        "dst.read_text() # → 'new'"
      ],
      "related": [
        "path.mkdir",
        "path.unlink",
        "path.rename"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "path.rglob",
      "title": ".rglob()",
      "kind": "function",
      "summary": {
        "ru": "Рекурсивно ищет файлы по шаблону во всех поддиректориях — то же, что `.glob()` с ведущим `**/`.",
        "en": "Match files against a pattern recursively in all subdirectories — equivalent to `.glob()` with a leading `**/`."
      },
      "body": {
        "ru": "Выдаёт всё подряд, включая каталоги, а не только файлы — если нужны именно файлы, фильтруйте по .is_file(). Обход идёт по всему дереву целиком, вместе с .git, .venv и node_modules, поэтому на большом каталоге он заметно медленный; результат при этом ленивый, работа начинается только когда вы начали итерацию.",
        "en": "It yields everything it matches, directories included, not just files — filter with .is_file() when you want files only. The walk covers the entire tree, .git, .venv and node_modules along with it, so on a big directory it is noticeably slow; the result is lazy, so nothing happens until you start iterating."
      },
      "syntax": "p.rglob('*.txt')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.rglob",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "поиск",
      "color_group": "module",
      "aliases": [
        "рекурсивный поиск файлов",
        "обойти все подпапки",
        "найти файлы во вложенных папках"
      ],
      "keywords": [
        "rglob",
        "Path.rglob"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "p = Path('/tmp')",
        "list(p.rglob('*.cfg')) # → все .cfg рекурсивно",
        "list(p.rglob('*')) # → всё содержимое дерева"
      ],
      "related": [
        "path.glob"
      ],
      "related_errors": []
    },
    {
      "id": "path.stat",
      "title": "Path.stat()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает структуру os.stat_result с метаданными файла: размер, время изменения, права доступа.",
        "en": "Returns an os.stat_result structure with the file's metadata: its size, modification time and permissions."
      },
      "body": {
        "ru": "stat() каждый раз лезет в файловую систему и кидает FileNotFoundError, если пути нет, — сохраняйте результат в переменную, а не дёргайте p.stat() отдельно под каждое поле. st_size — байты, st_mtime — float-секунды от эпохи (в дату переводится через datetime.fromtimestamp), а биты прав в st_mode на Windows почти ничего не значат. Для символической ссылки stat() показывает метаданные цели; чтобы увидеть саму ссылку, нужен lstat().",
        "en": "stat() hits the filesystem on every call and raises FileNotFoundError when the path is gone, so store the result once instead of calling p.stat() again for each field. Sizes are in bytes and st_mtime is a float of seconds since the epoch — pass it to datetime.fromtimestamp() to get a date; the permission bits in st_mode mean little on Windows. On a symlink stat() reports the target — use lstat() to inspect the link itself."
      },
      "syntax": "p.stat()\np.stat().st_size\np.stat().st_mtime",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "метаданные",
      "color_group": "module",
      "aliases": [
        "размер файла",
        "дата изменения файла",
        "метаданные файла"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "f = Path('/tmp/stat_test.txt')",
        "f.write_text('hello')",
        "st = f.stat()",
        "st.st_size # → 5",
        "st.st_mtime # → float (unix timestamp)",
        "import datetime",
        "datetime.datetime.fromtimestamp(st.st_mtime) # → datetime объект",
        "f.unlink()"
      ],
      "related": [
        "os.stat",
        "os.stat_result",
        "os.path.getsize",
        "os.path.getmtime"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "path.unlink",
      "title": ".unlink()",
      "kind": "function",
      "summary": {
        "ru": "Удаляет файл или символическую ссылку. `missing_ok=True` подавляет FileNotFoundError. Каталоги удаляет `.rmdir()`.",
        "en": "Delete a file or symlink; `missing_ok=True` suppresses FileNotFoundError. Use `.rmdir()` for directories."
      },
      "body": {
        "ru": "Связка if p.exists(): p.unlink() — гонка: между проверкой и удалением файл может исчезнуть, и вы всё равно получите FileNotFoundError. Надёжнее сразу p.unlink(missing_ok=True) (параметр появился в Python 3.8) или ловить исключение. Для символической ссылки удаляется сама ссылка, а файл, на который она указывала, остаётся на месте; удаление окончательное, мимо корзины.",
        "en": "The pattern if p.exists(): p.unlink() is a race — the file can vanish between the check and the delete, and you get FileNotFoundError anyway. Just call p.unlink(missing_ok=True) (added in Python 3.8) or catch the exception. On a symlink it removes the link itself and leaves the target file untouched; deletion is permanent and does not go through any trash."
      },
      "syntax": "p.unlink(missing_ok=True)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "операции",
      "color_group": "module",
      "aliases": [
        "удалить файл",
        "стереть файл",
        "удалить файл если он существует"
      ],
      "keywords": [
        "unlink",
        "Path.unlink"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "f = Path('/tmp/hello_path.txt')",
        "f.write_text('hi')",
        "f.unlink() # → файл удалён",
        "f.unlink(missing_ok=True) # → повторный вызов не падает"
      ],
      "related": [
        "path.mkdir",
        "path.rename",
        "path.replace"
      ],
      "related_errors": [
        "FileNotFoundError",
        "IsADirectoryError",
        "PermissionError"
      ]
    },
    {
      "id": "path.write_bytes",
      "title": ".write_bytes()",
      "kind": "function",
      "summary": {
        "ru": "Записывает bytes в файл, перезаписывая его целиком, и возвращает число записанных байт. Бинарный аналог `.write_text()`.",
        "en": "Write bytes to the file, overwriting it entirely, and return the number of bytes written — the binary counterpart of `.write_text()`."
      },
      "body": {
        "ru": "Существующий файл затирается молча, и режима «дописать» у метода нет — чтобы добавить в конец, нужен open(p, 'ab'). Родительские каталоги он тоже не создаёт: если директории нет, будет FileNotFoundError, поэтому сначала p.parent.mkdir(parents=True, exist_ok=True).",
        "en": "An existing file is truncated without any warning, and there is no append mode here — to add to the end use open(p, 'ab'). Missing parent directories are not created either: without them you get FileNotFoundError, so call p.parent.mkdir(parents=True, exist_ok=True) first."
      },
      "syntax": "p.write_bytes(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.write_bytes",
      "version": "3.5",
      "section": "Модуль pathlib",
      "subcat": "чтение/запись",
      "color_group": "module",
      "aliases": [
        "записать байты в файл",
        "записать двоичный файл"
      ],
      "keywords": [
        "write_bytes",
        "Path.write_bytes"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "p = Path('/tmp/test_pathlib.bin')",
        "p.write_bytes(b'\\xff\\xfe') # → 2",
        "p.read_bytes() # → b'\\xff\\xfe'"
      ],
      "related": [
        "path.read_text",
        "path.write_text",
        "path.read_bytes"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError",
        "TypeError"
      ]
    },
    {
      "id": "path.write_text",
      "title": ".write_text()",
      "kind": "function",
      "summary": {
        "ru": "Записывает строку в файл, перезаписывая его целиком, и возвращает число записанных символов. Файл закрывается сам.",
        "en": "Write a string to the file, overwriting it entirely, and return the number of characters written; the file is closed automatically."
      },
      "body": {
        "ru": "Возвращается число записанных символов, а не байт: на кириллице или эмодзи файл на диске окажется заметно больше этого числа. Запись идёт в текстовом режиме, поэтому перевод строки транслируется в системный — на Windows он ляжет в файл как CRLF; если нужны ровно те байты, что вы отдали, передайте newline (параметр появился в Python 3.10).",
        "en": "The return value counts characters, not bytes, so with Cyrillic or emoji the file on disk is bigger than the number you get back. Writing happens in text mode, so line endings are translated to the platform default and on Windows end up as CRLF — pass the newline argument (added in Python 3.10) when you need the exact bytes you handed in."
      },
      "syntax": "p.write_text(data, encoding='utf-8')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.Path.write_text",
      "version": "3.5",
      "section": "Модуль pathlib",
      "subcat": "чтение/запись",
      "color_group": "module",
      "aliases": [
        "записать текст в файл",
        "сохранить строку в файл",
        "перезаписать файл целиком"
      ],
      "keywords": [
        "write_text",
        "Path.write_text"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "p = Path('/tmp/test_pathlib.txt')",
        "p.write_text('hello world', encoding='utf-8') # → 11",
        "p.write_text('line1\\nline2')",
        "p.read_text().splitlines() # → ['line1', 'line2']"
      ],
      "related": [
        "path.read_text",
        "path.read_bytes",
        "path.write_bytes"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "pathlib.PosixPath",
      "title": "pathlib.PosixPath",
      "kind": "term",
      "summary": {
        "ru": "Конкретный путь с вводом-выводом на POSIX-системах (Linux/macOS); что возвращает Path() там. Подкласс PurePosixPath.",
        "en": "A concrete path with I/O on POSIX systems (Linux/macOS); what Path() returns there."
      },
      "body": {
        "ru": "Писать PosixPath в коде почти никогда не нужно: Path() сам подставляет нужный класс на каждой ОС, а на Windows создать PosixPath просто не выйдет — будет UnsupportedOperation (до 3.13 — NotImplementedError). Если надо разобрать POSIX-путь на чужой платформе, без обращения к диску, бери PurePosixPath: он работает везде. Ещё частая путаница при отладке — print(список_путей) печатает repr и показывает PosixPath('a/b'); саму строку пути даёт str(p).",
        "en": "You almost never need to name PosixPath yourself: Path() picks the right class per OS, and on Windows constructing PosixPath fails outright with UnsupportedOperation (NotImplementedError before 3.13). To parse a POSIX-style path on any platform without touching the disk, use PurePosixPath instead. A common debugging surprise: printing a list of paths shows their repr, PosixPath('a/b'), while the bare path string comes from str(p)."
      },
      "syntax": "pathlib.PosixPath(*segments)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.PosixPath",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "конкретные пути",
      "color_group": "op",
      "aliases": [
        "путь в юникс-системах",
        "путь с прямыми слешами",
        "путь на линуксе"
      ],
      "keywords": [],
      "tags": [
        "pathlib"
      ],
      "examples": [
        "import pathlib",
        "print(issubclass(pathlib.PosixPath, pathlib.PurePosixPath))   # → True",
        "print(issubclass(pathlib.PosixPath, pathlib.Path))   # → True",
        "print(issubclass(pathlib.PosixPath, pathlib.WindowsPath))   # → False",
        "print(hasattr(pathlib.PosixPath, 'read_text'), hasattr(pathlib.PurePosixPath, 'read_text'))   # → True False",
        "print(type(pathlib.Path()).__name__)   # → PosixPath на Linux/macOS, WindowsPath на Windows"
      ],
      "related": [
        "pathlib.WindowsPath",
        "path",
        "pathlib.PurePosixPath"
      ],
      "related_errors": []
    },
    {
      "id": "pathlib.PurePath",
      "title": "pathlib.PurePath",
      "kind": "term",
      "summary": {
        "ru": "Путь без обращения к ФС: только вычисления над строкой пути (name/suffix/parts/parent, оператор /).",
        "en": "A filesystem path for pure computation, with no I/O (name/suffix/parts/parent)."
      },
      "body": {
        "ru": "PurePath() — фабрика: на Windows вы получите PureWindowsPath, на остальных ОС — PurePosixPath, поэтому для предсказуемого поведения в тестах называйте нужный класс явно. У чистого пути нет ни .exists(), ни .open(), ни .iterdir() — всё, что трогает диск, живёт только в Path. И пути разных флейворов никогда не равны между собой, даже если строки совпадают.",
        "en": "PurePath() is a factory: it gives you PureWindowsPath on Windows and PurePosixPath elsewhere, so name the flavour explicitly when you want deterministic behaviour in tests. A pure path has no .exists(), .open() or .iterdir() — everything that touches the disk lives on Path only. Paths of different flavours never compare equal, even when their strings are identical."
      },
      "syntax": "pathlib.PurePath(*segments)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.PurePath",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "чистые пути",
      "color_group": "op",
      "aliases": [
        "путь без обращения к файловой системе",
        "разбор строки пути"
      ],
      "keywords": [],
      "tags": [
        "pathlib"
      ],
      "examples": [
        "import pathlib",
        "p = pathlib.PurePath('a/b/c.txt')",
        "print(p.name, p.suffix)   # → c.txt .txt",
        "print(p.parts)            # → ('a', 'b', 'c.txt')"
      ],
      "related": [
        "pathlib.PurePosixPath",
        "pathlib.PureWindowsPath",
        "path"
      ],
      "related_errors": []
    },
    {
      "id": "pathlib.PurePosixPath",
      "title": "pathlib.PurePosixPath",
      "kind": "term",
      "summary": {
        "ru": "Чистый путь с семантикой POSIX (разделитель /) — независимо от ОС, где выполняется код.",
        "en": "A pure path with POSIX semantics (/ separator), regardless of the host OS."
      },
      "body": {
        "ru": "Пригодится, когда на Windows нужно разобрать чужой POSIX-путь — из конфига, архива, URL — не превращая его в путь с обратными слэшами. Сравнение здесь чувствительно к регистру: 'A.txt' и 'a.txt' — разные пути, в отличие от PureWindowsPath. Букв дисков этот флейвор не знает: у 'C:/x' атрибут drive пустой, а 'C:' становится обычным первым компонентом.",
        "en": "Reach for it when you need to parse a POSIX path that came from somewhere else — a config file, a tar archive, a URL — without Windows turning it into backslashes. Comparison is case-sensitive here, so 'A.txt' and 'a.txt' are distinct paths, unlike with PureWindowsPath. It knows nothing about drive letters: for 'C:/x' the drive attribute is empty and 'C:' is just the first component."
      },
      "syntax": "pathlib.PurePosixPath(*segments)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.PurePosixPath",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "чистые пути",
      "color_group": "op",
      "aliases": [
        "путь с прямыми слэшами",
        "путь в стиле линукс"
      ],
      "keywords": [],
      "tags": [
        "pathlib"
      ],
      "examples": [
        "import pathlib",
        "print(pathlib.PurePosixPath('/a/b').parts)   # → ('/', 'a', 'b')",
        "p = pathlib.PurePosixPath('/home/user/report.txt')",
        "print(p.parent, p.name, p.suffix)   # → /home/user report.txt .txt",
        "print(pathlib.PurePosixPath('/a') / 'b' / 'c.txt')   # → /a/b/c.txt",
        "print(pathlib.PurePosixPath('a/b').is_absolute(), pathlib.PurePosixPath('/a/b').is_absolute())   # → False True",
        "print(hasattr(pathlib.PurePosixPath('/a'), 'exists'))   # → False (у чистого пути нет ввода-вывода)"
      ],
      "related": [
        "pathlib.PureWindowsPath",
        "pathlib.PurePath",
        "pathlib.PosixPath"
      ],
      "related_errors": []
    },
    {
      "id": "pathlib.PureWindowsPath",
      "title": "pathlib.PureWindowsPath",
      "kind": "term",
      "summary": {
        "ru": "Чистый путь с семантикой Windows (разделитель \\, буквы дисков) — независимо от текущей ОС.",
        "en": "A pure path with Windows semantics (\\ separator, drive letters), regardless of the host OS."
      },
      "body": {
        "ru": "Сравнение и хеширование здесь не учитывают регистр: PureWindowsPath('A/B') равен PureWindowsPath('a/b'), а вот с PurePosixPath той же строки — никогда. Обратите внимание на разницу 'C:/data' и 'C:data': первый привязан к корню диска, второй относителен текущему каталогу диска C, и .parts у них разные. Класс работает на любой ОС, но открыть через него файл нельзя — для этого нужен Path.",
        "en": "Comparison and hashing ignore case here, so PureWindowsPath('A/B') equals PureWindowsPath('a/b') — while a PurePosixPath built from the same text never does. Watch the difference between 'C:/data' and 'C:data': the first is anchored at the drive root, the second is relative to the current directory of drive C, and their .parts differ. It runs anywhere, but it cannot open anything — that is what Path is for."
      },
      "syntax": "pathlib.PureWindowsPath(*segments)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.PureWindowsPath",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "чистые пути",
      "color_group": "op",
      "aliases": [
        "путь с обратными слэшами",
        "путь в стиле виндовс",
        "буква диска в пути"
      ],
      "keywords": [],
      "tags": [
        "pathlib"
      ],
      "examples": [
        "import pathlib",
        "print(str(pathlib.PureWindowsPath('a', 'b')))   # → a\\b",
        "w = pathlib.PureWindowsPath('C:/Users/anna/doc.txt')",
        "print(w.drive, w.name, w.suffix)   # → C: doc.txt .txt",
        "print(w.as_posix())   # → C:/Users/anna/doc.txt",
        "print(pathlib.PureWindowsPath('A/B') == pathlib.PureWindowsPath('a/b'))   # → True (регистр не важен)",
        "print(pathlib.PureWindowsPath('C:/a').is_absolute(), pathlib.PureWindowsPath('/a').is_absolute())   # → True False"
      ],
      "related": [
        "pathlib.PurePosixPath",
        "pathlib.PurePath",
        "pathlib.WindowsPath",
        "os.path.splitdrive"
      ],
      "related_errors": []
    },
    {
      "id": "pathlib.UnsupportedOperation",
      "title": "pathlib.UnsupportedOperation",
      "kind": "exception",
      "summary": {
        "ru": "Подкласс NotImplementedError: бросается, когда операция pathlib недоступна на этой платформе — например symlink_to()/readlink() без os.symlink или PosixPath на Windows. Python 3.13+.",
        "en": "A subclass of NotImplementedError raised when a pathlib operation is unsupported on the current platform — e.g. symlink_to()/readlink() without os.symlink, or PosixPath on Windows. Python 3.13+."
      },
      "body": {
        "ru": "Класс появился только в Python 3.13, поэтому except pathlib.UnsupportedOperation на 3.12 и раньше упадёт с AttributeError ещё до того, как что-то пойдёт не так. Кросс-версионный код ловит NotImplementedError: UnsupportedOperation — его подкласс, а старые версии pathlib бросали именно NotImplementedError. Такая ошибка значит не баг в вашем коде, а что операции просто нет на этой платформе, — реагировать надо запасной веткой поведения, а не повтором вызова.",
        "en": "The class only exists since Python 3.13, so except pathlib.UnsupportedOperation blows up with AttributeError on 3.12 and older before anything else can go wrong. Version-portable code catches NotImplementedError instead: UnsupportedOperation is a subclass of it, and older pathlib raised NotImplementedError in these very spots. Seeing it means the operation is absent on this platform, not that your code is buggy, so handle it with a fallback branch rather than a retry."
      },
      "syntax": "pathlib.UnsupportedOperation",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.UnsupportedOperation",
      "version": "3.13",
      "section": "Модуль pathlib",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "операция не поддерживается платформой",
        "неподдерживаемая операция пути",
        "нет поддержки символических ссылок"
      ],
      "keywords": [
        "pathlib.UnsupportedOperation",
        "UnsupportedOperation"
      ],
      "tags": [
        "pathlib"
      ],
      "examples": [
        "import pathlib",
        "Unsupported = getattr(pathlib, 'UnsupportedOperation', NotImplementedError)",
        "print(issubclass(Unsupported, NotImplementedError))   # → True",
        "print(issubclass(Unsupported, RuntimeError))   # → True",
        "print(isinstance(Unsupported('symlink недоступен'), NotImplementedError))   # → True",
        "print(hasattr(pathlib.Path, 'symlink_to'))   # → True"
      ],
      "related": [
        "pathlib.PosixPath",
        "pathlib.WindowsPath",
        "notimplementederror",
        "os.symlink"
      ],
      "related_errors": []
    },
    {
      "id": "pathlib.WindowsPath",
      "title": "pathlib.WindowsPath",
      "kind": "term",
      "summary": {
        "ru": "Конкретный путь с вводом-выводом на Windows; что возвращает Path() там. Подкласс PureWindowsPath.",
        "en": "A concrete path with I/O on Windows; what Path() returns there."
      },
      "body": {
        "ru": "На Linux и macOS создать WindowsPath нельзя — будет UnsupportedOperation (до 3.13 — NotImplementedError); чтобы разбирать windows-пути на любой ОС, используй PureWindowsPath, он не ходит на диск. Сравнение и хеширование здесь регистронезависимы: PureWindowsPath('A.TXT') == PureWindowsPath('a.txt') даёт True, тогда как у POSIX-путей это два разных пути. Прямые слэши в аргументе принимаются, но наружу путь выходит с обратными.",
        "en": "WindowsPath cannot be instantiated on Linux or macOS — you get UnsupportedOperation (NotImplementedError before 3.13); to parse Windows-style paths anywhere, use PureWindowsPath, which never touches the disk. Comparison and hashing are case-insensitive here: PureWindowsPath('A.TXT') == PureWindowsPath('a.txt') is True, whereas POSIX paths treat those as two different files. Forward slashes are accepted on input, but the path prints back with backslashes."
      },
      "syntax": "pathlib.WindowsPath(*segments)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#pathlib.WindowsPath",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "конкретные пути",
      "color_group": "op",
      "aliases": [
        "путь в винде",
        "путь с обратными слешами",
        "путь с буквой диска"
      ],
      "keywords": [],
      "tags": [
        "pathlib"
      ],
      "examples": [
        "import pathlib",
        "print(issubclass(pathlib.WindowsPath, pathlib.PureWindowsPath))   # → True",
        "print(hasattr(pathlib.WindowsPath, 'exists'), hasattr(pathlib.PureWindowsPath, 'exists'))   # → True False",
        "print(pathlib.PureWindowsPath('C:/Users/ivan/note.txt').name)   # → note.txt",
        "print(pathlib.PureWindowsPath('C:/Users/ivan/note.txt').parent)   # → C:\\Users\\ivan",
        "print(type(pathlib.Path('.')).__name__)   # → WindowsPath на Windows, PosixPath на Linux/macOS"
      ],
      "related": [
        "pathlib.PosixPath",
        "path",
        "pathlib.PureWindowsPath"
      ],
      "related_errors": []
    },
    {
      "id": "оператор",
      "title": "/ оператор",
      "kind": "term",
      "summary": {
        "ru": "Оператор / для объектов Path объединяет компоненты пути, аналог os.path.join().",
        "en": "The / operator on Path objects joins the components of a path, like os.path.join()."
      },
      "body": {
        "ru": "Склейка чисто текстовая: диск не трогается, существование не проверяется, и '..' не схлопывается — Path('a/b') / '..' так и останется 'a/b/..', пока не позовёте resolve(). Абсолютный компонент справа обнуляет всё, что было слева: Path('/home/user') / '/etc' даёт просто '/etc' — ловушка, когда правая часть приходит из пользовательского ввода. Достаточно, чтобы Path был с одной стороны, вторым операндом может быть обычная строка.",
        "en": "Joining is pure string work — nothing is created or checked on disk, and '..' is not collapsed, so Path('a/b') / '..' stays 'a/b/..' until you call resolve(). An absolute right-hand component discards everything on the left: Path('/home/user') / '/etc' is just '/etc', which bites when the right side comes from user input. Only one side has to be a Path; the other may be a plain string."
      },
      "syntax": "Path('dir') / 'subdir' / 'file.txt'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/pathlib.html#operators",
      "version": "",
      "section": "Модуль pathlib",
      "subcat": "объединение",
      "color_group": "module",
      "aliases": [
        "объединение путей",
        "склеить путь из частей",
        "добавить имя файла к папке"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from pathlib import Path",
        "base = Path('/home/user')",
        "p = base / 'docs' / 'report.txt'",
        "str(p) # → '/home/user/docs/report.txt'",
        "Path('/etc') / 'nginx' / 'nginx.conf'",
        "# → PosixPath('/etc/nginx/nginx.conf')",
        "Path('src') / Path('main.py') # → PosixPath('src/main.py')"
      ],
      "related": [
        "os.path.join",
        "path",
        ".stem-.suffix-.suffixes-.name-.parent-.p"
      ],
      "related_errors": []
    },
    {
      "id": "random.betavariate",
      "title": "random.betavariate",
      "kind": "function",
      "summary": {
        "ru": "Случайное число из бета-распределения (параметры alpha>0, beta>0); результат всегда в диапазоне [0, 1].",
        "en": "A random number from the beta distribution (alpha>0, beta>0); always in [0, 1]."
      },
      "body": {
        "ru": "Бета-распределение обычно берут, когда нужно случайное значение самой вероятности: среднее равно alpha/(alpha+beta), и чем больше сумма параметров, тем теснее значения жмутся к этому среднему. Частный случай alpha = beta = 1 — это ровно равномерное распределение на [0, 1], а при обоих параметрах меньше единицы, наоборот, значения тянутся к краям диапазона. Нулевые или отрицательные параметры — ValueError.",
        "en": "Beta is the go-to when you need a random probability rather than a random number: the mean is alpha/(alpha+beta), and the larger the two parameters are, the tighter the values cluster around that mean. With alpha = beta = 1 it degenerates into the plain uniform distribution on [0, 1]; with both parameters below 1 the values pile up at the ends instead. Zero or negative parameters raise ValueError."
      },
      "syntax": "random.betavariate(alpha, beta)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.betavariate",
      "version": "",
      "section": "Модуль random",
      "subcat": "распределения",
      "color_group": "module",
      "aliases": [
        "бета-распределение",
        "случайное число из бета-распределения"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "print(0.0 <= random.betavariate(2.0, 3.0) <= 1.0)   # → True",
        "print(type(random.betavariate(2.0, 3.0)).__name__)   # → float",
        "print(round(sum(random.betavariate(2.0, 2.0) for _ in range(10000)) / 10000, 1))   # → 0.5",
        "print(sum(random.betavariate(8.0, 2.0) for _ in range(10000)) / 10000 > 0.5)   # → True",
        "print(random.betavariate(0.0, 3.0))   # → ValueError"
      ],
      "related": [
        "random.gammavariate",
        "random.uniform",
        "random.triangular"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "random.binomialvariate",
      "title": "random.binomialvariate",
      "kind": "function",
      "summary": {
        "ru": "Число успехов в n испытаниях Бернулли с вероятностью p — целое из биномиального распределения в диапазоне [0, n] (Python 3.12+).",
        "en": "The number of successes in n Bernoulli trials with probability p; an int in [0, n] (3.12+)."
      },
      "body": {
        "ru": "Это единственная функция-распределение в random, которая возвращает int, а не float. По смыслу она сворачивает в один вызов цикл sum(random.random() < p for _ in range(n)), но работает заметно быстрее и не зависит от n так сильно. Появилась только в 3.12 — на более старых версиях будет AttributeError, там сумму пишут руками; n < 0 или p вне [0, 1] дают ValueError.",
        "en": "This is the only distribution function in random that hands back an int rather than a float. It collapses the loop sum(random.random() < p for _ in range(n)) into a single call, and does it much faster without scaling badly with n. It landed in 3.12 only, so older interpreters raise AttributeError and you write the sum by hand; a negative n or a p outside [0, 1] raises ValueError."
      },
      "syntax": "random.binomialvariate(n=1, p=0.5)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.binomialvariate",
      "version": "3.12",
      "section": "Модуль random",
      "subcat": "распределения",
      "color_group": "module",
      "aliases": [
        "биномиальное распределение",
        "число успехов в испытаниях",
        "схема Бернулли"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "print(0 <= random.binomialvariate(10, 0.5) <= 10)   # → True",
        "print(random.binomialvariate() in (0, 1))   # → True",
        "print(random.binomialvariate(10, 0.0), random.binomialvariate(10, 1.0))   # → 0 10",
        "print(round(sum(random.binomialvariate(10, 0.5) for _ in range(10000)) / 10000))   # → 5",
        "print(random.binomialvariate(10, 1.5))   # → ValueError"
      ],
      "related": [
        "random.random",
        "random.choices",
        "random.gauss"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "random.choice",
      "title": "random.choice",
      "kind": "term",
      "summary": {
        "ru": "Возвращает случайный элемент из непустой последовательности (list, tuple, str). При пустой — IndexError.",
        "en": "Returns a random item of a non-empty sequence (list, tuple, str). An empty one raises IndexError."
      },
      "body": {
        "ru": "choice умеет работать только с тем, что поддерживает индексацию и len: множество, словарь или генератор дадут TypeError, их надо сначала обернуть в list (list(d) вернёт случайный ключ). Вызовы независимы, поэтому подряд легко получить один и тот же элемент — если нужны разные, берите sample, а не choice в цикле.",
        "en": "choice only handles objects that support indexing and len: a set, a dict or a generator raises TypeError, so wrap them in list first (list(d) gives you a random key). Calls are independent, so the same item can come up twice in a row — when you need distinct items, use sample instead of choice in a loop."
      },
      "syntax": "random.choice(seq)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.choice",
      "version": "",
      "section": "Модуль random",
      "subcat": "выборка",
      "color_group": "module",
      "aliases": [
        "случайный элемент списка",
        "выбрать случайно из списка",
        "случайный выбор"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import random",
        "random.seed(1)",
        "print(random.choice([1,2,3,4,5]))  # → случайный элемент",
        "print(random.choice('abcdef'))     # → случайный символ",
        "faces = ['♠','♥','♦','♣']",
        "print(random.choice(faces))  # → случайная масть",
        "import random",
        "result = [random.choice(['heads','tails']) for _ in range(5)]",
        "print(result)",
        "print(random.choice(range(100)))  # → 0..99"
      ],
      "related": [
        "random.choices",
        "random.sample",
        "random.shuffle"
      ],
      "related_errors": []
    },
    {
      "id": "random.choices",
      "title": "random.choices",
      "kind": "term",
      "summary": {
        "ru": "Возвращает список из k случайных элементов с возвратом. Поддерживает веса (weights или cum_weights).",
        "en": "Returns a list of k random items chosen with replacement. Weights are supported (weights or cum_weights)."
      },
      "body": {
        "ru": "Выборка идёт с возвратом: дубликаты в результате — норма, а k спокойно может превышать длину population; если элементы должны быть разными, нужен sample. Веса относительные и к единице не нормируются, но длина weights обязана совпадать с population, иначе ValueError. Кумулятивные суммы весов пересчитываются при каждом вызове, поэтому для множества выборок с одними и теми же весами дешевле один раз посчитать их самому и передать через cum_weights.",
        "en": "Sampling is done with replacement, so duplicates are expected and k may freely exceed the size of population; when the items must be distinct, use sample. Weights are relative and are not normalised to 1, but their count must match population or you get a ValueError. The cumulative weight table is rebuilt on every call, so for many draws with the same weights it is cheaper to compute it once yourself and pass cum_weights."
      },
      "syntax": "random.choices(population, weights=None, k=1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.choices",
      "version": "3.6",
      "section": "Модуль random",
      "subcat": "выборка",
      "color_group": "module",
      "aliases": [
        "случайная выборка с повторениями",
        "взвешенный случайный выбор",
        "несколько случайных элементов с возвратом"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import random",
        "random.seed(42)",
        "print(random.choices([1,2,3,4,5], k=3))  # → 3 элемента",
        "print(random.choices('abc', weights=[1,2,3], k=5))  # → с весами",
        "print(random.choices(['H','T'], k=10))  # → монета",
        "results = random.choices(range(1,7), k=1000)",
        "from collections import Counter",
        "print(Counter(results).most_common(3))  # → топ-3",
        "print(random.choices(['red','green'], weights=[1,9], k=5))"
      ],
      "related": [
        "random.sample",
        "random.choice",
        "random.shuffle"
      ],
      "related_errors": []
    },
    {
      "id": "random.expovariate",
      "title": "random.expovariate",
      "kind": "function",
      "summary": {
        "ru": "Случайное число из экспоненциального распределения с интенсивностью lambd; результат всегда неотрицателен.",
        "en": "A random number from the exponential distribution with rate lambd; always non-negative."
      },
      "body": {
        "ru": "lambd — это интенсивность, то есть единица, делённая на желаемое среднее: чтобы интервалы получались со средним 5, надо передать 0.2, а не 5. Подмена интенсивности средним — самая частая ошибка при моделировании очередей и времени между событиями. Нулевой lambd недопустим, отрицательный зеркалит выдачу в отрицательные числа, а вызов вообще без аргумента работает только начиная с Python 3.12.",
        "en": "lambd is a rate, not a mean: it equals one divided by the average you want, so an average gap of 5 needs 0.2, not 5. Passing the mean by mistake is the classic bug in queue and inter-arrival simulations. Zero is not allowed, a negative lambd mirrors the results into negative values, and calling it with no argument at all only works from Python 3.12 on."
      },
      "syntax": "random.expovariate(lambd=1.0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.expovariate",
      "version": "",
      "section": "Модуль random",
      "subcat": "распределения",
      "color_group": "module",
      "aliases": [
        "экспоненциальное распределение",
        "показательное распределение"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "print(random.expovariate(1.0) >= 0.0)   # → True",
        "print(type(random.expovariate()).__name__)   # → float",
        "print(round(sum(random.expovariate(2.0) for _ in range(10000)) / 10000, 1))   # → 0.5",
        "print(random.expovariate(1000.0) < 1.0)   # → True",
        "print(random.expovariate(0.0))   # → ZeroDivisionError"
      ],
      "related": [
        "random.gammavariate",
        "random.weibullvariate",
        "random.paretovariate"
      ],
      "related_errors": [
        "ZeroDivisionError"
      ]
    },
    {
      "id": "random.gammavariate",
      "title": "random.gammavariate",
      "kind": "function",
      "summary": {
        "ru": "Случайное число из гамма-распределения (параметры alpha>0, beta>0); результат всегда положителен.",
        "en": "A random number from the gamma distribution (alpha>0, beta>0); always positive."
      },
      "body": {
        "ru": "beta здесь — масштаб, поэтому среднее равно alpha*beta (отсюда 6 при alpha=2, beta=3). Часть учебников и библиотек называет beta обратную величину — интенсивность, так что формулу, взятую из чужого источника, нередко надо переводить в 1/beta. К математической гамма-функции (math.gamma) это отношения не имеет, а alpha и beta обязаны быть строго положительными, иначе ValueError.",
        "en": "Here beta is the scale, so the mean is alpha*beta — that is why alpha=2, beta=3 averages around 6. Many textbooks and libraries define beta the other way round, as a rate, so a formula copied from elsewhere often has to be converted to 1/beta. This has nothing to do with the mathematical gamma function (math.gamma), and both alpha and beta must be strictly positive or you get a ValueError."
      },
      "syntax": "random.gammavariate(alpha, beta)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.gammavariate",
      "version": "",
      "section": "Модуль random",
      "subcat": "распределения",
      "color_group": "module",
      "aliases": [
        "гамма-распределение",
        "случайное число из гамма-распределения"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "print(random.gammavariate(2.0, 3.0) > 0.0)   # → True",
        "print(type(random.gammavariate(2.0, 3.0)).__name__)   # → float",
        "print(round(sum(random.gammavariate(2.0, 3.0) for _ in range(10000)) / 10000))   # → 6",
        "print(random.gammavariate(1.0, 2.0) > 0.0)   # → True",
        "print(random.gammavariate(0.0, 3.0))   # → ValueError"
      ],
      "related": [
        "random.expovariate",
        "random.betavariate",
        "random.weibullvariate"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "random.gauss",
      "title": "random.gauss",
      "kind": "term",
      "summary": {
        "ru": "Возвращает случайное число из нормального (гауссова) распределения с параметрами mu (среднее) и sigma (ст. отклонение).",
        "en": "Returns a random number from the normal (Gaussian) distribution with the parameters mu (the mean) and sigma (the standard deviation)."
      },
      "body": {
        "ru": "Скорость достигается кешем: один вызов считает сразу пару значений и отдаёт второе при следующем обращении, поэтому два потока способны получить одно и то же число. В многопоточном коде берут normalvariate или отдельный экземпляр random.Random на поток. Значения ничем не ограничены — примерно 0.3% выходят за mu ± 3*sigma, так что для заведомо неотрицательных величин (рост, время) обрезка снизу исказит распределение сильнее, чем повторная генерация.",
        "en": "The speed comes from a cache: one call computes a pair of values and hands out the second one on the next call, so two threads can end up with the same number. In threaded code use normalvariate or give each thread its own random.Random instance. The output is unbounded — roughly 0.3% of draws fall outside mu ± 3*sigma — so for quantities that must stay positive, clipping at zero skews the distribution more than simply drawing again."
      },
      "syntax": "random.gauss(mu=0.0, sigma=1.0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.gauss",
      "version": "",
      "section": "Модуль random",
      "subcat": "распределения",
      "color_group": "module",
      "aliases": [
        "гауссово распределение",
        "нормальное распределение",
        "колокол Гаусса"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import random",
        "random.seed(42)",
        "print(random.gauss(0, 1))    # → стандартное нормальное",
        "print(random.gauss(100, 15)) # → IQ-подобное",
        "results = [random.gauss(0,1) for _ in range(1000)]",
        "print(round(sum(results)/len(results), 2))  # → ≈ 0",
        "print(round((sum(x**2 for x in results)/len(results))**0.5,2))  # → ≈ 1",
        "print(random.gauss(50, 10))  # → около 50"
      ],
      "related": [
        "random.normalvariate",
        "random.uniform",
        "statistics.NormalDist"
      ],
      "related_errors": []
    },
    {
      "id": "random.getrandbits",
      "title": "random.getrandbits",
      "kind": "function",
      "summary": {
        "ru": "Целое число из k случайных бит (0 ≤ результат < 2**k); опирается на воспроизводимое ядро генератора, поэтому под фиксированным seed стабильно между версиями Python.",
        "en": "An int of k random bits (0 ≤ result < 2**k); reproducible under a fixed seed."
      },
      "body": {
        "ru": "Под капотом Mersenne Twister: по достаточному числу выданных значений его внутреннее состояние восстанавливается, поэтому пароли, токены и ключи генерируют через secrets.randbits() или os.urandom(), а не здесь. Отрицательное k даёт ValueError, а k=0 законно и всегда возвращает 0.",
        "en": "The engine underneath is the Mersenne Twister: observe enough output and its internal state can be reconstructed, so passwords, tokens and keys belong to secrets.randbits() or os.urandom() instead. A negative k raises ValueError, while k=0 is legal and always yields 0."
      },
      "syntax": "random.getrandbits(k)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.getrandbits",
      "version": "",
      "section": "Модуль random",
      "subcat": "биты и байты",
      "color_group": "module",
      "aliases": [
        "случайные биты",
        "случайное число заданной длины в битах"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "random.seed(0)",
        "print(random.getrandbits(8))   # → 216",
        "print(0 <= random.getrandbits(4) < 16)   # → True",
        "print(len(f'{random.getrandbits(128):032x}'))   # → 32",
        "print(random.getrandbits(0))   # → 0",
        "print(random.getrandbits(-1))   # → ValueError"
      ],
      "related": [
        "random.randbytes",
        "random.randrange",
        "random.seed"
      ],
      "related_errors": []
    },
    {
      "id": "random.getstate",
      "title": "random.getstate",
      "kind": "function",
      "summary": {
        "ru": "Возвращает внутреннее состояние генератора (непрозрачный объект); позже его можно вернуть через setstate, чтобы повторить ту же последовательность.",
        "en": "Return the generator's internal state; restore it later with setstate to replay the sequence."
      },
      "body": {
        "ru": "Объект непрозрачный — это внутренний массив вихря Мерсенна: сохранить его (в том числе через pickle) и вернуть можно только целиком через setstate, разбирать или править руками нельзя, а состояние, записанное другой версией Python, может не загрузиться. Для обычной воспроизводимости достаточно seed(); getstate нужен, когда надо вернуться в середину уже сгенерированной последовательности, не проигрывая её с начала. И учтите: модульные функции random работают с одним общим генератором, так что восстановление состояния влияет на весь код в процессе.",
        "en": "The returned object is opaque — the raw Mersenne Twister state. Save it (pickle works) and hand it back whole to setstate; never pick it apart, and do not count on a state written by a different Python version loading. For ordinary reproducibility seed() is enough; getstate earns its keep when you need to jump back into the middle of a sequence without replaying it. Remember too that the module-level functions all share one generator, so restoring a state affects every other piece of code in the process."
      },
      "syntax": "state = random.getstate()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.getstate",
      "version": "",
      "section": "Модуль random",
      "subcat": "состояние ГСЧ",
      "color_group": "module",
      "aliases": [
        "сохранить состояние генератора",
        "повторить последовательность случайных чисел",
        "состояние генератора случайных чисел"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "random.seed(1)",
        "state = random.getstate()",
        "a = random.random()",
        "random.setstate(state)",
        "print(a == random.random())   # → True"
      ],
      "related": [
        "random.setstate",
        "random.seed"
      ],
      "related_errors": []
    },
    {
      "id": "random.lognormvariate",
      "title": "random.lognormvariate",
      "kind": "function",
      "summary": {
        "ru": "Случайное число из логнормального распределения (логарифм подчинён нормальному с mu, sigma); результат всегда положителен.",
        "en": "A random number from the log-normal distribution; always positive."
      },
      "body": {
        "ru": "mu и sigma описывают логарифм результата, а не сам результат: медиана выдачи равна exp(mu), а среднее — exp(mu + sigma^2/2). Подставлять сюда желаемое среднее и разброс самих чисел — типичная ошибка. Распределение скошено вправо: длинный хвост больших значений тянет среднее заметно выше медианы, из-за чего оно и годится для моделирования доходов, времени отклика, размеров файлов.",
        "en": "mu and sigma describe the logarithm of the result, not the result itself: the median is exp(mu) and the mean is exp(mu + sigma^2/2). Feeding in the mean and spread you want for the actual numbers is the usual mistake. The shape is right-skewed — a long tail of large values pulls the mean well above the median — which is exactly why it fits incomes, response times and file sizes."
      },
      "syntax": "random.lognormvariate(mu, sigma)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.lognormvariate",
      "version": "",
      "section": "Модуль random",
      "subcat": "распределения",
      "color_group": "module",
      "aliases": [
        "логнормальное распределение",
        "логарифмически нормальное распределение"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "print(type(random.lognormvariate(0.0, 1.0)).__name__)   # → float (значение случайное, медиана ≈ 1.0)",
        "print(random.lognormvariate(0.0, 1.0) > 0.0)   # → True",
        "print(random.lognormvariate(0.0, 0.0))   # → 1.0",
        "sample = [random.lognormvariate(0.0, 1.0) for _ in range(1000)]",
        "print(all(x > 0.0 for x in sample))   # → True"
      ],
      "related": [
        "random.normalvariate",
        "random.gauss",
        "math.exp"
      ],
      "related_errors": []
    },
    {
      "id": "random.normalvariate",
      "title": "random.normalvariate",
      "kind": "function",
      "summary": {
        "ru": "Случайное число из нормального (гауссова) распределения со средним mu и стандартным отклонением sigma.",
        "en": "A random number from the normal (Gaussian) distribution with mean mu and stddev sigma."
      },
      "body": {
        "ru": "От gauss отличается не распределением, а реализацией: normalvariate ничего не кеширует между вызовами, поэтому безопасен при одновременном обращении из нескольких потоков, но чуть медленнее. Значения по умолчанию mu=0.0 и sigma=1.0 появились в Python 3.11 — на более старых версиях оба аргумента обязательны.",
        "en": "It differs from gauss in implementation, not in distribution: normalvariate keeps no state between calls, so it is safe when several threads draw at once, at the cost of being slightly slower. The defaults mu=0.0 and sigma=1.0 arrived in Python 3.11; on older versions both arguments are required."
      },
      "syntax": "random.normalvariate(mu=0.0, sigma=1.0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.normalvariate",
      "version": "",
      "section": "Модуль random",
      "subcat": "распределения",
      "color_group": "module",
      "aliases": [
        "нормальный закон распределения",
        "случайное число со средним и стандартным отклонением"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "print(isinstance(random.normalvariate(0.0, 1.0), float))   # → True",
        "print(isinstance(random.normalvariate(), float))   # → True",
        "print(random.normalvariate(5.0, 0.0))   # → 5.0",
        "sample = [random.normalvariate(100.0, 15.0) for _ in range(10000)]",
        "print(90.0 < sum(sample) / len(sample) < 110.0)   # → True",
        "print(isinstance(random.gauss(0.0, 1.0), float))   # → True"
      ],
      "related": [
        "random.gauss",
        "random.lognormvariate",
        "statistics.NormalDist"
      ],
      "related_errors": []
    },
    {
      "id": "random.paretovariate",
      "title": "random.paretovariate",
      "kind": "function",
      "summary": {
        "ru": "Случайное число из распределения Парето с параметром формы alpha; результат всегда не меньше 1.",
        "en": "A random number from the Pareto distribution with shape alpha; always ≥ 1."
      },
      "body": {
        "ru": "Хвост тяжёлый: среднее у распределения конечно только при alpha > 1, а дисперсия — при alpha > 2, поэтому при alpha около единицы среднее по выборке скачет от прогона к прогону и опираться на него бессмысленно. Масштаб зафиксирован — минимум всегда 1; чтобы получить другую нижнюю границу, результат просто умножают на неё. alpha должен быть положительным: нулевой даст ZeroDivisionError.",
        "en": "This is a heavy-tailed distribution: the mean exists only when alpha > 1 and the variance only when alpha > 2, so for alpha near 1 the sample average jumps wildly between runs and tells you almost nothing. The scale is fixed at 1 (the minimum possible value) — multiply the result if you need a different lower bound. Pass a positive alpha; zero raises ZeroDivisionError."
      },
      "syntax": "random.paretovariate(alpha)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.paretovariate",
      "version": "",
      "section": "Модуль random",
      "subcat": "распределения",
      "color_group": "module",
      "aliases": [
        "распределение Парето",
        "закон Парето"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "print(random.paretovariate(1.0) >= 1.0)   # → True",
        "print(random.paretovariate(1000.0) < 1.1)   # → True",
        "sample = [random.paretovariate(2.0) for _ in range(1000)]",
        "print(min(sample) >= 1.0)   # → True",
        "print(random.paretovariate(0.0))   # → ZeroDivisionError"
      ],
      "related": [
        "random.weibullvariate",
        "random.expovariate",
        "random.lognormvariate"
      ],
      "related_errors": []
    },
    {
      "id": "random.randbytes",
      "title": "random.randbytes",
      "kind": "function",
      "summary": {
        "ru": "Возвращает bytes из n случайных байтов (Python 3.9+); удобно для генерации токенов/солей в некриптографических задачах.",
        "en": "Return n random bytes as a bytes object (3.9+)."
      },
      "body": {
        "ru": "Настоящее преимущество перед os.urandom — воспроизводимость: под фиксированным seed вернётся та же цепочка байт, и бинарные фикстуры в тестах становятся детерминированными. Для солей, сессионных токенов и вообще всего, что охраняет доступ, берут secrets.token_bytes() — этот генератор предсказуем, если понаблюдать за его выдачей.",
        "en": "Its real edge over os.urandom is repeatability: fix the seed and the same byte string comes back, which makes binary test fixtures deterministic. For salts, session tokens or anything else that guards access, reach for secrets.token_bytes() — this generator becomes predictable once enough of its output is seen."
      },
      "syntax": "random.randbytes(n)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.randbytes",
      "version": "3.9",
      "section": "Модуль random",
      "subcat": "биты и байты",
      "color_group": "module",
      "aliases": [
        "случайные байты",
        "сгенерировать случайный токен"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "print(len(random.randbytes(4)))   # → 4",
        "print(type(random.randbytes(2)))   # → <class 'bytes'>",
        "print(random.randbytes(0))   # → b''",
        "print(len(random.randbytes(16).hex()))   # → 32",
        "print(random.randbytes(-1))   # → ValueError"
      ],
      "related": [
        "random.getrandbits",
        "os.urandom",
        "random.systemrandom"
      ],
      "related_errors": []
    },
    {
      "id": "random.randint",
      "title": "random.randint",
      "kind": "term",
      "summary": {
        "ru": "Возвращает случайное целое число из диапазона [a, b] включительно. Оба конца включены.",
        "en": "Returns a random integer from the range [a, b] inclusive. Both ends are included."
      },
      "body": {
        "ru": "Верхняя граница включена — этим randint отличается от range и randrange, и отсюда классический промах: random.randint(0, len(items)) однажды вернёт len(items) и уронит индексацию, нужно len(items) - 1. Для паролей, токенов и всего, что защищает данные, randint не годится: последовательность предсказуема по нескольким выданным числам, там берут модуль secrets.",
        "en": "Unlike range and randrange, the upper bound is included — hence the classic off-by-one: random.randint(0, len(items)) will eventually return len(items) and blow up the indexing, you want len(items) - 1. Never use it for passwords, tokens or anything security-related: the stream is predictable from a handful of outputs, so reach for the secrets module instead."
      },
      "syntax": "random.randint(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.randint",
      "version": "",
      "section": "Модуль random",
      "subcat": "целые",
      "color_group": "module",
      "aliases": [
        "случайное целое число",
        "случайное число в заданном диапазоне",
        "рандом от и до"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import random",
        "random.seed(42)",
        "print(random.randint(1, 6))   # → бросок кубика",
        "print(random.randint(0, 100)) # → 0..100",
        "print(random.randint(-10, 10)) # → -10..10",
        "results = [random.randint(1,6) for _ in range(10)]",
        "print(results)  # → 10 бросков",
        "from collections import Counter",
        "print(Counter(results))"
      ],
      "related": [
        "random.randrange",
        "random.uniform",
        "random.choice"
      ],
      "related_errors": []
    },
    {
      "id": "random.random",
      "title": "random.random",
      "kind": "term",
      "summary": {
        "ru": "Возвращает псевдослучайное число из диапазона [0.0, 1.0) с равномерным распределением.",
        "en": "Returns a pseudo-random number from [0.0, 1.0), uniformly distributed."
      },
      "body": {
        "ru": "Под капотом вихрь Мерсенна: последовательность полностью восстанавливается по внутреннему состоянию генератора, поэтому random() не годится для паролей, токенов и ключей — для этого есть модуль secrets. Ровно 1.0 не выпадет никогда (правая граница открыта), а значения ложатся на сетку с шагом 2**-53, так что сравнивать результат на точное равенство с каким-то числом бессмысленно.",
        "en": "Under the hood is the Mersenne Twister: the whole sequence can be reconstructed from the generator state, so random() must not be used for passwords, tokens or keys — use the secrets module for that. Exactly 1.0 never comes out (the upper bound is excluded), and values land on a grid of 2**-53 steps, so testing the result for exact equality with a given number is pointless."
      },
      "syntax": "random.random()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.random",
      "version": "",
      "section": "Модуль random",
      "subcat": "вещественные",
      "color_group": "module",
      "aliases": [
        "случайное число от 0 до 1",
        "случайное вещественное число",
        "случайная дробь"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import random",
        "random.seed(42)",
        "print(random.random())  # → 0.6394...",
        "print(random.random())  # → 0.0250...",
        "# Масштабирование на [a, b):",
        "a, b = 5, 10",
        "x = a + (b-a) * random.random()",
        "print(round(x, 2))  # → случайное в [5,10)",
        "print(type(random.random()))  # → float"
      ],
      "related": [
        "random.uniform",
        "random.randint",
        "random.seed"
      ],
      "related_errors": []
    },
    {
      "id": "random.randrange",
      "title": "random.randrange",
      "kind": "term",
      "summary": {
        "ru": "Возвращает случайный элемент из range(start, stop[, step]). Аналог choice(range(...)), но без создания списка.",
        "en": "Returns a random element of range(start, stop[, step]). Like choice(range(...)), but without building the list."
      },
      "body": {
        "ru": "Верхний конец не включается, ровно как у range: randrange(1, 10) никогда не вернёт 10 — если нужен диапазон с включённым концом, это randint. Пустой диапазон (randrange(0) или randrange(5, 5)) — это ValueError, а не None. Начиная с Python 3.12 дробные аргументы отвергаются: randrange(10.0) даёт TypeError, хотя раньше такое значение молча приводилось к целому.",
        "en": "The upper bound is excluded, exactly like in range: randrange(1, 10) never yields 10 — use randint when you want the endpoint included. An empty range such as randrange(0) or randrange(5, 5) raises ValueError rather than returning None. Since Python 3.12 non-integer arguments are rejected outright: randrange(10.0) raises TypeError, whereas older versions silently converted such values."
      },
      "syntax": "random.randrange(stop)\nrandom.randrange(start, stop[, step])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.randrange",
      "version": "",
      "section": "Модуль random",
      "subcat": "целые",
      "color_group": "module",
      "aliases": [
        "случайное число с шагом",
        "случайный индекс списка",
        "случайное чётное число"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import random",
        "random.seed(0)",
        "print(random.randrange(10))     # → 0..9",
        "print(random.randrange(1, 10))  # → 1..9",
        "print(random.randrange(0, 10, 2))  # → чётное 0,2,4,6,8",
        "print(random.randrange(1, 10, 2))  # → нечётное 1,3,5,7,9",
        "results = [random.randrange(0,10,2) for _ in range(5)]",
        "print(results)"
      ],
      "related": [
        "random.randint",
        "range",
        "random.choice"
      ],
      "related_errors": []
    },
    {
      "id": "random.sample",
      "title": "random.sample",
      "kind": "term",
      "summary": {
        "ru": "Возвращает список из k уникальных случайных элементов без повторений. k не может превышать len(population).",
        "en": "Returns a list of k distinct random items, without repetition. k cannot exceed len(population)."
      },
      "body": {
        "ru": "Уникальность тут позиционная, а не по значению: если в population есть одинаковые элементы, одно и то же значение спокойно попадёт в результат дважды. k больше длины population даёт ValueError, а не укороченную выборку; множество или словарь передавать нельзя (с 3.11 это TypeError) — сначала оберните в list(). Сам population не меняется, в отличие от random.shuffle.",
        "en": "Distinctness here is positional, not by value: if population holds equal items, the same value can appear twice in the result. A k larger than len(population) raises ValueError instead of returning a shorter list, and sets or dicts are rejected outright since 3.11 — wrap them in list() first. The population itself is left untouched, unlike random.shuffle."
      },
      "syntax": "random.sample(population, k)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.sample",
      "version": "",
      "section": "Модуль random",
      "subcat": "выборка",
      "color_group": "module",
      "aliases": [
        "случайная выборка без повторений",
        "выбрать несколько уникальных случайных элементов",
        "случайные неповторяющиеся элементы"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import random",
        "random.seed(7)",
        "print(random.sample(range(10), 3))  # → 3 уникальных",
        "print(random.sample('abcdef', 4))   # → 4 символа",
        "deck = list(range(1,53))",
        "hand = random.sample(deck, 5)       # покерная рука",
        "print(sorted(hand))",
        "print(random.sample(range(1,46), 6))  # → лотерея 6 из 45",
        "print(random.sample([1,2,3], 3))    # → перестановка"
      ],
      "related": [
        "random.choices",
        "random.choice",
        "random.shuffle"
      ],
      "related_errors": []
    },
    {
      "id": "random.seed",
      "title": "random.seed",
      "kind": "term",
      "summary": {
        "ru": "Инициализирует генератор псевдослучайных чисел. Одинаковый seed — одинаковая последовательность.",
        "en": "Initializes the pseudo-random number generator. The same seed gives the same sequence."
      },
      "body": {
        "ru": "Классическая ошибка — звать seed с одним и тем же числом внутри цикла: генератор каждый раз откатывается в начало и выдаёт одно и то же значение. Seed ставят один раз на старте и только ради воспроизводимости; без него (seed(None)) генератор сам берёт энтропию у ОС. Одинаковая последовательность random() на одинаковом seed гарантирована документацией, а вот для shuffle и sample такой гарантии между версиями Python нет.",
        "en": "A classic mistake is calling seed with the same value inside a loop: the generator rewinds every iteration and keeps handing back the identical number. Seed once at startup, and only when you need reproducibility; left alone (seed(None)) the generator pulls entropy from the OS. The docs promise that random() reproduces the same sequence for the same seed, but no such promise covers shuffle or sample across Python versions."
      },
      "syntax": "random.seed(a=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.seed",
      "version": "",
      "section": "Модуль random",
      "subcat": "воспроизводимость",
      "color_group": "module",
      "aliases": [
        "зерно генератора случайных чисел",
        "воспроизводимые случайные числа",
        "зафиксировать случайную последовательность"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import random",
        "random.seed(42)",
        "print([random.random() for _ in range(3)])  # → [0.639, 0.025, 0.275]",
        "random.seed(42)",
        "print([random.random() for _ in range(3)])  # → то же самое!",
        "random.seed(None)  # случайная инициализация",
        "random.seed('hello')  # строка как seed",
        "print(random.randint(1,100))"
      ],
      "related": [
        "random.getstate",
        "random.setstate",
        "random.random"
      ],
      "related_errors": []
    },
    {
      "id": "random.setstate",
      "title": "random.setstate",
      "kind": "function",
      "summary": {
        "ru": "Восстанавливает внутреннее состояние генератора из объекта, полученного ранее через getstate — генерация продолжится с той же точки.",
        "en": "Restore the generator's internal state from a getstate() object."
      },
      "body": {
        "ru": "Объект state — непрозрачный кортеж, завязанный на конкретную реализацию генератора: складывать его в файл и подсовывать другой версии Python не стоит, для воспроизводимости надёжнее запомнить seed. И помните, что setstate перематывает общий генератор модуля — откатывается случайность всей программы, включая сторонние библиотеки; если нужна изоляция, заведите свой экземпляр random.Random().",
        "en": "The state object is an opaque tuple tied to the current generator implementation, so do not persist it and feed it to another Python version — remembering the seed is the safer way to get reproducibility. Also note that setstate rewinds the module-wide generator, so every consumer of random, including third-party libraries, is rewound with it; use a private random.Random() instance when you need isolation."
      },
      "syntax": "random.setstate(state)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.setstate",
      "version": "",
      "section": "Модуль random",
      "subcat": "состояние ГСЧ",
      "color_group": "module",
      "aliases": [
        "восстановить состояние генератора",
        "продолжить генерацию с сохранённой точки"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "random.seed(1)",
        "saved = random.getstate()",
        "first = random.random()",
        "random.setstate(saved)",
        "print(random.random() == first)   # → True"
      ],
      "related": [
        "random.getstate",
        "random.seed"
      ],
      "related_errors": []
    },
    {
      "id": "random.shuffle",
      "title": "random.shuffle",
      "kind": "term",
      "summary": {
        "ru": "Перемешивает список на месте случайным образом. Изменяет оригинальный объект, ничего не возвращает.",
        "en": "Shuffles a list in place at random. It changes the original object and returns nothing."
      },
      "body": {
        "ru": "Классическая ошибка — присвоить результат: deck = random.shuffle(deck) затирает список значением None. Перемешать можно только изменяемую последовательность — на кортеже или строке будет TypeError; чтобы получить перемешанную копию, не трогая оригинал, используйте random.sample(x, len(x)).",
        "en": "The classic slip is assigning the result: deck = random.shuffle(deck) replaces the list with None. Only mutable sequences work — a tuple or a string raises TypeError; to get a shuffled copy while keeping the original intact, use random.sample(x, len(x))."
      },
      "syntax": "random.shuffle(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.shuffle",
      "version": "",
      "section": "Модуль random",
      "subcat": "перемешивание",
      "color_group": "module",
      "aliases": [
        "перемешать список",
        "случайный порядок элементов",
        "перетасовать колоду"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import random",
        "random.seed(10)",
        "deck = list(range(1, 14))",
        "random.shuffle(deck)",
        "print(deck)  # → перемешанная колода",
        "words = ['alpha','beta','gamma','delta']",
        "random.shuffle(words)",
        "print(words)  # → случайный порядок",
        "# Копия перемешанная:",
        "import random",
        "original = [1,2,3,4,5]",
        "import copy; shuffled = copy.copy(original)",
        "random.shuffle(shuffled)",
        "print(original, shuffled)"
      ],
      "related": [
        "random.sample",
        "list.sort",
        "random.choice"
      ],
      "related_errors": []
    },
    {
      "id": "random.systemrandom",
      "title": "random.SystemRandom",
      "kind": "term",
      "summary": {
        "ru": "Криптографически безопасный генератор, использующий os.urandom().",
        "en": "A cryptographically secure generator that draws on os.urandom()."
      },
      "body": {
        "ru": "Воспроизводимости здесь нет по определению: seed() не делает ничего, а getstate()/setstate() бросают NotImplementedError — зафиксировать выборку для теста не выйдет, для этого нужен обычный random. Если задача — пароль, токен или ключ, берите сразу модуль secrets: тот же источник энтропии, но с готовыми token_hex(), token_urlsafe() и compare_digest().",
        "en": "Reproducibility is impossible by design: seed() does nothing and getstate()/setstate() raise NotImplementedError, so you cannot pin results for a test — use the ordinary random for that. When the goal is a password, token or key, reach for the secrets module instead: same entropy source, but with ready-made token_hex(), token_urlsafe() and compare_digest()."
      },
      "syntax": "random.SystemRandom()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.SystemRandom",
      "version": "",
      "section": "Модуль random",
      "subcat": "класс random",
      "color_group": "module",
      "aliases": [
        "криптостойкий генератор случайных чисел",
        "безопасная генерация случайных чисел"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import random",
        "srng = random.SystemRandom()",
        "print(srng.random())  # → криптобезопасное [0,1)",
        "print(srng.randint(1, 100))  # → безопасное целое",
        "print(srng.choice(['a','b','c']))  # → безопасный выбор",
        "# Нельзя установить seed для SystemRandom",
        "try:",
        "    srng.seed(42)  # → NotImplementedError или ничего",
        "except NotImplementedError:",
        "    print('seed not supported')",
        "    import os; print(len(os.urandom(16)))  # → 16 байт"
      ],
      "related": [
        "os.urandom",
        "random.seed",
        "random.random"
      ],
      "related_errors": []
    },
    {
      "id": "random.triangular",
      "title": "random.triangular",
      "kind": "function",
      "summary": {
        "ru": "Случайное число из треугольного распределения на отрезке [low, high] с модой mode; результат в этих границах.",
        "en": "A random number from the triangular distribution on [low, high] with mode; within the bounds."
      },
      "body": {
        "ru": "Если mode не передан, пик оказывается ровно посередине между low и high, и распределение становится симметричным — многие ждут смещения к low и получают не то, что задумали. Её берут там, где данных нет, а есть только экспертная оценка «минимум — вероятнее всего — максимум»: в отличие от uniform она учитывает, что значения у моды вероятнее крайних. Вырожденный случай low == high не падает, а просто возвращает low.",
        "en": "With mode omitted the peak sits exactly midway between low and high, giving a symmetric triangle — people often assume the default leans toward low and get a different shape than intended. Reach for it in three-point estimation, when all you have is a minimum, a most-likely value and a maximum: unlike uniform it gives weight to the peak. The degenerate case low == high does not raise, it returns low."
      },
      "syntax": "random.triangular(low=0.0, high=1.0, mode=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.triangular",
      "version": "",
      "section": "Модуль random",
      "subcat": "распределения",
      "color_group": "module",
      "aliases": [
        "треугольное распределение",
        "случайное число с наиболее вероятным значением"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "print(0.0 <= random.triangular(0.0, 10.0, 5.0) <= 10.0)   # → True",
        "print(0.0 <= random.triangular() <= 1.0)   # → True",
        "print(random.triangular(5.0, 5.0, 5.0))   # → 5.0",
        "print(all(1 <= random.triangular(1, 10, 2) <= 10 for _ in range(1000)))   # → True",
        "print(round(sum(random.triangular(0, 9, 0) for _ in range(20000)) / 20000))   # → 3"
      ],
      "related": [
        "random.uniform",
        "random.betavariate",
        "random.gauss"
      ],
      "related_errors": []
    },
    {
      "id": "random.uniform",
      "title": "random.uniform",
      "kind": "term",
      "summary": {
        "ru": "Возвращает случайное вещественное число из диапазона [a, b] с равномерным распределением.",
        "en": "Returns a random floating-point number from the range [a, b], uniformly distributed."
      },
      "body": {
        "ru": "Попадёт ли в результат сама граница b — не гарантировано: значение считается как a + (b - a) * random(), и всё решает округление float, поэтому на «включительно» полагаться не стоит. Порядок аргументов не важен, uniform(10, 1) работает так же, как uniform(1, 10). Результат всегда вещественный — для случайного целого нужен randint или randrange, а не int(uniform(...)).",
        "en": "Whether the endpoint b actually shows up is not guaranteed: the value is computed as a + (b - a) * random(), and float rounding decides, so do not rely on the range being closed. Argument order does not matter — uniform(10, 1) behaves like uniform(1, 10). The result is always a float; for a random integer reach for randint or randrange rather than int(uniform(...))."
      },
      "syntax": "random.uniform(a, b)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.uniform",
      "version": "",
      "section": "Модуль random",
      "subcat": "вещественные",
      "color_group": "module",
      "aliases": [
        "случайное число в диапазоне",
        "случайное дробное число в интервале"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import random",
        "random.seed(1)",
        "print(random.uniform(1, 10))  # → случайное в [1,10]",
        "print(random.uniform(-1, 1))  # → в [-1,1]",
        "print(random.uniform(0, 0.5)) # → в [0,0.5]",
        "print(random.uniform(100, 200))  # → в [100,200]",
        "results = [random.uniform(0,1) for _ in range(5)]",
        "print([round(x,3) for x in results])"
      ],
      "related": [
        "random.random",
        "random.randint",
        "random.gauss"
      ],
      "related_errors": []
    },
    {
      "id": "random.vonmisesvariate",
      "title": "random.vonmisesvariate",
      "kind": "function",
      "summary": {
        "ru": "Случайный угол из распределения фон Мизеса (кругового аналога нормального) с центром mu и концентрацией kappa; результат в [0, 2π].",
        "en": "A random angle from the von Mises distribution (mu, kappa); in [0, 2π]."
      },
      "body": {
        "ru": "kappa — мера кучности: при kappa = 0 концентрации нет вовсе и функция выдаёт равномерный угол по всему кругу, а при больших kappa разброс вокруг mu близок к нормальному со стандартным отклонением примерно 1/sqrt(kappa). Помните, что это угол в радианах с заворотом через 2π: усреднять такие значения арифметически нельзя — «среднее» 0.1 и 6.2 радиан даст 3.15, то есть ровно противоположное направление.",
        "en": "kappa controls concentration: at kappa = 0 there is none at all and you get a uniform angle over the whole circle, while for large kappa the spread around mu is close to normal with a standard deviation of about 1/sqrt(kappa). The result is an angle in radians that wraps at 2π, so plain arithmetic averaging is wrong — the \"mean\" of 0.1 and 6.2 radians comes out as 3.15, pointing the opposite way."
      },
      "syntax": "random.vonmisesvariate(mu, kappa)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.vonmisesvariate",
      "version": "",
      "section": "Модуль random",
      "subcat": "распределения",
      "color_group": "module",
      "aliases": [
        "распределение фон Мизеса",
        "случайный угол",
        "круговое нормальное распределение"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random, math",
        "print(0.0 <= random.vonmisesvariate(0.0, 1.0) <= 2 * math.pi)   # → True",
        "print(0.0 <= random.vonmisesvariate(0.0, 0.0) <= 2 * math.pi)   # → True",
        "print(abs(random.vonmisesvariate(math.pi, 500.0) - math.pi) < 0.5)   # → True",
        "print(0.0 <= math.degrees(random.vonmisesvariate(0.0, 4.0)) <= 360.0)   # → True",
        "print(all(0.0 <= random.vonmisesvariate(math.pi, 2.0) <= 2 * math.pi for _ in range(1000)))   # → True"
      ],
      "related": [
        "random.uniform",
        "random.gauss",
        "math.pi"
      ],
      "related_errors": []
    },
    {
      "id": "random.weibullvariate",
      "title": "random.weibullvariate",
      "kind": "function",
      "summary": {
        "ru": "Случайное число из распределения Вейбулла (параметры масштаба alpha и формы beta); результат всегда неотрицателен.",
        "en": "A random number from the Weibull distribution (scale alpha, shape beta); always non-negative."
      },
      "body": {
        "ru": "Порядок параметров непривычный: сначала масштаб alpha, потом форма beta, тогда как в учебниках и в scipy форму обычно пишут первой — перепутанные аргументы дают правдоподобные с виду, но неверные числа. При beta = 1 распределение вырождается в экспоненциальное со средним alpha, а с ростом beta значения всё плотнее жмутся к alpha — этим и пользуются, моделируя износ и время до отказа.",
        "en": "The argument order trips people up: scale alpha comes first, shape beta second, whereas textbooks and scipy usually put the shape first — swap them and you still get plausible-looking numbers that are simply wrong. At beta = 1 the distribution collapses to an exponential with mean alpha, and as beta grows the values cluster ever more tightly around alpha, which is why it is the standard choice for modelling wear-out and time to failure."
      },
      "syntax": "random.weibullvariate(alpha, beta)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/random.html#random.weibullvariate",
      "version": "",
      "section": "Модуль random",
      "subcat": "распределения",
      "color_group": "module",
      "aliases": [
        "распределение Вейбулла",
        "случайное число из распределения Вейбулла"
      ],
      "keywords": [],
      "tags": [
        "random"
      ],
      "examples": [
        "import random",
        "print(random.weibullvariate(1.0, 1.0) >= 0.0)   # → True",
        "print(round(sum(random.weibullvariate(2.0, 1.0) for _ in range(50000)) / 50000))   # → 2",
        "print(abs(random.weibullvariate(10.0, 200.0) - 10.0) < 2.0)   # → True",
        "print(round(sum(random.weibullvariate(1.0, 1.0) > 1.0 for _ in range(50000)) / 50000, 1))   # → 0.4",
        "print(random.weibullvariate(1.0, 0.0))   # → ZeroDivisionError"
      ],
      "related": [
        "random.expovariate",
        "random.gammavariate",
        "random.paretovariate"
      ],
      "related_errors": []
    },
    {
      "id": "re-named-group",
      "title": "(?P<name>...)",
      "kind": "function",
      "summary": {
        "ru": "Именованная группа захвата. Позволяет обращаться к совпадению по имени через match.group('name') или match['name'] вместо числового индекса.",
        "en": "A named capturing group. It lets you reach the match by name, through match.group('name') or match['name'], instead of by a numeric index."
      },
      "body": {
        "ru": "Имя не отменяет нумерацию, а дополняет её: та же группа по-прежнему доступна как m.group(1), внутри шаблона — как обратная ссылка (?P=name), а в строке замены re.sub — как \\g<name>. Имя должно быть валидным идентификатором и уникальным в пределах шаблона: два (?P<x>...) в одном регулярном выражении просто не скомпилируются. В groupdict() попадают только именованные группы, и у той, что не участвовала в совпадении, значение будет None, а не пустая строка.",
        "en": "Naming a group does not replace its number: the same group is still m.group(1), it is referenced inside the pattern as (?P=name) and inside an re.sub replacement as \\g<name>. The name must be a valid identifier and unique within the pattern — two (?P<x>...) groups in one regex simply fail to compile. groupdict() reports named groups only, and a named group that did not take part in the match maps to None, not to an empty string."
      },
      "syntax": "(?P<name>pattern)\nmatch.group('name')\nmatch.groupdict()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.Match.group",
      "version": "",
      "section": "Модуль re",
      "subcat": "группы",
      "color_group": "module",
      "aliases": [
        "именованная группа",
        "обращение к группе по имени",
        "дать имя группе в регулярном выражении"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "pattern = r'(?P<year>\\d{4})-(?P<month>\\d{2})-(?P<day>\\d{2})'",
        "m = re.match(pattern, '2024-06-17')",
        "print(m.group('year'))   # 2024",
        "print(m['month'])        # 06",
        "print(m.groupdict())     # {'year': '2024', 'month': '06', 'day': '17'}"
      ],
      "related": [
        "re.groupdict",
        "re.group",
        "группы-в-паттернах"
      ],
      "related_errors": [
        "AttributeError",
        "IndexError"
      ]
    },
    {
      "id": "re.Pattern",
      "title": "re.Pattern",
      "kind": "term",
      "summary": {
        "ru": "Скомпилированное регулярное выражение (результат re.compile) с методами match/search/findall/sub.",
        "en": "A compiled regular expression (from re.compile) with match/search/findall/sub methods."
      },
      "body": {
        "ru": "Класс не создают напрямую: re.Pattern(...) не вызывают, объект получается только из re.compile, а само имя нужно для аннотаций и isinstance. Паттерн помнит, из чего он скомпилирован — str-паттерн, применённый к bytes-строке (и наоборот), даёт TypeError, а не пустой результат. Полезные атрибуты: pattern, flags и groupindex со словарём именованных групп.",
        "en": "You never instantiate it yourself: re.Pattern(...) is not a constructor, objects come only from re.compile, and the name exists for type hints and isinstance checks. A pattern remembers its string type — applying a str pattern to a bytes object (or the reverse) raises TypeError instead of quietly finding nothing. Useful attributes are pattern, flags and groupindex, the mapping of named groups."
      },
      "syntax": "re.compile(pattern) -> re.Pattern",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.Pattern",
      "version": "",
      "section": "Модуль re",
      "subcat": "объекты",
      "color_group": "module",
      "aliases": [
        "скомпилированный шаблон",
        "объект регулярного выражения"
      ],
      "keywords": [],
      "tags": [
        "re"
      ],
      "examples": [
        "import re",
        "p = re.compile(r'\\d+')",
        "print(p.findall('a12b3'))   # → ['12', '3']",
        "print(p.search('year 2024').group())   # → 2024",
        "print(p.match('year 2024'))   # → None",
        "print(p.sub('#', 'a12b3'))   # → a#b#",
        "print(isinstance(p, re.Pattern))   # → True"
      ],
      "related": [
        "re.compile",
        "re.search",
        "re.findall"
      ],
      "related_errors": []
    },
    {
      "id": "re.RegexFlag",
      "title": "re.RegexFlag",
      "kind": "term",
      "summary": {
        "ru": "Перечисление флагов регулярных выражений (IGNORECASE, MULTILINE, DOTALL, …).",
        "en": "An enumeration of regex flags (IGNORECASE, MULTILINE, DOTALL, …)."
      },
      "body": {
        "ru": "Это enum.IntFlag, то есть флаги — обычные битовые маски: re.I | re.M даёт новый RegexFlag со значением 10, и такое объединение через | — единственный способ включить несколько флагов сразу. Само имя re.RegexFlag в коде почти не пишут: оно всплывает в аннотациях типов и в repr, а в вызовах используют короткие re.I, re.M, re.S.",
        "en": "RegexFlag is an enum.IntFlag, so the flags are plain bit masks: re.I | re.M produces another RegexFlag whose value is 10, and combining with | is the only way to turn on several flags at once. You rarely spell out re.RegexFlag yourself — it shows up in type hints and reprs, while calls use the short re.I, re.M, re.S."
      },
      "syntax": "re.RegexFlag",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.RegexFlag",
      "version": "3.11",
      "section": "Модуль re",
      "subcat": "флаги",
      "color_group": "module",
      "aliases": [
        "перечисление флагов регулярок",
        "тип флагов регулярного выражения"
      ],
      "keywords": [],
      "tags": [
        "re"
      ],
      "examples": [
        "import re",
        "print(re.IGNORECASE.name)   # → IGNORECASE",
        "print(bool(re.match('abc', 'ABC', re.IGNORECASE)))   # → True",
        "print(re.IGNORECASE.value)   # → 2",
        "flags = re.IGNORECASE | re.MULTILINE",
        "print(re.findall('^b', 'a\\nB', flags))   # → ['B']"
      ],
      "related": [
        "флаги-re",
        "re.compile",
        "re.Pattern"
      ],
      "related_errors": []
    },
    {
      "id": "re.compile",
      "title": "re.compile()",
      "kind": "function",
      "summary": {
        "ru": "Компилирует паттерн в объект RegexObject для повторного использования. Ускоряет многократные операции.",
        "en": "Compiles a pattern into a RegexObject that can be reused. It speeds up repeated operations."
      },
      "body": {
        "ru": "Ускорение обычно меньше ожидаемого: модульные re.findall/re.search сами кешируют скомпилированные паттерны, так что compile выигрывает прежде всего в читаемости — паттерн собран один раз и в одном месте. Настоящее преимущество объекта в другом: его методы принимают pos и endpos, ограничивая поиск куском строки без срезов, а флаги задаются один раз при компиляции.",
        "en": "The speedup is usually smaller than you expect: the module-level re.findall/re.search already cache compiled patterns, so compile mostly buys readability by keeping the pattern in one place. The object's own API is the real gain — its methods take pos and endpos to restrict the search to part of the string without copying it, and flags are fixed once at compile time."
      },
      "syntax": "re.compile(pattern, flags=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.compile",
      "version": "",
      "section": "Модуль re",
      "subcat": "компиляция",
      "color_group": "module",
      "aliases": [
        "скомпилировать регулярное выражение",
        "заранее подготовленный шаблон",
        "ускорить повторный поиск по шаблону"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "pat = re.compile(r'\\d+')",
        "pat.findall('a1 b22') # → ['1', '22']",
        "pat.sub('X', 'a1b2') # → 'aXbX'",
        "pat.match('123abc').group() # → '123'",
        "pat2 = re.compile(r'hello', re.IGNORECASE)",
        "bool(pat2.search('Hello World')) # → True"
      ],
      "related": [
        "re.Pattern",
        "re.search",
        "флаги-re",
        "re.purge"
      ],
      "related_errors": []
    },
    {
      "id": "re.escape",
      "title": "re.escape()",
      "kind": "function",
      "summary": {
        "ru": "Экранирует все спецсимволы в строке, чтобы использовать её как литеральный паттерн в регулярном выражении. Полезно, когда паттерн формируется из пользовательского ввода. С Python 3.7 экранируются только символы, действительно значимые для regex.",
        "en": "Escapes every special character in a string so that it can be used as a literal pattern inside a regular expression. Useful when the pattern is built from user input; since Python 3.7 only characters meaningful to regex are escaped."
      },
      "body": {
        "ru": "Нужна ровно там, где часть паттерна — это данные: имя файла, слово из пользовательского ввода, разделитель из переменной. Без экранирования точка, плюс или скобки внезапно станут метасимволами, а незакрытая скобка — ошибкой re.error. На строку замены в re.sub() это не распространяется: там свои правила, и пришедшие из данных \\1 или \\g<name> всё равно сработают как ссылки на группы — надёжнее передать вместо repl функцию.",
        "en": "Reach for it exactly when part of the pattern is data: a file name, a word typed by the user, a separator held in a variable. Without escaping a dot, plus or bracket silently becomes a metacharacter, and an unbalanced bracket becomes an re.error. It does not cover the replacement string of re.sub(), which has its own rules — a \\1 or \\g<name> coming from data still acts as a group reference, so pass a function as repl instead."
      },
      "syntax": "re.escape(pattern) -> str",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.escape",
      "version": "",
      "section": "Модуль re",
      "subcat": "экранирование",
      "color_group": "module",
      "aliases": [
        "экранировать спецсимволы в шаблоне",
        "искать текст буквально",
        "шаблон из пользовательского ввода"
      ],
      "keywords": [
        "re.escape",
        "экранирование",
        "спецсимволы"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "print(re.escape('1+1=2'))  # → 1\\+1=2",
        "print(re.escape('file.txt'))  # → file\\.txt",
        "print(re.escape('file.name (v2).txt'))  # → file\\.name\\ \\(v2\\)\\.txt",
        "print(re.escape('a|b|c'))  # → a\\|b\\|c",
        "print(re.findall(re.escape('price (USD)'), 'price (USD) today'))  # → ['price (USD)']",
        "print(re.compile(re.escape('c++')).search('i know c++') is not None)  # → True"
      ],
      "related": [
        "re.compile",
        "re.findall",
        "re.sub"
      ],
      "related_errors": []
    },
    {
      "id": "re.findall",
      "title": "re.findall()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает список всех совпадений паттерна в строке. Если есть группы — список кортежей.",
        "en": "Returns the list of every match of the pattern in the string. If the pattern has groups, it is a list of tuples."
      },
      "body": {
        "ru": "Главная ловушка — группы: если в паттерне ровно одна группа, вернутся не целые совпадения, а только её содержимое; когда скобки нужны лишь для группировки, бери незахватывающие (?:...). Совпадения ищутся непересекающиеся, слева направо, и весь список строится сразу — на большом тексте лучше re.finditer: он отдаёт Match-объекты лениво и знает позиции найденного.",
        "en": "The classic trap is groups: with exactly one group in the pattern you get that group's contents rather than the whole matches, so use the non-capturing (?:...) when the parentheses are only for grouping. Matches are non-overlapping and scanned left to right, and the entire list is built at once — for large texts prefer re.finditer, which yields Match objects lazily and carries their positions."
      },
      "syntax": "re.findall(pattern, string, flags=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.findall",
      "version": "",
      "section": "Модуль re",
      "subcat": "поиск",
      "color_group": "module",
      "aliases": [
        "найти все совпадения",
        "список всех вхождений по шаблону",
        "вытащить все числа из строки"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "re.findall(r'\\d+', 'a1 b22 c333') # → ['1', '22', '333']",
        "re.findall(r'[aeiou]', 'hello world') # → ['e', 'o', 'o']",
        "re.findall(r'(\\w+)=(\\w+)', 'x=1 y=2') # → [('x', '1'), ('y', '2')]",
        "re.findall(r'cat|dog', 'cat and dog') # → ['cat', 'dog']"
      ],
      "related": [
        "re.finditer",
        "re.search",
        "группы-в-паттернах"
      ],
      "related_errors": []
    },
    {
      "id": "re.finditer",
      "title": "re.finditer()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает итератор объектов Match для всех совпадений. Экономит память при большом тексте.",
        "en": "Returns an iterator of Match objects for every match. It saves memory on large texts."
      },
      "body": {
        "ru": "Итератор ленивый и одноразовый: второй проход по нему уже ничего не даст, так что при повторном использовании сразу оборачивайте результат в list(). В отличие от findall(), который отдаёт строки (а при наличии групп — кортежи групп), здесь приходят полноценные объекты Match со start(), end() и group(n); совпадения идут слева направо и никогда не перекрываются.",
        "en": "The iterator is lazy and single-pass: a second loop over it sees nothing, so wrap it in list() if you need the results more than once. Unlike findall(), which hands back strings (or tuples of groups when the pattern has groups), finditer() yields real Match objects with start(), end() and group(n); matches come left to right and never overlap."
      },
      "syntax": "re.finditer(pattern, string, flags=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.finditer",
      "version": "",
      "section": "Модуль re",
      "subcat": "поиск",
      "color_group": "module",
      "aliases": [
        "итератор по совпадениям",
        "перебрать совпадения в цикле",
        "позиции всех совпадений"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "for m in re.finditer(r'\\d+', 'a1 b22 c333'):",
        "print(m.group(), m.start()) # → 1 1, 22 3, 333 6",
        "matches = list(re.finditer(r'[A-Z]', 'Hello World'))",
        "len(matches) # → 2",
        "matches[0].group() # → 'H'"
      ],
      "related": [
        "re.findall",
        "re.search",
        "re.group"
      ],
      "related_errors": []
    },
    {
      "id": "re.fullmatch",
      "title": "re.fullmatch()",
      "kind": "function",
      "summary": {
        "ru": "Проверяет совпадение паттерна со всей строкой целиком. Возвращает Match или None.",
        "en": "Checks whether the pattern matches the whole string. Returns a Match or None."
      },
      "body": {
        "ru": "Когда вопрос звучит «подходит ли строка целиком», берите именно fullmatch: match смотрит только на начало и на строку вида 123abc с шаблоном для цифр спокойно даст совпадение. Приделать якорь $ вручную — не эквивалент: он допускает завершающий перевод строки, поэтому строка с переносом на конце такую проверку пройдёт, а fullmatch её отвергнет.",
        "en": "When the question is whether the entire string fits the pattern, reach for fullmatch(): match() anchors only at the start, so a digits pattern happily accepts something like 123abc. Bolting on a $ by hand is not the same thing — $ also matches just before a trailing newline, so a string ending in a line break slips through while fullmatch() rejects it."
      },
      "syntax": "re.fullmatch(pattern, string, flags=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.fullmatch",
      "version": "3.4",
      "section": "Модуль re",
      "subcat": "поиск",
      "color_group": "module",
      "aliases": [
        "совпадение всей строки целиком",
        "проверить строку на соответствие шаблону",
        "валидация строки по шаблону"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "re.fullmatch(r'\\d+', '12345') # → <Match '12345'>",
        "re.fullmatch(r'\\d+', '123abc') # → None",
        "re.fullmatch(r'[a-z]+', 'hello') # → <Match 'hello'>",
        "re.fullmatch(r'[a-z]+', 'Hello') # → None",
        "re.fullmatch(r'[a-z]+', 'Hello', re.IGNORECASE) # → <Match 'Hello'>"
      ],
      "related": [
        "re.match",
        "re.search"
      ],
      "related_errors": []
    },
    {
      "id": "re.group",
      "title": "re.group()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает текст одной захваченной группы объекта Match. group(0) (или group()) — всё совпадение целиком, group(n) — n-я группа.",
        "en": "Return the text of a single captured group of a Match object: group(0) (or group()) is the whole match, group(n) is the n-th group."
      },
      "body": {
        "ru": "Самая частая ошибка случается раньше самого вызова: re.match и re.search при неудаче возвращают None, и тогда .group() падает с AttributeError: 'NoneType' object has no attribute 'group' — результат поиска нужно сначала проверить. Группы нумеруются по порядку открывающих скобок, вложенные считаются наравне с внешними; если группа не участвовала в совпадении (например, стояла под знаком ?), group(n) вернёт None, а не пустую строку.",
        "en": "The usual failure happens before the call itself: re.match and re.search return None when nothing matches, so .group() then raises AttributeError: 'NoneType' object has no attribute 'group' — check the result first. Groups are numbered by their opening parentheses, nested ones counted alongside the outer ones, and a group that did not participate in the match (say, one guarded by ?) gives None rather than an empty string."
      },
      "syntax": "m.group(n=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.Match.group",
      "version": "",
      "section": "Модуль re",
      "subcat": "группы",
      "color_group": "module",
      "aliases": [
        "получить текст совпадения",
        "достать найденную подстроку",
        "текст группы регулярки"
      ],
      "keywords": [
        "re.group",
        "group"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "m = re.match(r'(\\w+)@(\\w+)', 'user@host')",
        "m.group() # → 'user@host'",
        "m.group(1) # → 'user'",
        "m.group(2) # → 'host'"
      ],
      "related": [
        "re.groups",
        "re.groupdict"
      ],
      "related_errors": [
        "AttributeError",
        "IndexError"
      ]
    },
    {
      "id": "re.groupdict",
      "title": "re.groupdict()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает словарь ИМЕНОВАННЫХ групп объекта Match — тех, что заданы через (?P<имя>...). Безымянные группы в словарь не попадают.",
        "en": "Return a dict of the named groups of a Match object — those declared with (?P<name>...); unnamed groups are not included."
      },
      "body": {
        "ru": "Имена переживают правки, которые ломают номера: вставили группу в середину длинного паттерна — все индексы съехали, а ключи словаря остались прежними. Если именованных групп в паттерне нет, вернётся пустой словарь, а не None и не ошибка; необязательная именованная группа, которая не сработала, всё равно попадёт в словарь — со значением default. Имена обязаны быть валидными идентификаторами и уникальными внутри паттерна, иначе он не скомпилируется.",
        "en": "Names survive edits that numbers do not: inserting a group in the middle of a long pattern shifts every index, but the dict keys stay put. A pattern with no named groups gives an empty dict rather than None or an error, and an optional named group that never participated still appears, carrying the default value. Group names must be valid identifiers and unique within the pattern, or it fails to compile."
      },
      "syntax": "m.groupdict(default=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.Match.groupdict",
      "version": "",
      "section": "Модуль re",
      "subcat": "группы",
      "color_group": "module",
      "aliases": [
        "словарь именованных групп",
        "получить группы по именам"
      ],
      "keywords": [
        "re.groupdict",
        "groupdict"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "m = re.match(r'(?P<y>\\d{4})-(?P<m>\\d{2})', '2024-01')",
        "m.groupdict() # → {'y': '2024', 'm': '01'}",
        "m.group('y') # → '2024' (по имени)"
      ],
      "related": [
        "re.group",
        "re.groups"
      ],
      "related_errors": [
        "AttributeError"
      ]
    },
    {
      "id": "re.groups",
      "title": "re.groups()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает кортеж ВСЕХ захваченных групп объекта Match (без группы 0). Незахваченным группам подставляется default (по умолчанию None).",
        "en": "Return a tuple of all captured groups of a Match object (group 0 excluded); groups that did not participate get default (None by default)."
      },
      "body": {
        "ru": "Группа 0 — всё совпадение целиком — в кортеж намеренно не входит, за ней надо идти в m.group(0). Необязательная группа, которая не сработала, даёт None, и он взрывается ровно там, где вы склеиваете строки или зовёте int(): либо проверяйте, либо передайте groups('') и получите пустые строки. Если групп в паттерне нет вовсе, вернётся пустой кортеж, а не None.",
        "en": "Group 0 — the whole match — is deliberately left out; ask m.group(0) for it. A group that did not participate comes back as None, which blows up exactly where you concatenate strings or call int(), so either check it or pass groups('') to get empty strings instead. A pattern with no groups yields an empty tuple, not None."
      },
      "syntax": "m.groups(default=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.Match.groups",
      "version": "",
      "section": "Модуль re",
      "subcat": "группы",
      "color_group": "module",
      "aliases": [
        "кортеж всех групп",
        "получить все захваченные группы",
        "все скобки регулярки сразу"
      ],
      "keywords": [
        "re.groups",
        "groups"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "m = re.match(r'(\\w+)@(\\w+)', 'user@host')",
        "m.groups() # → ('user', 'host')",
        "m2 = re.match(r'(a)(b)?', 'a')",
        "m2.groups() # → ('a', None)",
        "m2.groups(default='-') # → ('a', '-')"
      ],
      "related": [
        "re.group",
        "re.groupdict"
      ],
      "related_errors": [
        "AttributeError"
      ]
    },
    {
      "id": "re.match",
      "title": "re.match()",
      "kind": "function",
      "summary": {
        "ru": "Проверяет совпадение паттерна с начала строки. Возвращает объект Match или None.",
        "en": "Checks whether the pattern matches at the start of the string. Returns a Match object or None."
      },
      "body": {
        "ru": "Название сбивает с толку: match не «сверяет строку с шаблоном», а лишь пробует приложить шаблон к нулевой позиции, поэтому совпадение в середине строки он не увидит — это работа search. Конец строки он тоже не проверяет, так что для валидации целиком нужен fullmatch; флаг MULTILINE здесь не спасает — он меняет смысл ^, а не точку старта match.",
        "en": "The name misleads: match() does not test the string against the pattern, it only tries the pattern at position 0, so a match in the middle is invisible to it — that is search()'s job. It says nothing about the end of the string either, so use fullmatch() for validation; MULTILINE does not help, since it changes what ^ means, not where match() starts looking."
      },
      "syntax": "re.match(pattern, string, flags=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.match",
      "version": "",
      "section": "Модуль re",
      "subcat": "поиск",
      "color_group": "module",
      "aliases": [
        "совпадение с начала строки",
        "проверить начало строки шаблоном"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "m = re.match(r'\\d+', '123abc') # → <Match '123'>",
        "m.group() # → '123'",
        "re.match(r'\\d+', 'abc123') # → None",
        "m2 = re.match(r'[a-z]+', 'hello world') # → <Match 'hello'>",
        "m2.end() # → 5"
      ],
      "related": [
        "re.search",
        "re.fullmatch",
        "re.group"
      ],
      "related_errors": []
    },
    {
      "id": "re.purge",
      "title": "re.purge",
      "kind": "function",
      "summary": {
        "ru": "Очищает внутренний кеш скомпилированных регулярных выражений.",
        "en": "Clear the internal cache of compiled regular expressions."
      },
      "body": {
        "ru": "В прикладном коде вызывать почти никогда не нужно: кеш ограничен по размеру и сам вытесняет старые записи. Реальные поводы — замеры скорости компиляции и профилирование памяти, когда прогретый кеш искажает цифры. Уже полученные объекты re.compile продолжают работать: purge выбрасывает внутреннюю таблицу модуля, а не ваши паттерны.",
        "en": "You almost never need this in application code: the cache is bounded and evicts old entries on its own. The honest use cases are benchmarking compilation speed or profiling memory, where a warm cache skews the numbers. Pattern objects you already hold keep working — purge drops the module's internal table, not your compiled patterns."
      },
      "syntax": "re.purge()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.purge",
      "version": "",
      "section": "Модуль re",
      "subcat": "кеш",
      "color_group": "module",
      "aliases": [],
      "keywords": [],
      "tags": [
        "re"
      ],
      "examples": [
        "import re",
        "print(re.purge())   # → None",
        "pat = re.compile(r'\\d+')",
        "print(pat.findall('a1b22c'))   # → ['1', '22']",
        "print(re.purge())   # → None",
        "print(pat.findall('a1b22c'))   # → ['1', '22']"
      ],
      "related": [
        "re.compile",
        "re.Pattern"
      ],
      "related_errors": []
    },
    {
      "id": "re.search",
      "title": "re.search()",
      "kind": "function",
      "summary": {
        "ru": "Ищет первое совпадение паттерна в любом месте строки. Возвращает Match или None.",
        "en": "Finds the first match of the pattern anywhere in the string. Returns a Match or None."
      },
      "body": {
        "ru": "Самая частая ошибка — сразу дёрнуть .group() у результата: если совпадения нет, вернётся None и вы получите AttributeError вместо внятного сообщения, поэтому результат сначала проверяют (удобно моржовым оператором прямо в if). Search находит только первое вхождение — когда нужны все, берите findall или finditer.",
        "en": "The classic bug is calling .group() straight on the result: with no match you get None and an AttributeError instead of a clear message, so check the result first (a walrus operator inside an if reads well here). search() stops at the first hit — when you need every occurrence, use findall() or finditer()."
      },
      "syntax": "re.search(pattern, string, flags=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.search",
      "version": "",
      "section": "Модуль re",
      "subcat": "поиск",
      "color_group": "module",
      "aliases": [
        "найти первое совпадение",
        "поиск подстроки по шаблону",
        "есть ли совпадение в строке"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "m = re.search(r'\\d+', 'abc123def') # → <Match '123'>",
        "m.group() # → '123'",
        "m.start() # → 3",
        "re.search(r'cat', 'the cat sat') # → <Match 'cat'>",
        "re.search(r'dog', 'the cat sat') # → None"
      ],
      "related": [
        "re.match",
        "re.findall",
        "re.group"
      ],
      "related_errors": []
    },
    {
      "id": "re.split",
      "title": "re.split()",
      "kind": "function",
      "summary": {
        "ru": "Разбивает строку по совпадениям с паттерном. Группы в паттерне включаются в результат.",
        "en": "Splits a string at the matches of the pattern. Groups in the pattern are included in the result."
      },
      "body": {
        "ru": "maxsplit=0 означает «без ограничений», а не «не разбивать»: лимит задаётся положительным числом. И, в отличие от str.split() без аргументов, re.split ничего не подчищает — разделитель в начале или конце строки оставит пустые строки по краям результата, а с Python 3.7 шаблон, способный совпасть с пустотой, режет строку в каждой позиции вместо прежнего ValueError.",
        "en": "maxsplit=0 means unlimited, not \"no splits\" — pass a positive number to cap it. And unlike str.split() with no arguments, re.split() tidies nothing up: a separator at either end leaves empty strings at the edges of the result, and since Python 3.7 a pattern that can match the empty string splits at every position instead of raising ValueError."
      },
      "syntax": "re.split(pattern, string, maxsplit=0, flags=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.split",
      "version": "",
      "section": "Модуль re",
      "subcat": "разбивка",
      "color_group": "module",
      "aliases": [
        "разбить строку по шаблону",
        "разделить строку по нескольким разделителям",
        "разбиение по регулярному выражению"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "re.split(r'\\s+', 'one two  three') # → ['one', 'two', 'three']",
        "re.split(r'[,;]', 'a,b;c') # → ['a', 'b', 'c']",
        "re.split(r'(\\d+)', 'a1b2c') # → ['a', '1', 'b', '2', 'c']",
        "re.split(r',', 'a,b,c', maxsplit=1) # → ['a', 'b,c']"
      ],
      "related": [
        "str.split",
        "re.findall",
        "группы-в-паттернах"
      ],
      "related_errors": []
    },
    {
      "id": "re.sub",
      "title": "re.sub",
      "kind": "function",
      "summary": {
        "ru": "Заменяет все вхождения шаблона в строке на replacement (строку или функцию).",
        "en": "Replace all matches of the pattern in the string with a replacement."
      },
      "body": {
        "ru": "Если ни шаблон, ни замена не используют регулярных конструкций, берите str.replace — быстрее и не придётся экранировать точку, плюс или скобки. В строке замены обратный слеш живёт по своим правилам: ссылка на группу пишется \\1 или \\g<name>, а '\\n' без префикса r превратится в настоящий перевод строки. Исходная строка при этом не меняется — результат надо присвоить.",
        "en": "When neither the pattern nor the replacement uses regex syntax, reach for str.replace: it is faster and you avoid escaping dots, pluses and brackets. Backslashes in the replacement follow their own rules — a group reference is \\1 or \\g<name>, and '\\n' without the r prefix becomes a real newline. The original string is never modified in place, so you have to assign the result."
      },
      "syntax": "re.sub(pattern, repl, string, count=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.sub",
      "version": "",
      "section": "Модуль re",
      "subcat": "замена",
      "color_group": "module",
      "aliases": [
        "замена по регулярному выражению",
        "заменить текст по шаблону",
        "подстановка вместо совпадений"
      ],
      "keywords": [],
      "tags": [
        "re"
      ],
      "examples": [
        "import re",
        "print(re.sub(r'\\d+', '#', 'a1b22c'))   # → a#b#c",
        "print(re.sub(r'\\d', '#', 'a1b2c3', count=2))   # → a#b#c3",
        "print(re.sub(r'(\\w+)@(\\w+)', r'\\2:\\1', 'user@host'))   # → host:user",
        "print(re.sub(r'\\d+', lambda m: str(int(m.group()) * 2), 'a2 b10'))   # → a4 b20",
        "print(re.sub(r'z+', '-', 'abc'))   # → abc"
      ],
      "related": [
        "re.subn",
        "str.replace",
        "re-named-group"
      ],
      "related_errors": []
    },
    {
      "id": "re.subn",
      "title": "re.subn",
      "kind": "function",
      "summary": {
        "ru": "Как sub(), но возвращает пару (новая_строка, число_замен).",
        "en": "Like sub(), but return a tuple (new_string, number_of_substitutions)."
      },
      "body": {
        "ru": "Берите его, когда важно знать, сработала ли замена: сравнивать результат с исходной строкой ненадёжно — текст замены может совпасть с найденным, и строка останется на вид прежней. Второе значение — количество выполненных замен, поэтому с count=N оно никогда не превысит N, даже если совпадений в строке больше.",
        "en": "Use it when you need to know whether anything was replaced: comparing the result against the original is unreliable, since the replacement text may equal what was matched and leave the string looking untouched. The second value counts substitutions actually performed, so with count=N it never exceeds N even when the string holds more matches."
      },
      "syntax": "re.subn(pattern, repl, string, count=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.subn",
      "version": "",
      "section": "Модуль re",
      "subcat": "замена",
      "color_group": "module",
      "aliases": [
        "замена с подсчётом",
        "сколько замен сделано",
        "число замен в строке"
      ],
      "keywords": [],
      "tags": [
        "re"
      ],
      "examples": [
        "import re",
        "print(re.subn(r'\\d', '#', 'a1b2'))   # → ('a#b#', 2)",
        "print(re.subn(r'\\d', '#', 'abc'))   # → ('abc', 0)",
        "print(re.subn(r'\\d', '#', 'a1b2c3', count=2))   # → ('a#b#c3', 2)",
        "text, n = re.subn(r'\\s+', ' ', 'a  b   c')",
        "print(n)   # → 2"
      ],
      "related": [
        "re.sub",
        "str.count",
        "str.replace"
      ],
      "related_errors": []
    },
    {
      "id": "группы-в-паттернах",
      "title": "Группы в паттернах",
      "kind": "term",
      "summary": {
        "ru": "Расширенные конструкции групп: именованные, не захватывающие, lookahead и lookbehind.",
        "en": "The extended group constructs: named groups, non-capturing groups, lookahead and lookbehind."
      },
      "body": {
        "ru": "Захватывающие скобки меняют то, что отдаёт findall: с одной группой он вернёт её содержимое вместо полного совпадения, поэтому, если скобки нужны только чтобы навесить повторитель, берите (?:...). Просмотры нулевой ширины — они проверяют соседний текст, но не съедают его, и в результат он не попадает. В модуле re lookbehind обязан быть фиксированной длины, так что (?<=ab|cde) просто не скомпилируется.",
        "en": "Capturing parentheses change what findall returns: with one group it hands back that group's text instead of the whole match, so use (?:...) when the parentheses exist only to attach a repetition. Lookarounds are zero-width — they test the neighbouring text without consuming it, and it never shows up in the result. In re a lookbehind must be fixed width, so (?<=ab|cde) will not compile at all."
      },
      "syntax": "(?P<name>...) именованная\n(?:...) не захватывающая\n(?=...) lookahead\n(?!...) negative lookahead\n(?<=...) lookbehind",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#regular-expression-syntax",
      "version": "",
      "section": "Модуль re",
      "subcat": "группы",
      "color_group": "module",
      "aliases": [
        "скобки в регулярном выражении",
        "просмотр вперёд и назад",
        "незахватывающая группа"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "re.search(r'(?P<d>\\d+)', 'x42').group('d') # → '42'",
        "re.findall(r'(?:\\d+)', '1 2') # → ['1', '2'] без захвата",
        "re.findall(r'\\d+(?= руб)', '5 руб 3 кг') # → ['5']",
        "re.findall(r'\\d+(?! руб)', '5 руб 3 кг') # → ['3']",
        "re.findall(r'(?<=@)\\w+', 'user@host') # → ['host']"
      ],
      "related": [
        "re-named-group",
        "re.groups",
        "re.group",
        "спецсимволы-паттернов"
      ],
      "related_errors": []
    },
    {
      "id": "спецсимволы-паттернов",
      "title": "Спецсимволы паттернов",
      "kind": "term",
      "summary": {
        "ru": "Специальные символы регулярных выражений: метасимволы, классы, квантификаторы, якоря.",
        "en": "The special characters of regular expressions: metacharacters, character classes, quantifiers and anchors."
      },
      "body": {
        "ru": "Паттерн всегда пиши raw-строкой: без префикса r обратные слеши разбирает сам Python, и \\b станет символом забоя ещё до того, как его увидит re. Внутри квадратных скобок метасимволы почти все теряют силу — точка и плюс там обычные символы, зато ^ в начале класса означает отрицание, а дефис между символами — диапазон. Квантификаторы по умолчанию жадные: .* тянется до последнего возможного совпадения, ленивый вариант — .*?.",
        "en": "Always write patterns as raw strings: without the r prefix Python processes the backslashes itself, so \\b turns into a backspace character before re ever sees it. Inside square brackets most metacharacters go literal — a dot or a plus is just that character — while ^ at the start negates the class and a hyphen between two characters means a range. Quantifiers are greedy by default: .* stretches to the last possible match, and .*? is the lazy form."
      },
      "syntax": ". ^ $ * + ? {n,m} [] | () \\ \\d \\w \\s",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#regular-expression-syntax",
      "version": "",
      "section": "Модуль re",
      "subcat": "паттерны",
      "color_group": "module",
      "aliases": [
        "метасимволы регулярных выражений",
        "квантификаторы",
        "классы символов в шаблоне"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "re.findall(r'.', 'ab') # → ['a', 'b']  . любой символ",
        "re.findall(r'\\d+', 'x12y3') # → ['12', '3']  \\d цифры",
        "re.findall(r'\\w+', 'hi_2!') # → ['hi_2']  \\w буквы/цифры/_",
        "re.findall(r'a{2,3}', 'a aa aaa') # → ['aa', 'aaa']",
        "re.findall(r'cat|dog', 'cat and dog') # → ['cat', 'dog']"
      ],
      "related": [
        "группы-в-паттернах",
        "re.escape",
        "флаги-re"
      ],
      "related_errors": []
    },
    {
      "id": "флаги-re",
      "title": "Флаги re",
      "kind": "term",
      "summary": {
        "ru": "Флаги изменяют поведение паттерна: регистр, многострочность, совпадение точки с \\n, подробный синтаксис.",
        "en": "The flags change how a pattern behaves: case sensitivity, multi-line mode, whether the dot matches \\n, and verbose syntax."
      },
      "body": {
        "ru": "Флаги не перекрывают друг друга: re.M меняет смысл только ^ и $, а чтобы точка начала ловить перевод строки, нужен именно re.S. Вторая ловушка — позиция аргумента: у re.sub() и re.split() перед flags идёт count, так что флаг, переданный без имени параметра, уйдёт в счётчик; а у скомпилированного паттерна флаги уже зашиты, и p.match(s, re.I) поймёт re.I как начальную позицию поиска.",
        "en": "The flags do not overlap: re.M only changes what ^ and $ mean, and it takes re.S to make the dot match a newline. The other trap is argument position — re.sub() and re.split() take count before flags, so a flag passed without its keyword lands in the counter, and on a compiled pattern the flags are already baked in, so p.match(s, re.I) reads re.I as a starting offset."
      },
      "syntax": "re.IGNORECASE (re.I), re.MULTILINE (re.M), re.DOTALL (re.S), re.VERBOSE (re.X)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/re.html#re.IGNORECASE",
      "version": "",
      "section": "Модуль re",
      "subcat": "флаги",
      "color_group": "module",
      "aliases": [
        "игнорировать регистр при поиске",
        "многострочный режим поиска",
        "точка совпадает с переводом строки"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import re",
        "re.findall(r'hello', 'Hello HELLO', re.I) # → ['Hello', 'HELLO']",
        "re.findall(r'^\\d', '1\\n2\\n3', re.M) # → ['1', '2', '3']",
        "re.match(r'.+', 'a\\nb', re.S).group() # → 'a\\nb'",
        "pat = re.compile(r'''",
        "\\d+   # цифры",
        "[a-z] # буква",
        "''', re.X)",
        "pat.findall('1a 2b 3') # → ['1a', '2b']"
      ],
      "related": [
        "re.RegexFlag",
        "re.compile",
        "спецсимволы-паттернов"
      ],
      "related_errors": []
    },
    {
      "id": "sqlite3.DataError",
      "title": "sqlite3.DataError",
      "kind": "exception",
      "summary": {
        "ru": "Подкласс sqlite3.DatabaseError: ошибка из-за самих обрабатываемых данных — числовое значение вне диапазона, слишком длинная строка.",
        "en": "A subclass of sqlite3.DatabaseError raised for errors caused by the processed data itself — numeric values out of range, strings that are too long."
      },
      "body": {
        "ru": "Сам модуль sqlite3 это исключение не возбуждает — класс существует ради совместимости с DB-API 2.0, и поднять его может разве что код поверх sqlite3, например пользовательская функция, заметившая обрезание данных. Слишком большое целое даёт OverflowError, а не DataError, так что в except стоит перечислять sqlite3.Error или DatabaseError, а не этот класс.",
        "en": "The sqlite3 module itself never raises this one — the class exists for DB-API 2.0 compliance, and only code built on top of sqlite3 (say, a user-defined function that notices truncated data) would raise it. An integer too large for SQLite gives OverflowError, not DataError, so catch sqlite3.Error or DatabaseError rather than this class."
      },
      "syntax": "sqlite3.DataError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sqlite3.html#sqlite3.DataError",
      "version": "",
      "section": "Модуль sqlite3",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "ошибка данных базы",
        "некорректные данные в базе",
        "значение вне диапазона в базе"
      ],
      "keywords": [
        "sqlite3.DataError",
        "DataError"
      ],
      "tags": [
        "sqlite3"
      ],
      "examples": [
        "import sqlite3",
        "print(issubclass(sqlite3.DataError, sqlite3.DatabaseError))   # → True",
        "print(issubclass(sqlite3.DataError, sqlite3.Error))   # → True",
        "print(sqlite3.DataError.__name__)   # → DataError",
        "print(isinstance(sqlite3.DataError('слишком длинная строка'), sqlite3.DatabaseError))   # → True",
        "print(sqlite3.DataError.__mro__[1].__name__)   # → DatabaseError"
      ],
      "related": [
        "valueerror",
        "overflowerror",
        "shutil.error",
        "иерархия-исключений"
      ],
      "related_errors": []
    },
    {
      "id": "sqlite3.DatabaseError",
      "title": "sqlite3.DatabaseError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибки, связанные с самой базой данных; базовый класс специализированных исключений sqlite3 (OperationalError, IntegrityError и др.), подкласс sqlite3.Error.",
        "en": "Raised for errors related to the database; base class for the specialised sqlite3 errors (OperationalError, IntegrityError, ...), subclass of sqlite3.Error."
      },
      "body": {
        "ru": "Ловить обычно стоит либо sqlite3.Error — он покрывает всё, включая InterfaceError, то есть ошибки использования самого модуля, а не базы, — либо DatabaseError, когда важны именно проблемы на стороне БД. Разбирать конкретную причину удобнее не по классу исключения, а по атрибутам sqlite_errorcode и sqlite_errorname: они есть у всех исключений sqlite3 начиная с Python 3.11.",
        "en": "In practice you catch either sqlite3.Error, which also covers InterfaceError (misuse of the module rather than a database problem), or DatabaseError when only database-side failures matter. To tell the exact cause apart, lean on the sqlite_errorcode and sqlite_errorname attributes instead of the class hierarchy — every sqlite3 exception carries them since Python 3.11."
      },
      "syntax": "raise sqlite3.DatabaseError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sqlite3.html#sqlite3.DatabaseError",
      "version": "",
      "section": "Модуль sqlite3",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "ошибка базы данных",
        "проблема с базой данных"
      ],
      "keywords": [
        "sqlite3.DatabaseError",
        "DatabaseError"
      ],
      "tags": [
        "sqlite3"
      ],
      "examples": [
        "import sqlite3",
        "print(issubclass(sqlite3.IntegrityError, sqlite3.DatabaseError))  # → True",
        "try:",
        "    sqlite3.connect(':memory:').execute('SELECT bad syntax here')",
        "except sqlite3.DatabaseError as e:",
        "    print(type(e).__name__)   # → OperationalError"
      ],
      "related": [
        "exception",
        "try-except"
      ],
      "related_errors": []
    },
    {
      "id": "sqlite3.Error",
      "title": "sqlite3.Error",
      "kind": "exception",
      "summary": {
        "ru": "Базовый класс всех исключений модуля sqlite3 (подкласс Exception): один except sqlite3.Error перехватывает любую ошибку работы с БД.",
        "en": "Base class of the other exceptions in the sqlite3 module (subclass of Exception); catch all database errors with a single except."
      },
      "body": {
        "ru": "Ловить sqlite3.Error разумно на границе программы — там, где вы решаете, что показать пользователю; внутри логики лучше конкретика, иначе опечатка в SQL (OperationalError) и нарушение UNIQUE (IntegrityError) обработаются одинаково. Начиная с Python 3.11 у пойманного объекта есть sqlite_errorcode и sqlite_errorname — код и имя ошибки прямо от движка, разбирать текст сообщения не нужно. И помните: перехват исключения сам по себе не откатывает транзакцию, нужен con.rollback() или блок with con (он коммитит или откатывает, но соединение не закрывает).",
        "en": "Catch sqlite3.Error at the edge of your program, where you decide what to tell the user; deeper down prefer the specific class, otherwise a typo in SQL (OperationalError) and a UNIQUE violation (IntegrityError) end up in the same handler. Since Python 3.11 the caught object carries sqlite_errorcode and sqlite_errorname, so you can branch on the engine's own error code instead of parsing the message text. Catching the exception does not undo anything by itself: call con.rollback(), or use with con, which commits or rolls back the transaction but does not close the connection."
      },
      "syntax": "raise sqlite3.Error",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sqlite3.html#sqlite3.Error",
      "version": "",
      "section": "Модуль sqlite3",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "ошибка при работе с базой данных",
        "поймать любую ошибку субд",
        "базовое исключение бд"
      ],
      "keywords": [
        "sqlite3.Error"
      ],
      "tags": [
        "sqlite3"
      ],
      "examples": [
        "import sqlite3",
        "print(issubclass(sqlite3.DatabaseError, sqlite3.Error))  # → True",
        "try:",
        "    sqlite3.connect(':memory:').execute('SELECT * FROM nope')",
        "except sqlite3.Error as e:",
        "    print(type(e).__name__)   # → OperationalError"
      ],
      "related": [
        "exception",
        "try-except",
        "raise"
      ],
      "related_errors": []
    },
    {
      "id": "sqlite3.IntegrityError",
      "title": "sqlite3.IntegrityError",
      "kind": "exception",
      "summary": {
        "ru": "Нарушена целостность данных в БД: UNIQUE, NOT NULL, проверка внешнего ключа (подкласс sqlite3.DatabaseError).",
        "en": "Raised when the relational integrity of the database is affected, e.g. a foreign key check fails (subclass of sqlite3.DatabaseError)."
      },
      "body": {
        "ru": "Это ошибка данных, а не кода: повтор того же запроса даст тот же результат, поэтому либо чините входные данные, либо заранее пишите INSERT OR IGNORE / ON CONFLICT. Отдельная ловушка SQLite: внешние ключи по умолчанию не проверяются — без PRAGMA foreign_keys = ON, выполненного для каждого соединения, битая ссылка запишется молча, и никакого IntegrityError вы не увидите.",
        "en": "This is a data problem, not a code problem: retrying the same statement produces the same failure, so either fix the incoming data or write INSERT OR IGNORE / ON CONFLICT up front. A SQLite-specific trap: foreign keys are not enforced by default — unless you run PRAGMA foreign_keys = ON on every single connection, a dangling reference is stored silently and IntegrityError never fires."
      },
      "syntax": "raise sqlite3.IntegrityError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sqlite3.html#sqlite3.IntegrityError",
      "version": "",
      "section": "Модуль sqlite3",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "нарушение целостности данных",
        "нарушение ограничения уникальности",
        "дубликат уникального ключа"
      ],
      "keywords": [
        "sqlite3.IntegrityError",
        "IntegrityError"
      ],
      "tags": [
        "sqlite3"
      ],
      "examples": [
        "import sqlite3",
        "con = sqlite3.connect(':memory:')",
        "con.executescript('CREATE TABLE t(id INTEGER PRIMARY KEY); INSERT INTO t VALUES (1);')",
        "try:",
        "    con.execute('INSERT INTO t VALUES (1)')",
        "except sqlite3.IntegrityError as e:",
        "    print(type(e).__name__)   # → IntegrityError"
      ],
      "related": [
        "exception",
        "try-except"
      ],
      "related_errors": []
    },
    {
      "id": "sqlite3.InterfaceError",
      "title": "sqlite3.InterfaceError",
      "kind": "exception",
      "summary": {
        "ru": "Ошибка неправильного использования низкоуровневого C API SQLite; подкласс sqlite3.Error, обычно указывает на баг в самом модуле sqlite3, а не в базе данных.",
        "en": "Raised for misuse of the low-level SQLite C API; a subclass of sqlite3.Error, it usually indicates a bug in the sqlite3 module rather than in the database."
      },
      "body": {
        "ru": "Практический вывод один: except sqlite3.DatabaseError его НЕ поймает — InterfaceError стоит на дереве рядом с DatabaseError, общий предок у них только sqlite3.Error, поэтому «ловим вообще всё про БД» пишется именно через Error. Отдельной обработки это исключение обычно не заслуживает: проблема не в вашем SQL и не в данных, а в неправильном обращении к C API, так что переписывать запрос бесполезно.",
        "en": "The one practical consequence: except sqlite3.DatabaseError will not catch it — InterfaceError sits beside DatabaseError in the hierarchy and their only common ancestor is sqlite3.Error, so a true catch-all for database problems must name Error. It rarely deserves a handler of its own: the fault is in how the C API was driven, not in your SQL or your data, so rewriting the query gets you nowhere."
      },
      "syntax": "sqlite3.InterfaceError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sqlite3.html#sqlite3.InterfaceError",
      "version": "",
      "section": "Модуль sqlite3",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "ошибка интерфейса базы данных",
        "неправильное использование драйвера бд",
        "ошибка низкоуровневого интерфейса субд"
      ],
      "keywords": [
        "sqlite3.InterfaceError",
        "InterfaceError"
      ],
      "tags": [
        "sqlite3"
      ],
      "examples": [
        "import sqlite3",
        "print(issubclass(sqlite3.InterfaceError, sqlite3.Error))          # → True",
        "print(issubclass(sqlite3.InterfaceError, sqlite3.DatabaseError))  # → False",
        "print(sqlite3.InterfaceError.__name__)                            # → InterfaceError",
        "try: raise sqlite3.InterfaceError('мисюз C API')",
        "except sqlite3.Error as e: print(type(e).__name__)                # → InterfaceError"
      ],
      "related": [
        "exception",
        "try-except",
        "argparse.argumenterror",
        "json.decoder.jsondecodeerror"
      ],
      "related_errors": []
    },
    {
      "id": "sqlite3.InternalError",
      "title": "sqlite3.InternalError",
      "kind": "exception",
      "summary": {
        "ru": "Внутренняя ошибка самого движка SQLite; подкласс sqlite3.DatabaseError, может указывать на проблему в используемой библиотеке SQLite.",
        "en": "Raised when SQLite encounters an internal error; a subclass of sqlite3.DatabaseError, it may indicate a problem with the runtime SQLite library."
      },
      "body": {
        "ru": "В нормальном коде вы это не встретите: движок сообщает, что обнаружил внутреннюю несогласованность, а за этим обычно стоит повреждённый файл базы или сломанная сборка библиотеки SQLite, а не ваш запрос. Писать под него отдельный except смысла нет — если исключение всё-таки прилетело, проверяйте сам файл (PRAGMA integrity_check) и версию движка, а не переписывайте SQL.",
        "en": "You will not meet this in ordinary code: the engine is reporting that it found an inconsistency inside itself, which in practice means a corrupted database file or a broken SQLite build rather than a bad query. A dedicated except branch buys nothing — if it ever fires, inspect the file (PRAGMA integrity_check) and the engine build instead of rewriting your SQL."
      },
      "syntax": "sqlite3.InternalError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sqlite3.html#sqlite3.InternalError",
      "version": "",
      "section": "Модуль sqlite3",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "внутренняя ошибка движка базы данных",
        "внутренний сбой субд",
        "внутренняя ошибка библиотеки бд"
      ],
      "keywords": [
        "sqlite3.InternalError",
        "InternalError"
      ],
      "tags": [
        "sqlite3"
      ],
      "examples": [
        "import sqlite3",
        "print(issubclass(sqlite3.InternalError, sqlite3.DatabaseError))   # → True",
        "print(issubclass(sqlite3.InternalError, sqlite3.Error))           # → True",
        "print(sqlite3.InternalError.__mro__[1].__name__)                  # → DatabaseError",
        "try: raise sqlite3.InternalError('сбой внутри SQLite')",
        "except sqlite3.DatabaseError as e: print(type(e).__name__)        # → InternalError"
      ],
      "related": [
        "exception",
        "try-except",
        "runtimeerror",
        "decimal.decimalexception"
      ],
      "related_errors": []
    },
    {
      "id": "sqlite3.NotSupportedError",
      "title": "sqlite3.NotSupportedError",
      "kind": "exception",
      "summary": {
        "ru": "Метод или возможность API не поддерживается используемой библиотекой SQLite (например, deterministic=True в create_function); подкласс sqlite3.DatabaseError.",
        "en": "Raised when a method or database API is not supported by the underlying SQLite library (e.g. deterministic=True in create_function); a subclass of sqlite3.DatabaseError."
      },
      "body": {
        "ru": "Главное здесь: доступность фичи определяется версией библиотеки SQLite, с которой собран интерпретатор, а не версией Python — один и тот же код спокойно работает у вас и падает на другой машине или в CI. Перед использованием свежих возможностей сверяйтесь с sqlite3.sqlite_version (это версия самого движка) и обрабатывайте исключение как «возможность недоступна», предусмотрев запасной путь, а не как ошибку данных.",
        "en": "The key point: whether a feature exists depends on the SQLite library your interpreter was built against, not on the Python version — the same code can run fine on your machine and fail on a colleague's or in CI. Check sqlite3.sqlite_version (the engine's own version string) before relying on newer features, and treat this exception as \"capability missing, take the fallback path\" rather than as a data error."
      },
      "syntax": "sqlite3.NotSupportedError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sqlite3.html#sqlite3.NotSupportedError",
      "version": "",
      "section": "Модуль sqlite3",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "операция не поддерживается базой данных",
        "неподдерживаемая возможность субд",
        "метод не поддерживается драйвером бд"
      ],
      "keywords": [
        "sqlite3.NotSupportedError",
        "NotSupportedError"
      ],
      "tags": [
        "sqlite3"
      ],
      "examples": [
        "import sqlite3",
        "print(issubclass(sqlite3.NotSupportedError, sqlite3.DatabaseError))  # → True",
        "print(sqlite3.NotSupportedError.__name__)                            # → NotSupportedError",
        "print(sqlite3.NotSupportedError.__mro__[2].__name__)                 # → Error",
        "try: raise sqlite3.NotSupportedError('deterministic=True не поддержан')",
        "except sqlite3.Error as e: print(type(e).__name__)                   # → NotSupportedError"
      ],
      "related": [
        "exception",
        "try-except",
        "notimplementederror",
        "json.decoder.jsondecodeerror"
      ],
      "related_errors": []
    },
    {
      "id": "sqlite3.OperationalError",
      "title": "sqlite3.OperationalError",
      "kind": "exception",
      "summary": {
        "ru": "Исключение sqlite3 для ошибок самой базы данных, не зависящих от программиста: файл БД не найден, нет такой таблицы, база заблокирована. Подкласс sqlite3.DatabaseError.",
        "en": "sqlite3 exception raised for errors related to the database's operation and not necessarily under the control of the programmer: database path not found, no such table, locked database. Subclass of sqlite3.DatabaseError."
      },
      "body": {
        "ru": "Самый частый источник в учебных задачах — «no such table»: sqlite3.connect() не ругается на несуществующий файл, а молча создаёт пустую базу, поэтому опечатка в пути оборачивается отсутствующей таблицей на первом же запросе. Второй сюжет — «database is locked»: другое соединение держит незакрытую транзакцию, и sqlite3 ждёт освобождения ограниченное время (по умолчанию 5 секунд, параметр timeout у connect), после чего бросает исключение. Отделять его от ProgrammingError стоит именно поэтому: тут чинят окружение — путь, схему, чужую транзакцию, а не текст своего кода.",
        "en": "In student code this almost always shows up as \"no such table\": sqlite3.connect() does not complain about a missing file, it quietly creates an empty database, so a typo in the path becomes a missing table on the first query. The other classic is \"database is locked\", raised when another connection still holds an open transaction; sqlite3 waits only for the connect() timeout (5 seconds by default) before giving up. Keep it separate from ProgrammingError: this one means you fix the environment — path, schema, someone else's uncommitted transaction — not your own statement."
      },
      "syntax": "sqlite3.OperationalError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sqlite3.html#sqlite3.OperationalError",
      "version": "",
      "section": "Модуль sqlite3",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "ошибка работы с базой данных",
        "нет такой таблицы",
        "база данных заблокирована"
      ],
      "keywords": [
        "sqlite3.OperationalError",
        "OperationalError"
      ],
      "tags": [
        "sqlite3"
      ],
      "examples": [
        "import sqlite3",
        "print(issubclass(sqlite3.OperationalError, sqlite3.DatabaseError))  # → True",
        "print(issubclass(sqlite3.OperationalError, sqlite3.Error))  # → True",
        "con = sqlite3.connect(':memory:')",
        "try: con.execute('SELECT * FROM missing')",
        "except sqlite3.OperationalError as e: print(e)  # → no such table: missing"
      ],
      "related": [
        "exception",
        "try-except",
        "raise"
      ],
      "related_errors": []
    },
    {
      "id": "sqlite3.ProgrammingError",
      "title": "sqlite3.ProgrammingError",
      "kind": "exception",
      "summary": {
        "ru": "Исключение sqlite3 для ошибок программиста в работе с API: неверное число параметров запроса, обращение к уже закрытому Connection. Подкласс sqlite3.DatabaseError.",
        "en": "sqlite3 exception raised for sqlite3 API programming errors: wrong number of bindings supplied to a query, or operating on a closed Connection. Subclass of sqlite3.DatabaseError."
      },
      "body": {
        "ru": "Это всегда ошибка кода: параметров передали не столько, сколько знаков ? в запросе (классика — отдать голую строку вместо кортежа, тогда она разбирается посимвольно), обратились к Connection или Cursor уже после close(), либо тронули соединение из другого потока, чем создали (по умолчанию check_same_thread=True). Повторять запрос бессмысленно — в отличие от OperationalError, само по себе оно не пройдёт; перехват уместен разве что чтобы показать понятное сообщение, а чинить надо вызов.",
        "en": "This one is always your bug: the number of bound parameters does not match the ? placeholders (a favourite mistake is passing a bare string instead of a one-element tuple, so it gets bound character by character), you touched a Connection or Cursor after close(), or you used a connection from a thread other than the one that created it (check_same_thread defaults to True). Retrying is pointless — unlike OperationalError it will never succeed on its own; catch it only to print something readable, then fix the call itself."
      },
      "syntax": "sqlite3.ProgrammingError",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sqlite3.html#sqlite3.ProgrammingError",
      "version": "",
      "section": "Модуль sqlite3",
      "subcat": "исключения",
      "color_group": "module",
      "aliases": [
        "неверное число параметров запроса",
        "обращение к закрытой базе данных",
        "ошибка в коде работы с базой"
      ],
      "keywords": [
        "sqlite3.ProgrammingError",
        "ProgrammingError"
      ],
      "tags": [
        "sqlite3"
      ],
      "examples": [
        "import sqlite3",
        "print(issubclass(sqlite3.ProgrammingError, sqlite3.DatabaseError))  # → True",
        "print(issubclass(sqlite3.ProgrammingError, sqlite3.OperationalError))  # → False",
        "con = sqlite3.connect(':memory:'); con.execute('CREATE TABLE t(a, b)')",
        "try: con.execute('INSERT INTO t VALUES (?, ?)', (1,))",
        "except sqlite3.ProgrammingError as e: print(type(e).__name__)  # → ProgrammingError"
      ],
      "related": [
        "exception",
        "try-except",
        "typeerror"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.NormalDist",
      "title": "statistics.NormalDist",
      "kind": "term",
      "summary": {
        "ru": "Нормальное распределение (μ, σ): методы pdf/cdf, выборка samples, арифметика.",
        "en": "A normal distribution (μ, σ) with pdf/cdf/samples and arithmetic."
      },
      "body": {
        "ru": "Объект неизменяемый, а арифметика вроде nd * 2 или nd + 5 возвращает новое распределение; сложение двух NormalDist предполагает независимость слагаемых — для связанных величин результат будет неверен. cdf(x) отвечает, какая доля значений не больше x, обратную задачу (найти границу по заданной вероятности) решает inv_cdf, а построить распределение прямо по данным умеет from_samples. Класс появился в Python 3.8.",
        "en": "Instances are immutable, and arithmetic such as nd * 2 or nd + 5 returns a new distribution; adding two NormalDist objects assumes the variables are independent, so the result is wrong for correlated ones. cdf(x) gives the share of values at or below x, the reverse question — the cutoff for a given probability — is inv_cdf, and from_samples builds a distribution straight from data. Added in Python 3.8."
      },
      "syntax": "statistics.NormalDist(mu=0.0, sigma=1.0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.NormalDist",
      "version": "",
      "section": "Модуль statistics",
      "subcat": "распределение",
      "color_group": "op",
      "aliases": [
        "нормальное распределение",
        "гауссово распределение",
        "плотность вероятности"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "nd = statistics.NormalDist(0, 1)",
        "print(nd.mean, nd.stdev)   # → 0.0 1.0",
        "print(round(nd.cdf(0), 2))   # → 0.5"
      ],
      "related": [
        "statistics.stdev",
        "statistics.mean",
        "random.gauss"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.correlation",
      "title": "statistics.correlation",
      "kind": "function",
      "summary": {
        "ru": "Коэффициент корреляции Пирсона (или Спирмена) двух наборов, от −1 до 1 (Python 3.10+).",
        "en": "Pearson's (or Spearman's) correlation coefficient of two datasets, −1..1 (3.10+)."
      },
      "body": {
        "ru": "Ловится только линейная связь: y = x² на симметричном наборе даёт корреляцию около нуля при полной зависимости, а высокое значение само по себе ничего не говорит о причине. Наборы обязаны быть одной длины и не короче двух значений, а постоянная последовательность (нулевой разброс) приводит к StatisticsError. Коэффициент Спирмена включается через method='ranked' — этот параметр есть только с 3.12.",
        "en": "It detects straight-line relationships only: y = x² on a symmetric dataset scores near zero despite being perfectly determined, and a high score says nothing about cause. Both inputs must have the same length and at least two values, and a constant sequence (zero spread) raises StatisticsError. Spearman's version requires method='ranked', which exists only from 3.12."
      },
      "syntax": "statistics.correlation(x, y)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.correlation",
      "version": "3.10",
      "section": "Модуль statistics",
      "subcat": "связь",
      "color_group": "op",
      "aliases": [
        "коэффициент корреляции",
        "корреляция Пирсона",
        "связь двух наборов данных"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.correlation([1, 2, 3], [2, 4, 6]))   # → 1.0",
        "print(statistics.correlation([1, 2, 3], [6, 4, 2]))   # → -1.0",
        "print(statistics.correlation([1, 2, 3, 4], [1, 3, 2, 4]))   # → 0.8",
        "print(statistics.correlation([1, 2, 3], [10, 30, 20], method='ranked'))   # → 0.5",
        "print(statistics.correlation([1, 1, 1], [1, 2, 3]))   # → StatisticsError"
      ],
      "related": [
        "statistics.covariance",
        "statistics.linear_regression"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.covariance",
      "title": "statistics.covariance",
      "kind": "function",
      "summary": {
        "ru": "Выборочная ковариация двух наборов данных (Python 3.10+).",
        "en": "Sample covariance of two datasets (3.10+)."
      },
      "body": {
        "ru": "Значение несёт единицы обоих наборов: переведёте метры в сантиметры — ковариация вырастет в сто раз, поэтому силу связи по ней не сравнивают (для этого correlation), а полезен в основном знак. Ноль означает лишь отсутствие линейной связи, а не независимость; входы должны быть равной длины и не короче двух значений.",
        "en": "The value carries the units of both inputs — switch metres to centimetres and it grows a hundredfold — so it is useless for comparing the strength of relationships; correlation is the tool for that, while covariance is mostly read for its sign. Zero means no linear relationship rather than independence, and both inputs must be the same length with at least two values."
      },
      "syntax": "statistics.covariance(x, y)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.covariance",
      "version": "3.10",
      "section": "Модуль statistics",
      "subcat": "связь",
      "color_group": "op",
      "aliases": [
        "ковариация",
        "выборочная ковариация",
        "совместная изменчивость двух наборов"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.covariance([1, 2, 3], [2, 4, 6]))   # → 2.0",
        "print(statistics.covariance([1, 2, 3], [6, 4, 2]))   # → -2.0",
        "print(statistics.covariance([1, 2, 3, 4], [1, 2, 2, 1]))   # → 0.0",
        "print(statistics.covariance([1, 2, 3], [1, 2, 3]) == statistics.variance([1, 2, 3]))   # → True",
        "print(statistics.covariance([1, 2, 3], [1, 2]))   # → StatisticsError"
      ],
      "related": [
        "statistics.correlation",
        "statistics.linear_regression",
        "statistics.variance"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.fmean",
      "title": "statistics.fmean",
      "kind": "function",
      "summary": {
        "ru": "Быстрое среднее арифметическое как float (быстрее mean, Python 3.8+).",
        "en": "Fast floating-point arithmetic mean (faster than mean; 3.8+)."
      },
      "body": {
        "ru": "За скорость платят точностью: fmean сразу приводит все значения к float, поэтому Decimal и Fraction на входе теряют свою точную арифметику, тогда как mean считает без потерь и сохраняет тип. Сама функция есть с 3.8, а вот именованный weights для взвешенного среднего добавили только в 3.11 — на более ранних версиях его нет.",
        "en": "Speed is paid for in precision: fmean converts everything to float, so Decimal and Fraction inputs lose their exact arithmetic, while mean stays exact and keeps the type. The function itself dates to 3.8, but the weights argument only appeared in 3.11 — earlier versions do not accept it."
      },
      "syntax": "statistics.fmean(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.fmean",
      "version": "3.8",
      "section": "Модуль statistics",
      "subcat": "среднее",
      "color_group": "op",
      "aliases": [
        "быстрое среднее",
        "среднее с плавающей точкой"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.fmean([1, 2, 3, 4]))   # → 2.5",
        "print(statistics.fmean([4, 5, 3, 5]))   # → 4.25",
        "print(statistics.fmean([80, 90], weights=[1, 3]))   # → 87.5",
        "print(statistics.mean([1, 2, 3]), statistics.fmean([1, 2, 3]))   # → 2 2.0",
        "print(statistics.fmean([]))   # → StatisticsError"
      ],
      "related": [
        "statistics.mean",
        "statistics.geometric_mean",
        "statistics.median"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.geometric_mean",
      "title": "statistics.geometric_mean",
      "kind": "function",
      "summary": {
        "ru": "Среднее геометрическое (корень n-й степени из произведения).",
        "en": "Geometric mean (the n-th root of the product)."
      },
      "body": {
        "ru": "Берут его там, где величины перемножаются, а не складываются: средний коэффициент роста за несколько периодов — это геометрическое среднее, арифметическое его завышает. Ноль или отрицательное значение в данных дают не ноль в ответе, а StatisticsError. Результат — float, посчитанный без гарантий точности до последнего разряда, так что сравнивать его через == не стоит.",
        "en": "Reach for it when values multiply rather than add: an average growth factor over several periods is a geometric mean, and the arithmetic mean overstates it. A zero or a negative value raises StatisticsError instead of returning zero. The result is a float computed with no exactness guarantees, so don't compare it with ==."
      },
      "syntax": "statistics.geometric_mean(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.geometric_mean",
      "version": "3.8",
      "section": "Модуль statistics",
      "subcat": "среднее",
      "color_group": "op",
      "aliases": [
        "среднее геометрическое",
        "корень n-й степени из произведения",
        "средний темп роста"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(round(statistics.geometric_mean([1, 4, 16]), 1))   # → 4.0",
        "print(round(statistics.geometric_mean([3, 12]), 1))   # → 6.0",
        "print(round(statistics.geometric_mean([1.10, 1.21, 1.331]), 2))   # → 1.21",
        "print(statistics.mean([1, 100]), round(statistics.geometric_mean([1, 100]), 1))   # → 50.5 10.0",
        "print(statistics.geometric_mean([-1, 4]))   # → StatisticsError"
      ],
      "related": [
        "statistics.harmonic_mean",
        "statistics.mean",
        "statistics.fmean"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.harmonic_mean",
      "title": "statistics.harmonic_mean",
      "kind": "function",
      "summary": {
        "ru": "Среднее гармоническое (обратное к среднему обратных); полезно для скоростей/темпов.",
        "en": "Harmonic mean (reciprocal of the mean of reciprocals)."
      },
      "body": {
        "ru": "Уместно, когда усредняются скорости или темпы при одинаковом объёме работы: половину пути на 60 и половину на 30 — это в среднем 40, а не 45. Один ноль среди данных обнуляет весь ответ, причём алгоритм на нём выходит досрочно и остальные значения даже не проверяет; отрицательное значение — StatisticsError. Параметр weights появился в 3.10.",
        "en": "It fits averaging rates over equal amounts of work: half the distance at 60 and half at 30 averages 40, not 45. A single zero makes the whole result zero — the algorithm short-circuits there and never validates the remaining values — while a negative value raises StatisticsError. The weights argument arrived in 3.10."
      },
      "syntax": "statistics.harmonic_mean(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.harmonic_mean",
      "version": "3.6",
      "section": "Модуль statistics",
      "subcat": "среднее",
      "color_group": "op",
      "aliases": [
        "среднее гармоническое",
        "средняя скорость по участкам"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.harmonic_mean([1, 2, 4]))   # → 1.7142857142857142",
        "print(statistics.harmonic_mean([10, 40]))   # → 16.0",
        "print(statistics.mean([60, 30]), statistics.harmonic_mean([60, 30]))   # → 45 40.0",
        "print(statistics.harmonic_mean([40, 60], weights=[5, 30]))   # → 56.0",
        "print(statistics.harmonic_mean([1, -2]))   # → StatisticsError"
      ],
      "related": [
        "statistics.geometric_mean",
        "statistics.mean",
        "statistics.fmean"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.linear_regression",
      "title": "statistics.linear_regression",
      "kind": "function",
      "summary": {
        "ru": "Линейная регрессия: наклон и точка пересечения (slope, intercept) методом наименьших квадратов (Python 3.10+).",
        "en": "Linear regression: slope and intercept by least squares (3.10+)."
      },
      "body": {
        "ru": "Подгонка несимметрична: x считается независимой переменной, y — зависимой, и от перестановки аргументов прямая получается другая. О том, насколько хорошо линия описывает данные, функция не говорит ничего — силу связи оценивает statistics.correlation. Точек нужно минимум две, и значения x не должны быть все одинаковыми, иначе StatisticsError.",
        "en": "The fit is not symmetric: x is the independent variable and y the dependent one, so swapping the arguments yields a different line. The result says nothing about how well the line describes the data — use statistics.correlation for the strength of the relationship. You need at least two points and non-constant x, otherwise StatisticsError."
      },
      "syntax": "statistics.linear_regression(x, y)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.linear_regression",
      "version": "3.10",
      "section": "Модуль statistics",
      "subcat": "связь",
      "color_group": "op",
      "aliases": [
        "линейная регрессия",
        "метод наименьших квадратов",
        "подобрать прямую по точкам"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "r = statistics.linear_regression([1, 2, 3], [2, 4, 6])",
        "print(round(r.slope, 1), round(r.intercept, 1))   # → 2.0 0.0",
        "slope, intercept = statistics.linear_regression([1, 2, 3, 4], [3, 5, 6, 9])",
        "print(round(slope, 2), round(intercept, 2))   # → 1.9 1.0",
        "print(round(slope * 5 + intercept, 2))   # → 10.5",
        "print(round(statistics.linear_regression([1, 2], [3, 5], proportional=True).slope, 2))   # → 2.6",
        "print(statistics.linear_regression([1, 1, 1], [2, 4, 6]))   # → StatisticsError"
      ],
      "related": [
        "statistics.correlation",
        "statistics.covariance"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.mean",
      "title": "statistics.mean",
      "kind": "function",
      "summary": {
        "ru": "Среднее арифметическое данных.",
        "en": "Arithmetic mean (average) of the data."
      },
      "body": {
        "ru": "mean считает точно (внутри — дроби) и сохраняет тип данных: список целых может вернуть int, Fraction останется Fraction, Decimal — Decimal; если нужен просто float и скорость, берите fmean. Среднее тянут выбросы — одно аномальное значение сдвигает его заметно, тогда как median почти не шелохнётся. На пустых данных — StatisticsError, а не 0.",
        "en": "mean is exact (fractions under the hood) and preserves the input type: a list of ints can come back as an int, Fractions stay Fractions, Decimals stay Decimals; when you just want a float and speed, use fmean. It is also dragged by outliers — a single freak value shifts it noticeably while median barely moves. Empty data raises StatisticsError rather than returning 0."
      },
      "syntax": "statistics.mean(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.mean",
      "version": "",
      "section": "Модуль statistics",
      "subcat": "среднее",
      "color_group": "op",
      "aliases": [
        "среднее арифметическое",
        "среднее значение списка",
        "посчитать среднее"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.mean([1, 2, 3, 4]))   # → 2.5",
        "print(statistics.mean([1, 2, 3]))   # → 2",
        "print(round(statistics.mean([4, 5, 3, 5, 4]), 2))   # → 4.2",
        "print(statistics.mean([1, 2, 3, 100]), statistics.median([1, 2, 3, 100]))   # → 26.5 2.5",
        "print(statistics.mean([])) # → StatisticsError"
      ],
      "related": [
        "statistics.median",
        "statistics.fmean",
        "statistics.mode",
        "statistics.stdev"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.median",
      "title": "statistics.median",
      "kind": "function",
      "summary": {
        "ru": "Медиана — среднее значение отсортированных данных (для чётной длины — среднее двух центральных).",
        "en": "Median — the middle value of sorted data."
      },
      "body": {
        "ru": "Предварительно сортировать данные не нужно — median() сортирует сама, отсюда и цена O(n log n). При чётной длине она усредняет два центральных значения, поэтому результат бывает дробным и может вообще отсутствовать в исходных данных; если нужен реальный элемент выборки, берите median_low() или median_high(). Пустая последовательность даёт StatisticsError, а не ноль и не None.",
        "en": "You do not need to sort the data first — median() sorts internally, which is where its O(n log n) cost comes from. On an even-length sample it averages the two middle values, so the answer can be fractional and need not occur in the data at all; when you need a value that genuinely exists in the sample, reach for median_low() or median_high(). Empty input raises StatisticsError rather than returning zero or None."
      },
      "syntax": "statistics.median(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.median",
      "version": "",
      "section": "Модуль statistics",
      "subcat": "медиана",
      "color_group": "op",
      "aliases": [
        "медиана",
        "серединное значение",
        "центральное значение выборки"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.median([1, 2, 3, 4]))   # → 2.5",
        "print(statistics.median([1, 3, 5]))      # → 3",
        "print(statistics.median([5, 1, 4, 2]))        # → 3.0",
        "print(statistics.median([1, 2, 3, 4, 100]))   # → 3",
        "print(statistics.median([]))                  # → StatisticsError"
      ],
      "related": [
        "statistics.median_low",
        "statistics.median_high",
        "statistics.median_grouped",
        "statistics.mean"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.median_grouped",
      "title": "statistics.median_grouped",
      "kind": "function",
      "summary": {
        "ru": "Медиана сгруппированных (непрерывных) данных с интерполяцией по интервалу.",
        "en": "Median of grouped continuous data, interpolated within the interval."
      },
      "body": {
        "ru": "Считает не медиану ваших чисел, а оценку медианы непрерывной величины: каждое значение трактуется как середина интервала шириной interval, внутри которого делается линейная интерполяция. Поэтому она уместна только для данных, округлённых до сетки (баллы, возраст в целых годах, показания прибора с фиксированным шагом), и interval обязан совпадать с реальным шагом — иначе число выйдет правдоподобным, но бессмысленным. Для сырых измерений нужна обычная median().",
        "en": "It does not return the median of your numbers but an estimate of the median of an underlying continuous variable: every value is treated as the midpoint of a class of width interval, and the answer is interpolated inside that class. So use it only for data already rounded onto a grid (scores, age in whole years, instrument readings with a fixed step), and make interval match that step — a wrong interval yields a plausible-looking but meaningless number. For raw measurements plain median() is the right call."
      },
      "syntax": "statistics.median_grouped(data, interval=1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.median_grouped",
      "version": "",
      "section": "Модуль statistics",
      "subcat": "медиана",
      "color_group": "op",
      "aliases": [
        "медиана сгруппированных данных",
        "медиана интервального ряда",
        "медиана с интерполяцией"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.median_grouped([1, 2, 2, 3, 4, 4, 4]))   # → 3.0",
        "print(statistics.median_grouped([1, 1, 2, 2, 2, 2]))      # → 1.75",
        "print(statistics.median([1, 1, 2, 2, 2, 2]))              # → 2.0",
        "print(statistics.median_grouped([10, 20, 30, 40], interval=10))   # → 25.0",
        "print(statistics.median_grouped([]))                      # → StatisticsError"
      ],
      "related": [
        "statistics.median",
        "statistics.median_low",
        "statistics.median_high"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.median_high",
      "title": "statistics.median_high",
      "kind": "function",
      "summary": {
        "ru": "Верхняя медиана: при чётной длине возвращает большее из двух центральных.",
        "en": "High median: for even-length data returns the larger of the two middle values."
      },
      "body": {
        "ru": "В отличие от median(), она ничего не усредняет и всегда возвращает элемент, реально присутствующий в данных, с исходным типом — поэтому работает и там, где среднее не имеет смысла: строки, даты, любые сравнимые объекты. На нечётной длине median(), median_low() и median_high() дают одно и то же, разница проявляется только на чётной.",
        "en": "Unlike median() it never averages anything: it returns an element that actually occurs in the data, with its original type, so it also works where averaging makes no sense — strings, dates, any comparable objects. For odd-length data median(), median_low() and median_high() all agree; the difference only appears when the sample size is even."
      },
      "syntax": "statistics.median_high(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.median_high",
      "version": "",
      "section": "Модуль statistics",
      "subcat": "медиана",
      "color_group": "op",
      "aliases": [
        "верхняя медиана",
        "большее из двух центральных значений"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.median_high([1, 2, 3, 4]))   # → 3",
        "print(statistics.median_high([1, 3, 5]))      # → 3",
        "print(statistics.median_high([5, 1, 4, 2]))   # → 4",
        "print(statistics.median_high(['a', 'b', 'c', 'd']))   # → c",
        "print(statistics.median_low([1, 2, 3, 4]), statistics.median_high([1, 2, 3, 4]))   # → 2 3",
        "print(statistics.median_high([]))             # → StatisticsError"
      ],
      "related": [
        "statistics.median_low",
        "statistics.median"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.median_low",
      "title": "statistics.median_low",
      "kind": "function",
      "summary": {
        "ru": "Нижняя медиана: при чётной длине возвращает меньшее из двух центральных (всегда элемент данных).",
        "en": "Low median: for even-length data returns the smaller of the two middle values."
      },
      "body": {
        "ru": "Главное отличие от median: обычная медиана при чётной длине усредняет два центральных значения и может выдать число, которого в данных нет (для [1, 2, 3, 4] это 2.5), а median_low всегда возвращает реальный элемент — это то, что нужно для порядковых данных, которые нельзя складывать и делить. Сортировать вход заранее не требуется, функция делает это сама, а на пустой последовательности бросает StatisticsError, а не возвращает None.",
        "en": "The point of median_low is that it never invents a value: plain median averages the two middle items on even-length data and can return something absent from the sample (2.5 for [1, 2, 3, 4]), while median_low hands back an actual data point — exactly what ordinal data that cannot be averaged needs. You do not have to pre-sort the input, it sorts internally, and empty data raises StatisticsError rather than returning None."
      },
      "syntax": "statistics.median_low(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.median_low",
      "version": "",
      "section": "Модуль statistics",
      "subcat": "медиана",
      "color_group": "op",
      "aliases": [
        "нижняя медиана",
        "меньшее из двух центральных значений"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.median_low([1, 2, 3, 4]))   # → 2",
        "print(statistics.median_low([1, 3, 5]))      # → 3",
        "print(statistics.median_low([5, 1, 4, 2]))   # → 2",
        "print(statistics.median_low(['a', 'b', 'c', 'd']))   # → b",
        "print(statistics.median([1, 2, 3, 4]), statistics.median_low([1, 2, 3, 4]))   # → 2.5 2",
        "print(statistics.median_low([]))             # → StatisticsError"
      ],
      "related": [
        "statistics.median_high",
        "statistics.median"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.mode",
      "title": "statistics.mode",
      "kind": "function",
      "summary": {
        "ru": "Мода — самое частое значение (первое при равенстве). Работает и с нечисловыми данными.",
        "en": "Mode — the most common value (the first one on a tie)."
      },
      "body": {
        "ru": "С Python 3.8 mode на данных с несколькими одинаково частыми значениями больше не падает с StatisticsError, а молча берёт первое из них по порядку появления — так что ничью вы никак не заметите; если она для вас значима, используйте multimode. Пустая последовательность по-прежнему даёт StatisticsError, а элементы должны быть хешируемыми: внутри считает Counter.",
        "en": "Since Python 3.8 mode no longer raises StatisticsError when several values tie for most frequent — it quietly returns the first one in order of appearance, so a tie passes unnoticed; reach for multimode when the tie matters. Empty data still raises StatisticsError, and elements must be hashable because counting goes through Counter."
      },
      "syntax": "statistics.mode(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.mode",
      "version": "",
      "section": "Модуль statistics",
      "subcat": "мода",
      "color_group": "op",
      "aliases": [
        "самое частое значение",
        "наиболее часто встречающийся элемент",
        "мода выборки"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.mode([1, 1, 2, 3]))       # → 1",
        "print(statistics.mode(['a', 'b', 'a']))   # → a",
        "print(statistics.mode('banana'))           # → a",
        "print(statistics.mode([1, 1, 2, 2]))       # → 1",
        "print(statistics.multimode([1, 1, 2, 2]))  # → [1, 2]",
        "print(statistics.mode([]))                 # → StatisticsError"
      ],
      "related": [
        "statistics.multimode",
        "collections.counter",
        "statistics.median"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.multimode",
      "title": "statistics.multimode",
      "kind": "function",
      "summary": {
        "ru": "Список всех мод (значений с максимальной частотой); пустой список для пустых данных.",
        "en": "A list of all modes (values with the highest frequency)."
      },
      "body": {
        "ru": "Появилась в 3.8 как безопасный вариант mode: не спорит с ничьими и на пустом входе отдаёт [], тогда как mode в этом случае бросает StatisticsError. Учтите обратную сторону: если все значения встречаются одинаково часто, в списке окажутся все до единого — это сигнал «моды нет», а не набор осмысленных мод; порядок в списке — по первому появлению значения.",
        "en": "Added in 3.8 as the tie-safe counterpart of mode: ties are fine and empty input yields [] instead of the StatisticsError that mode raises. Watch the flip side — when every value occurs equally often you get all of them back, which means \"no mode at all\" rather than a list of meaningful ones; the order follows first appearance."
      },
      "syntax": "statistics.multimode(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.multimode",
      "version": "3.8",
      "section": "Модуль statistics",
      "subcat": "мода",
      "color_group": "op",
      "aliases": [
        "все самые частые значения",
        "несколько мод",
        "список наиболее частых элементов"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.multimode([1, 1, 2, 2, 3]))   # → [1, 2]",
        "print(statistics.multimode('aabbbcc'))  # → ['b']",
        "print(statistics.multimode([1, 2, 3]))  # → [1, 2, 3]",
        "print(statistics.multimode([]))  # → []",
        "print(statistics.mode([1, 1, 2, 2, 3]))  # → 1"
      ],
      "related": [
        "statistics.mode",
        "collections.counter"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.pstdev",
      "title": "statistics.pstdev",
      "kind": "function",
      "summary": {
        "ru": "Стандартное отклонение генеральной совокупности (на n степенях свободы).",
        "en": "Population standard deviation (with n degrees of freedom)."
      },
      "body": {
        "ru": "Буква p значит population — делитель n. Если ваши числа лишь выборка из большей совокупности, брать надо stdev с делителем n-1, иначе разброс систематически занижается; pstdev честен только когда данные и есть вся совокупность. Аргумент mu — не произвольная точка отсчёта, а заранее известное истинное среднее: подставив что попало, вы получите корень из среднего квадрата отклонения от этой точки, а вовсе не стандартное отклонение.",
        "en": "The p stands for population: the divisor is n. If your numbers are only a sample drawn from something larger, you want stdev with its n-1 correction, otherwise the spread comes out systematically too small; pstdev is honest only when the data is the whole population. The mu argument is not an arbitrary reference point but a mean you already know to be true — feed it anything else and you get the root mean square deviation about that point, not a standard deviation."
      },
      "syntax": "statistics.pstdev(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.pstdev",
      "version": "",
      "section": "Модуль statistics",
      "subcat": "разброс",
      "color_group": "op",
      "aliases": [
        "стандартное отклонение генеральной совокупности",
        "отклонение по всем данным"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.pstdev([2, 4, 4, 4, 5, 5, 7, 9]))   # → 2.0",
        "print(statistics.pstdev([2, 4, 4, 4, 5, 5, 7, 9], mu=5))  # → 2.0",
        "print(statistics.pstdev([1, 2, 3, 4, 5]))  # → 1.4142135623730951",
        "print(statistics.pstdev([5]))  # → 0.0",
        "print(statistics.stdev([2, 4, 4, 4, 5, 5, 7, 9]))  # → 2.138089935299395"
      ],
      "related": [
        "statistics.stdev",
        "statistics.pvariance"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.pvariance",
      "title": "statistics.pvariance",
      "kind": "function",
      "summary": {
        "ru": "Дисперсия генеральной совокупности (на n).",
        "en": "Population variance (with n)."
      },
      "body": {
        "ru": "Тип результата следует типу данных: Decimal и Fraction сохраняются точно, а на целых числах ответ может оказаться int, а не float — не стройте код на предположении, что тут всегда float. По смыслу это дисперсия всей совокупности (делитель n): для выборки нужна variance с n-1, а mu передавайте только когда истинное среднее действительно известно, иначе получится второй момент вокруг чужой точки.",
        "en": "The return type follows the input type: Decimal and Fraction stay exact, and integer data can give you an int rather than a float — do not assume a float comes back. And this is the population variance (divisor n): for a sample use variance with its n-1, and pass mu only when the true mean is genuinely known, otherwise you get the second moment about some unrelated point."
      },
      "syntax": "statistics.pvariance(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.pvariance",
      "version": "",
      "section": "Модуль statistics",
      "subcat": "разброс",
      "color_group": "op",
      "aliases": [
        "дисперсия генеральной совокупности",
        "дисперсия по всем данным"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.pvariance([1, 2, 3, 4, 5]))   # → 2",
        "print(statistics.pvariance([1, 2, 3, 4, 5], mu=0))  # → 11",
        "print(statistics.pvariance([2, 4, 4, 4, 5, 5, 7, 9]))  # → 4",
        "print(statistics.pvariance([5]))  # → 0",
        "print(statistics.variance([1, 2, 3, 4, 5]))  # → 2.5"
      ],
      "related": [
        "statistics.variance",
        "statistics.pstdev"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.stdev",
      "title": "statistics.stdev",
      "kind": "function",
      "summary": {
        "ru": "Выборочное стандартное отклонение (на n−1 степенях свободы).",
        "en": "Sample standard deviation (with n−1 degrees of freedom)."
      },
      "body": {
        "ru": "Деление на n−1 делает это оценкой по выборке: если данные и есть вся совокупность, нужен pstdev, иначе разброс окажется завышен. Меньше двух значений — StatisticsError, а необязательный xbar лишь избавляет от повторного счёта уже известного среднего: подставите не то число — получите бессмыслицу молча, без всякой ошибки.",
        "en": "Dividing by n−1 makes this a sample estimate; if your data is the entire population, use pstdev or you will overstate the spread. Fewer than two values raises StatisticsError, and the optional xbar only saves recomputing a mean you already know — pass anything else and the result is silently meaningless."
      },
      "syntax": "statistics.stdev(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.stdev",
      "version": "",
      "section": "Модуль statistics",
      "subcat": "разброс",
      "color_group": "op",
      "aliases": [
        "выборочное стандартное отклонение",
        "среднеквадратичное отклонение выборки",
        "разброс значений вокруг среднего"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.stdev([2, 4, 4, 4, 5, 5, 7, 9]))   # → 2.138089935299395",
        "print(statistics.stdev([1, 2, 3, 4, 5]))  # → 1.5811388300841898",
        "print(statistics.stdev([1, 2, 3, 4, 5], xbar=3))  # → 1.5811388300841898",
        "print(statistics.stdev([7, 7, 7, 7]))  # → 0.0",
        "print(statistics.stdev([5]))  # → StatisticsError",
        "print(statistics.pstdev([1, 2, 3, 4, 5]))  # → 1.4142135623730951"
      ],
      "related": [
        "statistics.variance",
        "statistics.pstdev",
        "statistics.mean"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.variance",
      "title": "statistics.variance",
      "kind": "function",
      "summary": {
        "ru": "Выборочная дисперсия (квадрат stdev, на n−1).",
        "en": "Sample variance (the square of stdev, with n−1)."
      },
      "body": {
        "ru": "Аргумент xbar принимается на веру: передадите не настоящее среднее — получите число, которое дисперсией уже не является, и никакого исключения не будет. Для полной совокупности берут pvariance с делением на n; а если разбросы нужно просто сравнить между собой, корень не нужен вовсе — variance и stdev упорядочивают наборы одинаково.",
        "en": "The xbar argument is taken on trust: hand it a value that is not the actual mean and you get a number that is no longer a variance, with no error raised. Use pvariance when the data is a whole population rather than a sample, and note that if you only need to compare spreads the square root is redundant — variance and stdev rank datasets identically."
      },
      "syntax": "statistics.variance(data)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.variance",
      "version": "",
      "section": "Модуль statistics",
      "subcat": "разброс",
      "color_group": "op",
      "aliases": [
        "выборочная дисперсия",
        "квадрат стандартного отклонения"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(statistics.variance([1, 2, 3, 4, 5]))   # → 2.5",
        "print(statistics.variance([1, 2, 3, 4, 5], xbar=3))  # → 2.5",
        "print(statistics.variance([2, 4, 4, 4, 5, 5, 7, 9]))  # → 4.571428571428571",
        "print(statistics.variance([5]))  # → StatisticsError",
        "print(statistics.pvariance([1, 2, 3, 4, 5]))  # → 2"
      ],
      "related": [
        "statistics.stdev",
        "statistics.pvariance",
        "statistics.mean"
      ],
      "related_errors": []
    },
    {
      "id": "string.Formatter",
      "title": "string.Formatter",
      "kind": "term",
      "summary": {
        "ru": "Класс-движок форматирования строк (та же логика, что str.format), допускающий переопределение поведения полей.",
        "en": "A string-formatting engine class (the same logic as str.format), with overridable behavior."
      },
      "body": {
        "ru": "Напрямую он почти не нужен: для обычного форматирования f-строки и str.format() короче и быстрее. Смысл появляется, когда вы наследуетесь и переопределяете get_value() или format_field() — например, чтобы вместо KeyError подставлять заглушку для пропущенного ключа. Ещё у класса есть parse(), который разбирает шаблон на куски (литеральный текст, имя поля, спецификация, преобразование) — удобно, когда шаблон нужно проанализировать, а не отрендерить.",
        "en": "You rarely need this class directly: for everyday formatting, f-strings and str.format() are shorter and faster. It earns its place when you subclass it and override get_value() or format_field() — say, to fall back to a placeholder instead of raising KeyError on a missing name. It also exposes parse(), which breaks a template into literal text, field name, format spec and conversion — handy when you want to inspect a template rather than render it."
      },
      "syntax": "string.Formatter().format(template, *args, **kwargs)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/string.html#string.Formatter",
      "version": "",
      "section": "Модуль string",
      "subcat": "форматирование",
      "color_group": "module",
      "aliases": [
        "класс форматирования строк",
        "своя логика подстановки полей"
      ],
      "keywords": [],
      "tags": [
        "string"
      ],
      "examples": [
        "import string",
        "f = string.Formatter()",
        "print(f.format('{0}-{1}', 'a', 'b'))   # → a-b",
        "print(f.format('{name}: {age}', name='Ann', age=7))  # → Ann: 7",
        "print(f.vformat('{0}+{1}', ('x', 'y'), {}))  # → x+y",
        "print(list(f.parse('{a}')))  # → [('', 'a', '', None)]",
        "print(f.format('{0:>5}', 'ab') == '{0:>5}'.format('ab'))  # → True"
      ],
      "related": [
        "format-метод-форматирования",
        "string.template",
        "f-строки",
        "str.format_map"
      ],
      "related_errors": []
    },
    {
      "id": "string.ascii_letters",
      "title": "string.ascii_letters",
      "kind": "term",
      "summary": {
        "ru": "Строка из всех ASCII-букв: строчных и прописных (a-z + A-Z). Удобна для генерации паролей и валидации.",
        "en": "The string of every ASCII letter, lower-case and upper-case (a-z + A-Z). Handy for generating passwords and for validation."
      },
      "body": {
        "ru": "Константа строго ASCII: кириллицы, умляутов и прочей юникод-графики в ней нет и не появится ни при какой локали, поэтому проверка «символ есть в string.ascii_letters» строже, чем c.isalpha(), которая пропускает и 'ё', и 'é'. Если генерируете пароль или токен по-настоящему, берите модуль secrets, а не random: random заточен под моделирование, и его выдача предсказуема по состоянию генератора.",
        "en": "This constant is ASCII and nothing else: Cyrillic, umlauts and other Unicode letters never appear in it, whatever the locale, so testing membership here is stricter than c.isalpha(), which happily accepts 'ё' or 'é'. For real passwords or tokens reach for the secrets module rather than random, whose output is reproducible from the generator state and is meant for simulation, not security."
      },
      "syntax": "string.ascii_letters  # 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/string.html#string.ascii_letters",
      "version": "",
      "section": "Модуль string",
      "subcat": "буквы",
      "color_group": "module",
      "aliases": [
        "все латинские буквы",
        "английский алфавит строчный и заглавный",
        "набор букв для пароля"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import string",
        "print(string.ascii_letters)  # → abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
        "print(len(string.ascii_letters))  # → 52",
        "import random",
        "pwd = ''.join(random.choices(string.ascii_letters, k=8))  # → случайный пароль 8 символов",
        "all_alpha = [c for c in 'Hello1' if c in string.ascii_letters]  # → ['H','e','l','l','o']",
        "print('a' in string.ascii_letters)  # → True"
      ],
      "related": [
        "string.ascii_lowercase",
        "string.digits",
        "string.punctuation"
      ],
      "related_errors": []
    },
    {
      "id": "string.ascii_lowercase",
      "title": "string.ascii_lowercase",
      "kind": "term",
      "summary": {
        "ru": "Строка 'abcdefghijklmnopqrstuvwxyz'. string.ascii_uppercase — 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.",
        "en": "The string 'abcdefghijklmnopqrstuvwxyz'. string.ascii_uppercase is 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'."
      },
      "body": {
        "ru": "Это обычная строка, так что позицию буквы в алфавите удобно брать как string.ascii_lowercase.index(ch) — то же самое, что ord(ch) - ord('a'), но без магических чисел; в задачах на шифр Цезаря это основной приём. Только помните, что оператор in для строк ищет подстроку, а не один символ: 'abc' даст True, а 'ac' — False, хотя обе буквы в алфавите есть.",
        "en": "It is an ordinary string, so string.ascii_lowercase.index(ch) gives a letter's alphabet position — the same as ord(ch) - ord('a') but without the magic number, which is the usual trick in Caesar-cipher exercises. Watch out that in on a string tests for a substring, not a single character: 'abc' is True while 'ac' is False even though both letters are in the alphabet."
      },
      "syntax": "string.ascii_lowercase  # 'abcdefghijklmnopqrstuvwxyz'\nstring.ascii_uppercase  # 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/string.html#string.ascii_lowercase",
      "version": "",
      "section": "Модуль string",
      "subcat": "буквы",
      "color_group": "module",
      "aliases": [
        "строчные латинские буквы",
        "прописные латинские буквы",
        "буквы алфавита по порядку"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import string",
        "print(string.ascii_lowercase)  # → abcdefghijklmnopqrstuvwxyz",
        "print(string.ascii_uppercase)  # → ABCDEFGHIJKLMNOPQRSTUVWXYZ",
        "print(len(string.ascii_lowercase))  # → 26",
        "lower_only = [c for c in 'Hello' if c in string.ascii_lowercase]  # → ['e','l','l','o']",
        "upper_only = [c for c in 'Hello' if c in string.ascii_uppercase]  # → ['H']"
      ],
      "related": [
        "string.ascii_letters",
        "string.digits",
        "str.lower"
      ],
      "related_errors": []
    },
    {
      "id": "string.capwords",
      "title": "string.capwords()",
      "kind": "function",
      "summary": {
        "ru": "Разбивает строку по пробелам, делает каждое слово с заглавной буквы, склеивает обратно. Аналог title(), но убирает лишние пробелы.",
        "en": "Splits a string on whitespace, capitalizes every word and joins it back together. Like title(), but it also collapses extra whitespace."
      },
      "body": {
        "ru": "capwords применяет к каждому слову capitalize(), а тот приводит остаток слова к нижнему регистру: 'McDonald' станет 'Mcdonald', а 'USA' — 'Usa'. От str.title() отличается тем, что режет строку по пробелам, а не по каждому не-буквенному символу, поэтому \"it's\" остаётся It's, а не It'S; но при sep=None любые пробельные символы (табы, переводы строк) схлопываются в один пробел, и исходное расположение слов не сохраняется.",
        "en": "capwords runs capitalize() on every word, and capitalize() lowercases the rest of the word: 'McDonald' comes back as 'Mcdonald' and 'USA' as 'Usa'. Unlike str.title(), it splits on whitespace rather than at every non-letter, so \"it's\" stays It's instead of becoming It'S — but with sep=None any run of whitespace (tabs, newlines included) collapses to a single space, so the original spacing is lost."
      },
      "syntax": "string.capwords(s, sep=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/string.html#string.capwords",
      "version": "",
      "section": "Модуль string",
      "subcat": "функции",
      "color_group": "module",
      "aliases": [
        "каждое слово с заглавной буквы",
        "привести слова к виду заголовка"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import string",
        "print(string.capwords('hello world'))  # → Hello World",
        "print(string.capwords('  hello   world  '))  # → Hello World",
        "print(string.capwords('it\\'s a test'))  # → It\\'s A Test",
        "print(string.capwords('one,two,three', ','))  # → One,Two,Three",
        "print(string.capwords('UPPER lower'))  # → Upper Lower"
      ],
      "related": [
        "str.title",
        "str.capitalize",
        "str.split"
      ],
      "related_errors": []
    },
    {
      "id": "string.digits",
      "title": "string.digits",
      "kind": "term",
      "summary": {
        "ru": "Строка '0123456789'. Используется для проверки цифровых символов и генерации числовых кодов.",
        "en": "The string '0123456789'. Used to test for digit characters and to generate numeric codes."
      },
      "body": {
        "ru": "Осторожно с in: это обычная строка, поэтому '12' in string.digits даёт True как подстрока, а '21' — уже False. Для проверки строки целиком берите s.isdigit() или set(s) <= set(string.digits). И учтите, что здесь только ASCII: восточноарабская цифра '٣' проходит isdigit(), но в string.digits её нет — то есть сравнение с этой константой строже, чем isdigit().",
        "en": "Watch out with in: this is a plain string, so '12' in string.digits is True (it is a substring), while '21' is False. To test a whole string use s.isdigit() or set(s) <= set(string.digits). Note also that the constant is ASCII-only: an Arabic-Indic digit like '٣' passes isdigit() but is absent from string.digits, which makes a membership test against it stricter than isdigit()."
      },
      "syntax": "string.digits  # '0123456789'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/string.html#string.digits",
      "version": "",
      "section": "Модуль string",
      "subcat": "цифры",
      "color_group": "module",
      "aliases": [
        "все цифры от нуля до девяти",
        "строка со всеми цифрами"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import string",
        "print(string.digits)  # → 0123456789",
        "print('5' in string.digits)  # → True",
        "digits_only = [c for c in 'abc123' if c in string.digits]  # → ['1','2','3']",
        "import random",
        "pin = ''.join(random.choices(string.digits, k=4))  # → '4-значный PIN'",
        "print(len(string.digits))  # → 10"
      ],
      "related": [
        "string.ascii_letters",
        "string.hexdigits",
        "str.isdigit"
      ],
      "related_errors": []
    },
    {
      "id": "string.hexdigits",
      "title": "string.hexdigits",
      "kind": "term",
      "summary": {
        "ru": "Строка '0123456789abcdefABCDEF'. string.octdigits — '01234567'. Применяются при парсинге шестнадцатеричных/восьмеричных чисел.",
        "en": "The string '0123456789abcdefABCDEF'. string.octdigits is '01234567'. Used when parsing hexadecimal and octal numbers."
      },
      "body": {
        "ru": "Для самого разбора числа константы не нужны: int(s, 16) и int(s, 8) конвертируют сами и бросают ValueError на мусоре — hexdigits/octdigits полезнее для предварительной проверки или для генерации случайного hex. В hexdigits намеренно оба регистра, так что приводить строку к нижнему перед проверкой незачем; зато int() с основанием 16 примет ещё и префикс '0x' и подчёркивания-разделители, которых в константе нет — валидация «все символы из hexdigits» такие строки отвергнет.",
        "en": "You do not need these constants to parse a number: int(s, 16) and int(s, 8) do the conversion themselves and raise ValueError on garbage, so the constants are better suited to pre-validation or to generating random hex. hexdigits deliberately holds both cases, so lowercasing before the check is pointless — but int() with base 16 additionally accepts a '0x' prefix and underscore separators, neither of which is in the constant, so an all-chars-in-hexdigits check will reject strings int() would happily read."
      },
      "syntax": "string.hexdigits  # '0123456789abcdefABCDEF'\nstring.octdigits  # '01234567'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/string.html#string.hexdigits",
      "version": "",
      "section": "Модуль string",
      "subcat": "цифры",
      "color_group": "module",
      "aliases": [
        "шестнадцатеричные цифры",
        "восьмеричные цифры",
        "допустимые символы шестнадцатеричного числа"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import string",
        "print(string.hexdigits)  # → 0123456789abcdefABCDEF",
        "print(string.octdigits)  # → 01234567",
        "is_hex = all(c in string.hexdigits for c in 'ff00AA')  # → True",
        "is_oct = all(c in string.octdigits for c in '0755')  # → True",
        "print(len(string.hexdigits))  # → 22"
      ],
      "related": [
        "string.digits",
        "hex",
        "системы-счисления"
      ],
      "related_errors": []
    },
    {
      "id": "string.printable",
      "title": "string.printable",
      "kind": "term",
      "summary": {
        "ru": "Строка всех печатаемых символов: digits + ascii_letters + punctuation + whitespace. Длина — 100 символов.",
        "en": "The string of every printable character: digits + ascii_letters + punctuation + whitespace. It is 100 characters long."
      },
      "body": {
        "ru": "Название обманывает: в константу входят перевод строки, возврат каретки, табуляция и подача страницы, поэтому фильтр «оставить только printable» переносы строк не уберёт. Плюс она ASCII-only — кириллица в неё не попадает, тогда как метод str.isprintable() смотрит по Unicode и считает 'я' печатаемым, а перевод строки — нет.",
        "en": "The name oversells it: newline, carriage return, tab and form feed are all in there, so a \"keep only printable characters\" filter built on this constant will not strip line breaks. It is also ASCII-only, so Cyrillic or accented letters fall out of it, whereas str.isprintable() judges by Unicode and calls 'я' printable but a newline not."
      },
      "syntax": "string.printable  # digits + letters + punctuation + whitespace",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/string.html#string.printable",
      "version": "",
      "section": "Модуль string",
      "subcat": "спецсимволы",
      "color_group": "module",
      "aliases": [
        "все печатаемые символы",
        "набор допустимых символов текста"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import string",
        "print(len(string.printable))  # → 100",
        "print(string.printable[:10])  # → 0123456789",
        "non_printable = [c for c in 'hello\\x00world' if c not in string.printable]  # → ['\\x00']",
        "print('A' in string.printable)  # → True",
        "print('\\x01' in string.printable)  # → False"
      ],
      "related": [
        "string.punctuation",
        "string.whitespace",
        "string.ascii_letters",
        "str.isprintable"
      ],
      "related_errors": []
    },
    {
      "id": "string.punctuation",
      "title": "string.punctuation",
      "kind": "term",
      "summary": {
        "ru": "Строка стандартных знаков пунктуации ASCII. Используется при фильтрации или проверке текста.",
        "en": "The string of the standard ASCII punctuation marks. Used when filtering or validating text."
      },
      "body": {
        "ru": "Это ровно 32 знака ASCII: типографские кавычки, длинное тире и многоточие сюда не входят, так что текст, скопированный с веб-страницы, чистится этой константой лишь наполовину. Зато подчёркивание пунктуацией считается — фильтр разрежет snake_case-имена; и для длинного текста посимвольный цикл лучше заменить на str.translate(str.maketrans('', '', string.punctuation)).",
        "en": "These are exactly 32 ASCII marks: curly quotes, em dashes and ellipsis characters are absent, so text pasted from a web page only gets half-cleaned by this constant. Underscore, on the other hand, does count as punctuation and will chop up snake_case names; for long text prefer str.translate(str.maketrans('', '', string.punctuation)) over a per-character loop."
      },
      "syntax": "string.punctuation  # '!\"#$%&\\'()*+,-./:;<=>?@[\\\\]^_`{|}~'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/string.html#string.punctuation",
      "version": "",
      "section": "Модуль string",
      "subcat": "спецсимволы",
      "color_group": "module",
      "aliases": [
        "знаки препинания",
        "убрать пунктуацию из текста"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import string",
        "print(len(string.punctuation))  # → 32",
        "no_punct = ''.join(c for c in 'Hello, world!' if c not in string.punctuation)  # → 'Hello world'",
        "print('!' in string.punctuation)  # → True",
        "has_punct = any(c in string.punctuation for c in 'abc!')  # → True",
        "only_punct = [c for c in 'Hi! :)' if c in string.punctuation]  # → ['!', ':', ')']"
      ],
      "related": [
        "string.whitespace",
        "string.printable",
        "str.translate",
        "str.maketrans"
      ],
      "related_errors": []
    },
    {
      "id": "string.template",
      "title": "string.Template",
      "kind": "term",
      "summary": {
        "ru": "Шаблон строки с подстановкой через $var или ${var}. Безопаснее % и format() для пользовательского ввода. Методы: substitute(), safe_substitute().",
        "en": "A string template with $var or ${var} substitution. Safer than % and format() for user input. Its methods: substitute(), safe_substitute()."
      },
      "body": {
        "ru": "«Безопаснее» здесь буквально про это: в шаблоне нет ни обращения к атрибутам, ни индексации, поэтому чужой шаблон не доберётся до внутренностей объекта так, как это позволяет {0.__class__.__mro__} в str.format(). С долларом аккуратнее: литеральный $ нужно удваивать ($$), иначе substitute() упадёт с ValueError на чём-нибудь вроде '$5'. А safe_substitute() не проверяет ничего: опечатку $nmae он молча оставит в тексте вместо KeyError — удобно для черновиков, опасно, когда важно заметить ошибку.",
        "en": "The \"safer\" part is literal: a Template supports neither attribute access nor indexing, so a user-supplied template cannot reach into an object's internals the way {0.__class__.__mro__} can with str.format(). Mind the dollar sign — a literal $ must be doubled as $$, otherwise substitute() raises ValueError on something like '$5'. And safe_substitute() validates nothing at all: a typo such as $nmae is left in the output as plain text instead of raising KeyError, which is convenient for drafts but hides real mistakes."
      },
      "syntax": "string.Template(template_string)\nt.substitute(mapping, **kwargs)\nt.safe_substitute(mapping, **kwargs)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/string.html#string.Template",
      "version": "",
      "section": "Модуль string",
      "subcat": "шаблоны",
      "color_group": "module",
      "aliases": [
        "шаблон строки с подстановкой",
        "подстановка переменных через знак доллара",
        "безопасная подстановка пользовательских данных"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import string",
        "t = string.Template('Hello, $name!')",
        "print(t.substitute(name='Alice'))  # → Hello, Alice!",
        "t2 = string.Template('${item} costs $price USD')",
        "print(t2.substitute(item='apple', price=1.5))  # → apple costs 1.5 USD",
        "print(t2.safe_substitute(item='apple'))  # → apple costs $price USD",
        "t3 = string.Template('Dear $who, thank you.')",
        "print(t3.substitute({'who': 'Bob'}))  # → Dear Bob, thank you."
      ],
      "related": [
        "string.Formatter",
        "format-метод-форматирования",
        "f-строки",
        "форматирование-старый-стиль"
      ],
      "related_errors": []
    },
    {
      "id": "string.whitespace",
      "title": "string.whitespace",
      "kind": "term",
      "summary": {
        "ru": "Строка из пробельных символов: пробел, таб, новая строка, возврат каретки, вертикальный таб, перевод страницы.",
        "en": "The string of the whitespace characters: space, tab, newline, carriage return, vertical tab and form feed."
      },
      "body": {
        "ru": "Здесь ровно шесть ASCII-символов, а str.strip() и str.split() без аргументов понимают пробел шире — по Unicode, вместе с неразрывным пробелом U+00A0. Поэтому строка, скопированная с веб-страницы, спокойно проходит проверку «символов из string.whitespace нет», но всё равно ведёт себя так, будто пробелы в ней остались.",
        "en": "There are exactly six ASCII characters here, while str.strip() and str.split() with no arguments use the wider Unicode notion of whitespace, non-breaking space U+00A0 included. So a string pasted from a web page can pass a \"contains nothing from string.whitespace\" check and still behave as if the spaces were never removed."
      },
      "syntax": "string.whitespace  # ' \\t\\n\\r\\x0b\\x0c'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/string.html#string.whitespace",
      "version": "",
      "section": "Модуль string",
      "subcat": "спецсимволы",
      "color_group": "module",
      "aliases": [
        "пробельные символы",
        "пробел табуляция перенос строки"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import string",
        "print(repr(string.whitespace))  # → ' \\t\\n\\r\\x0b\\x0c'",
        "print(len(string.whitespace))  # → 6",
        "no_ws = ''.join(c for c in 'a b\\tc' if c not in string.whitespace)  # → 'abc'",
        "print(' ' in string.whitespace)  # → True",
        "print('\\t' in string.whitespace)  # → True"
      ],
      "related": [
        "string.punctuation",
        "str.strip",
        "str.isspace",
        "string.printable"
      ],
      "related_errors": []
    },
    {
      "id": "subprocess-PIPE",
      "title": "subprocess.PIPE",
      "kind": "term",
      "summary": {
        "ru": "Константа для перенаправления stdin/stdout/stderr в трубопровод (pipe). Позволяет читать вывод или писать ввод программно.",
        "en": "The constant that redirects stdin/stdout/stderr into a pipe. It lets the program read the output or write the input itself."
      },
      "body": {
        "ru": "В subprocess.run() отдельно указывать PIPE обычно не нужно — capture_output=True делает ровно это для обоих потоков сразу. PIPE полезен, когда потоки надо развести: stderr=subprocess.STDOUT сливает ошибки в общий stdout, а subprocess.DEVNULL выбрасывает ненужный вывод, не накапливая его в памяти. Труба конечна: если поток заведён в PIPE, его обязан кто-то читать, иначе дочерний процесс заблокируется на записи.",
        "en": "Inside subprocess.run() you rarely need to name PIPE at all — capture_output=True does exactly this for both streams. PIPE earns its keep when you want to route streams: stderr=subprocess.STDOUT merges errors into the same stdout, while subprocess.DEVNULL throws unwanted output away instead of buffering it. A pipe has a finite buffer, so whatever you send into one someone has to read, or the child blocks forever on its next write."
      },
      "syntax": "subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/subprocess.html#subprocess.PIPE",
      "version": "",
      "section": "Модуль subprocess",
      "subcat": "константы",
      "color_group": "module",
      "aliases": [
        "перехватить вывод дочернего процесса",
        "перенаправление потоков процесса"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import subprocess",
        "result = subprocess.run(",
        "['echo', 'привет'],",
        "stdout=subprocess.PIPE, stderr=subprocess.PIPE,",
        "text=True",
        ")",
        "print(result.stdout)   # привет",
        "print(result.stderr)   # пусто"
      ],
      "related": [
        "subprocess-Popen",
        "subprocess-run",
        "sys.stdin-sys.stdout-sys.stderr"
      ],
      "related_errors": []
    },
    {
      "id": "subprocess-Popen",
      "title": "subprocess.Popen()",
      "kind": "function",
      "summary": {
        "ru": "Низкоуровневый запуск процесса с ручным управлением. Позволяет читать stdout/stderr в реальном времени и писать в stdin.",
        "en": "Low-level process launching, managed by hand. It lets you read stdout/stderr as they come and write to stdin."
      },
      "body": {
        "ru": "Popen не ждёт: вызов возвращает управление сразу, дочерний процесс живёт параллельно, и код возврата появится только после wait() или communicate(). Если и stdout, и stderr заведены на PIPE и вычитывать их вручную по очереди, буфер ОС рано или поздно переполнится и оба процесса встанут намертво — communicate() читает оба потока одновременно и потому безопасен. Когда нужен просто результат команды, а не диалог с ней, берите subprocess.run().",
        "en": "Popen returns immediately: the child keeps running in parallel and there is no exit code until you call wait() or communicate(). Draining stdout and stderr by hand, one after the other, when both are pipes will deadlock as soon as an OS buffer fills up; communicate() reads both at the same time and avoids that trap. If you only need the finished result rather than a live conversation with the process, reach for subprocess.run() instead."
      },
      "syntax": "proc = subprocess.Popen(args, stdin=, stdout=, stderr=, text=False, ...)\nproc.communicate(input=None)\nproc.wait()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/subprocess.html#subprocess.Popen",
      "version": "3.9",
      "section": "Модуль subprocess",
      "subcat": "запуск процессов",
      "color_group": "module",
      "aliases": [
        "запуск процесса с ручным управлением",
        "читать вывод программы на лету",
        "писать в стандартный ввод процесса"
      ],
      "keywords": [
        "subprocess.Popen"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import subprocess",
        "proc = subprocess.Popen(",
        "['python3', '-c', 'print(\"hello\")'],",
        "stdout=subprocess.PIPE, text=True",
        ")",
        "out, err = proc.communicate()",
        "print(out)  # hello",
        "print(proc.returncode)  # 0"
      ],
      "related": [
        "subprocess-run",
        "subprocess-PIPE",
        "subprocess-check_output"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "subprocess-check_output",
      "title": "subprocess.check_output()",
      "kind": "function",
      "summary": {
        "ru": "Запускает команду и возвращает её stdout как bytes (или str при text=True). При ненулевом коде возврата бросает CalledProcessError.",
        "en": "Runs a command and returns its stdout as bytes (or as a str with text=True). A non-zero exit code raises CalledProcessError."
      },
      "body": {
        "ru": "Захватывается только stdout — stderr по умолчанию уходит прямо в консоль родителя, поэтому текст ошибки легко потерять; ловите CalledProcessError и смотрите e.returncode и e.output либо передавайте stderr=subprocess.STDOUT. По сути это тонкая обёртка над subprocess.run(..., check=True, stdout=PIPE), оставшаяся с доpython-3.5 времён: в новом коде документация советует run(). Без text=True вернутся bytes, и сравнение результата с обычной строкой молча даст False.",
        "en": "Only stdout is captured — stderr goes straight to the parent's console by default, so the actual error message is easy to lose; catch CalledProcessError and inspect e.returncode and e.output, or pass stderr=subprocess.STDOUT. It is a thin wrapper over subprocess.run(..., check=True, stdout=PIPE) left over from pre-3.5 days, and the docs point new code at run(). Without text=True you get bytes back, and comparing that to a normal string silently yields False."
      },
      "syntax": "subprocess.check_output(args, *, text=False, stderr=None, timeout=None, ...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/subprocess.html#subprocess.check_output",
      "version": "3.1",
      "section": "Модуль subprocess",
      "subcat": "запуск процессов",
      "color_group": "module",
      "aliases": [
        "получить вывод команды в переменную",
        "прочитать результат консольной команды"
      ],
      "keywords": [
        "subprocess.check_output"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import subprocess",
        "out = subprocess.check_output(['python3', '--version'], text=True)",
        "print(out)  # Python 3.x.y",
        "try:",
        "    subprocess.check_output(['false'])  # команда, возвращающая 1",
        "except subprocess.CalledProcessError as e:",
        "    print('код возврата:', e.returncode)"
      ],
      "related": [
        "subprocess-run",
        "subprocess-Popen",
        "subprocess-PIPE"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "subprocess-run",
      "title": "subprocess.run()",
      "kind": "function",
      "summary": {
        "ru": "Запускает команду, ждёт завершения и возвращает CompletedProcess. Основной способ вызова внешних программ в Python 3.5+.",
        "en": "Runs a command, waits for it to finish and returns a CompletedProcess. The main way to call external programs since Python 3.5."
      },
      "body": {
        "ru": "По умолчанию check=False, поэтому упавшая команда выглядит как успешный вызов — проверяйте result.returncode или ставьте check=True, иначе ошибка пройдёт незамеченной. Без capture_output=True вывод идёт напрямую в консоль, а result.stdout остаётся None. Аргументы передавайте списком: строка вместе с shell=True превращает любой пользовательский ввод в дыру для инъекции команд.",
        "en": "check=False is the default, so a command that failed still looks like a successful call — either test result.returncode yourself or pass check=True, otherwise the failure slips by unnoticed. Without capture_output=True the output goes straight to the console and result.stdout stays None. Pass the arguments as a list: a single string plus shell=True turns any user-supplied text into a command-injection hole."
      },
      "syntax": "subprocess.run(args, *, capture_output=False, text=False, check=False, timeout=None, ...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/subprocess.html#subprocess.run",
      "version": "3.5",
      "section": "Модуль subprocess",
      "subcat": "запуск процессов",
      "color_group": "module",
      "aliases": [
        "запустить внешнюю программу",
        "вызвать команду операционной системы",
        "выполнить команду и дождаться завершения"
      ],
      "keywords": [
        "subprocess.run"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import subprocess",
        "# простой запуск",
        "result = subprocess.run(['ls', '-la'], capture_output=True, text=True)",
        "print(result.stdout)",
        "# проверка кода возврата",
        "subprocess.run(['python', '--version'], check=True)  # CalledProcessError при ошибке"
      ],
      "related": [
        "subprocess-Popen",
        "subprocess-check_output",
        "subprocess-PIPE",
        "os.system"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError"
      ]
    },
    {
      "id": "sys.argv",
      "title": "sys.argv",
      "kind": "term",
      "summary": {
        "ru": "Список аргументов командной строки. sys.argv[0] — имя скрипта, остальные — переданные аргументы.",
        "en": "The list of command-line arguments. sys.argv[0] is the script name, the rest are the arguments passed to it."
      },
      "body": {
        "ru": "Все элементы — строки, даже «10»: без int() или float() арифметики не выйдет. Обращение к sys.argv[1] без проверки len(sys.argv) — самая частая причина IndexError, когда скрипт запустили без аргументов. Само sys.argv[0] зависит от способа запуска (при python -c это '-c', при чтении кода из stdin — пустая строка), а как только флагов становится больше пары, разбирайте их через argparse, а не вручную.",
        "en": "Every element is a string, even \"10\", so anything arithmetic needs int() or float() first. Touching sys.argv[1] without checking len(sys.argv) is the classic IndexError you get the moment the script is run with no arguments. sys.argv[0] itself depends on how Python was started (it is '-c' under python -c, and an empty string when the code comes from stdin), and once you have more than a couple of flags, parse them with argparse rather than by hand."
      },
      "syntax": "sys.argv  # list[str]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.argv",
      "version": "",
      "section": "Модуль sys",
      "subcat": "аргументы",
      "color_group": "module",
      "aliases": [
        "аргументы командной строки",
        "параметры запуска скрипта",
        "передать значения скрипту при запуске"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import sys",
        "print(sys.argv)  # → ['script.py', 'arg1', 'arg2']",
        "script_name = sys.argv[0]  # → 'script.py'",
        "if len(sys.argv) > 1:",
        "    first_arg = sys.argv[1]  # → первый аргумент",
        "    for arg in sys.argv[1:]:",
        "        print(arg)  # → вывод всех аргументов",
        "        print(len(sys.argv))  # → кол-во аргументов включая имя скрипта"
      ],
      "related": [
        "input",
        "sys.exit",
        "os.environ"
      ],
      "related_errors": []
    },
    {
      "id": "sys.exit",
      "title": "sys.exit()",
      "kind": "function",
      "summary": {
        "ru": "Завершает программу. Аргумент 0 — успешное завершение, не-0 — ошибка. Поднимает SystemExit.",
        "en": "Ends the program. The argument 0 means success, anything non-zero an error. It raises SystemExit."
      },
      "body": {
        "ru": "SystemExit наследуется от BaseException, а не от Exception: except Exception его пропустит, а вот голый except: или except BaseException молча съест выход, и программа поедет дальше — блоки finally при этом отрабатывают всегда. Если аргумент не число, объект печатается в stderr, а код возврата будет 1: sys.exit('файл не найден') — это сообщение об ошибке, а не успешное завершение.",
        "en": "SystemExit inherits from BaseException, not Exception, so except Exception lets it through while a bare except: or except BaseException silently swallows the exit and the program keeps running; finally blocks always execute either way. A non-integer argument is printed to stderr and the exit status becomes 1, so sys.exit('file not found') reports a failure, not a clean finish."
      },
      "syntax": "sys.exit(status=0)\n# status: int или str",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.exit",
      "version": "",
      "section": "Модуль sys",
      "subcat": "управление",
      "color_group": "module",
      "aliases": [
        "завершить программу",
        "выйти из скрипта",
        "код возврата программы"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import sys",
        "try:",
        "    sys.exit(0)",
        "except SystemExit as e:",
        "    print(e.code)  # → 0",
        "    try:",
        "        sys.exit(1)",
        "    except SystemExit as e:",
        "        print(e.code)  # → 1",
        "    try:",
        "        sys.exit('Error!')",
        "    except SystemExit as e:",
        "        print(e.code)  # → Error!",
        "        print(isinstance(SystemExit(), BaseException))  # → True"
      ],
      "related": [
        "systemexit",
        "os._exit",
        "sys.argv"
      ],
      "related_errors": [
        "SystemExit"
      ]
    },
    {
      "id": "sys.getrecursionlimit",
      "title": "sys.getrecursionlimit()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает текущий предел глубины рекурсии интерпретатора (по умолчанию 1000). При его превышении поднимается RecursionError.",
        "en": "Return the interpreter's current maximum recursion depth (1000 by default); exceeding it raises RecursionError."
      },
      "body": {
        "ru": "Предел считает глубину всего стека интерпретатора, а не только вызовов вашей функции: если рекурсия запускается из-под нескольких вложенных вызовов, до RecursionError останется заметно меньше тысячи шагов. Значение общее для процесса, так что использовать его как точный бюджет глубины алгоритма не стоит — это предохранитель, а не мера памяти.",
        "en": "The number counts frames on the whole interpreter stack, not just calls of your own function, so recursion started deep inside other calls hits RecursionError well before a thousand levels. It is a process-wide safety valve rather than a precise depth budget for your algorithm, and it says nothing about available memory."
      },
      "syntax": "sys.getrecursionlimit() -> int",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.getrecursionlimit",
      "version": "",
      "section": "Модуль sys",
      "subcat": "рекурсия",
      "color_group": "module",
      "aliases": [
        "узнать предел рекурсии",
        "текущий лимит глубины вызовов"
      ],
      "keywords": [
        "sys.getrecursionlimit",
        "getrecursionlimit"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import sys",
        "print(sys.getrecursionlimit())  # → 1000",
        "print(isinstance(sys.getrecursionlimit(), int))  # → True",
        "sys.setrecursionlimit(2000)",
        "print(sys.getrecursionlimit())  # → 2000",
        "sys.setrecursionlimit(1000)  # вернули значение по умолчанию",
        "def deep(n): return deep(n + 1)",
        "print(deep(0))  # → RecursionError"
      ],
      "related": [
        "sys.setrecursionlimit"
      ],
      "related_errors": []
    },
    {
      "id": "sys.getsizeof",
      "title": "sys.getsizeof()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает размер объекта в байтах (без учёта вложенных объектов). Использует __sizeof__ плюс служебные данные GC.",
        "en": "Returns the size of an object in bytes (not counting the objects nested in it). It uses __sizeof__ plus the GC bookkeeping."
      },
      "body": {
        "ru": "Для контейнеров считается только сам контейнер: список из миллиона строк покажет несколько мегабайт массива указателей, а сами строки не учтёт. Складывать размеры рекурсивно вручную тоже обманчиво — общий или интернированный объект посчитается несколько раз. Если вопрос «сколько памяти реально ест мой код», нужен tracemalloc, а не getsizeof.",
        "en": "For containers only the container itself is measured: a list of a million strings reports a few megabytes of pointer array and ignores the strings. Summing sizes recursively by hand is misleading too — a shared or interned object gets counted more than once. When the real question is how much memory your code actually uses, reach for tracemalloc instead."
      },
      "syntax": "sys.getsizeof(object, default=...) -> int",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.getsizeof",
      "version": "",
      "section": "Модуль sys",
      "subcat": "память",
      "color_group": "module",
      "aliases": [
        "размер объекта в памяти",
        "сколько байт занимает объект",
        "потребление памяти объектом"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import sys",
        "print(sys.getsizeof(0))  # → 28",
        "print(sys.getsizeof(''))  # → 49",
        "print(sys.getsizeof([]))  # → 56",
        "print(sys.getsizeof({'a': 1}))  # → 232",
        "print(sys.getsizeof(list(range(100))))  # → 920"
      ],
      "related": [
        "len",
        "__slots__",
        "sys.intern"
      ],
      "related_errors": []
    },
    {
      "id": "sys.intern",
      "title": "sys.intern()",
      "kind": "function",
      "summary": {
        "ru": "Интернирует строку — гарантирует, что идентичные строки используют один объект в памяти. Ускоряет сравнение через 'is'.",
        "en": "Interns a string — it guarantees that identical strings share one object in memory. It speeds up comparison with 'is'."
      },
      "body": {
        "ru": "Выигрыш здесь прежде всего в памяти, когда одна и та же строка повторяется тысячами раз (ключи разобранного файла, повторяющиеся токены), а не в скорости вообще: обычное == и так начинает со сравнения по идентичности. Ссылку на результат надо хранить самому — интернированная строка не бессмертна и уходит вместе с последней ссылкой. И не переносите приём на обычный код: сравнивать строки через is нельзя, короткие литералы CPython интернирует сам, поэтому такой код обманчиво работает в примерах и ломается на строках, собранных в рантайме.",
        "en": "The real win is memory when the same string occurs thousands of times (keys parsed from a file, repeated tokens), not speed in general: plain == already starts with an identity check. You must keep a reference to the returned string yourself — interned strings are not immortal and die with the last reference. Do not turn this into a habit of comparing strings with is: CPython interns short literals on its own, so such code looks fine in toy examples and breaks on strings built at runtime."
      },
      "syntax": "sys.intern(string: str) -> str",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.intern",
      "version": "",
      "section": "Модуль sys",
      "subcat": "строки",
      "color_group": "module",
      "aliases": [
        "интернирование строк",
        "одна копия строки в памяти"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import sys",
        "a = sys.intern('hello')",
        "b = sys.intern('hello')",
        "print(a is b)  # → True",
        "c = 'hel' + 'lo'",
        "print(sys.intern(c) is a)  # → True",
        "words = [sys.intern(w) for w in ['apple', 'banana', 'apple']]",
        "print(words[0] is words[2])  # → True",
        "print(type(sys.intern('x')))  # → <class 'str'>"
      ],
      "related": [
        "строки-в-памяти-интернирование-неизменяе",
        "is-is-not",
        "sys.getsizeof"
      ],
      "related_errors": []
    },
    {
      "id": "sys.modules",
      "title": "sys.modules",
      "kind": "term",
      "summary": {
        "ru": "Словарь всех загруженных модулей {имя: module}. Кэш импортов Python. Можно удалить модуль для перезагрузки.",
        "en": "The dictionary of every loaded module, {name: module}. Python's import cache. A module can be removed from it to force a reload."
      },
      "body": {
        "ru": "Именно из-за этого кэша тело модуля выполняется ровно один раз: повторный import в той же сессии не увидит правок в файле. Но удаление ключа из sys.modules модуль не выгружает — старые классы и объекты продолжают жить в уже созданных ссылках, и после нового импорта isinstance начнёт неожиданно возвращать False. Для перезагрузки берите importlib.reload(), а надёжнее всего — просто перезапустить процесс.",
        "en": "This cache is why a module body runs exactly once: a second import in the same session will not pick up edits to the file. Deleting a key from sys.modules does not unload anything — the old classes and objects survive in references already taken, so after a re-import isinstance starts returning False for what looks like the same class. Use importlib.reload() for a reload, and restart the process when you need certainty."
      },
      "syntax": "sys.modules  # dict[str, ModuleType]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.modules",
      "version": "",
      "section": "Модуль sys",
      "subcat": "модули",
      "color_group": "module",
      "aliases": [
        "загруженные модули",
        "кэш импортов",
        "список импортированных модулей"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import sys",
        "import os",
        "print('os' in sys.modules)  # → True",
        "print(type(sys.modules['os']))  # → <class 'module'>",
        "print(len(sys.modules))  # → кол-во загруженных модулей",
        "del sys.modules['os']  # → принудительная выгрузка",
        "import os  # → повторная загрузка"
      ],
      "related": [
        "sys.path",
        "__import__",
        "modulenotfounderror"
      ],
      "related_errors": []
    },
    {
      "id": "sys.path",
      "title": "sys.path",
      "kind": "term",
      "summary": {
        "ru": "Список директорий, в которых Python ищет модули при импорте. Можно модифицировать для добавления своих путей.",
        "en": "The list of directories Python searches for modules on import. It can be modified to add paths of your own."
      },
      "body": {
        "ru": "Первым в списке идёт каталог запускаемого скрипта, поэтому свой файл random.py или json.py рядом с кодом затенит одноимённый стандартный модуль — это классическая причина загадочного AttributeError вроде «module 'random' has no attribute 'randint'». Правка sys.path живёт только внутри текущего процесса и считается костылём: для постоянного эффекта ставят пакет (pip install -e .) или задают PYTHONPATH.",
        "en": "The first entry is the directory of the script being run, so your own random.py or json.py sitting next to the code will shadow the standard module — the classic cause of a baffling AttributeError like \"module 'random' has no attribute 'randint'\". Editing sys.path only affects the current process and is a workaround: for a lasting fix install the package (pip install -e .) or set PYTHONPATH."
      },
      "syntax": "sys.path  # list[str]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.path",
      "version": "",
      "section": "Модуль sys",
      "subcat": "модули",
      "color_group": "module",
      "aliases": [
        "где питон ищет модули",
        "пути поиска модулей",
        "добавить свой путь импорта"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import sys",
        "print(sys.path)  # → ['', '/usr/lib/python3...', ...]",
        "sys.path.append('/my/modules')  # → добавить путь",
        "sys.path.insert(0, '/priority/path')  # → приоритетный путь",
        "print(sys.path[0])  # → '' (текущая директория)",
        "print('/usr' in ''.join(sys.path))  # → True (обычно)"
      ],
      "related": [
        "sys.modules",
        "__import__",
        "modulenotfounderror"
      ],
      "related_errors": []
    },
    {
      "id": "sys.setrecursionlimit",
      "title": "sys.setrecursionlimit()",
      "kind": "function",
      "summary": {
        "ru": "Устанавливает предел глубины рекурсии. Слишком большое значение может уронить процесс по переполнению стека — это не защита, а предохранитель.",
        "en": "Set the maximum recursion depth; too high a value can crash the process with a stack overflow — the limit is a safeguard, not a hard guarantee."
      },
      "body": {
        "ru": "Поднять предел — не значит починить бесконечную рекурсию: ловимый RecursionError просто сменится жёстким падением всего процесса по переполнению стека, уже без traceback. Опустить предел ниже текущей глубины тоже не выйдет — сам вызов поднимет RecursionError. Если алгоритм честно уходит вглубь на десятки тысяч шагов, его переписывают итеративно со своим стеком, а не крутят лимит.",
        "en": "Raising the limit does not fix runaway recursion — it only trades a catchable RecursionError for a hard crash of the whole process on stack overflow, with no traceback. Lowering it below the current depth fails as well: the call itself raises RecursionError. If an algorithm genuinely needs tens of thousands of levels, rewrite it iteratively with an explicit stack instead of tuning the limit."
      },
      "syntax": "sys.setrecursionlimit(limit: int) -> None",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.setrecursionlimit",
      "version": "",
      "section": "Модуль sys",
      "subcat": "рекурсия",
      "color_group": "module",
      "aliases": [
        "увеличить лимит рекурсии",
        "изменить предел глубины вызовов"
      ],
      "keywords": [
        "sys.setrecursionlimit",
        "setrecursionlimit"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import sys",
        "sys.setrecursionlimit(5000)",
        "print(sys.getrecursionlimit())  # → 5000",
        "sys.setrecursionlimit(1000)  # → возврат к стандарту"
      ],
      "related": [
        "sys.getrecursionlimit"
      ],
      "related_errors": []
    },
    {
      "id": "sys.stdin-sys.stdout-sys.stderr",
      "title": "sys.stdin / sys.stdout / sys.stderr",
      "kind": "term",
      "summary": {
        "ru": "Стандартные потоки ввода/вывода/ошибок. Можно перенаправлять для тестирования или записи в файл.",
        "en": "The standard input, output and error streams. They can be redirected for testing or to write to a file."
      },
      "body": {
        "ru": "Когда вывод уходит в файл или пайп, stdout буферизуется блоками, а stderr отдаётся построчно — поэтому в логе сообщения print и трейсбеки часто оказываются вперемешку не в том порядке; лечится print(..., flush=True) или запуском python -u. Учтите, что sys.stdout.write() принимает только строку и сам перевод строки не дописывает, а оригиналы потоков всегда лежат в sys.__stdout__ и sys.__stderr__, если вы что-то подменили.",
        "en": "When output goes to a file or a pipe, stdout is block-buffered while stderr stays line-buffered, so print messages and tracebacks often land in the log out of order; fix it with print(..., flush=True) or by running python -u. Note that sys.stdout.write() takes a string only and adds no newline of its own, and the untouched originals are always kept in sys.__stdout__ and sys.__stderr__ if you swapped a stream out."
      },
      "syntax": "sys.stdin   # TextIOWrapper (чтение)\nsys.stdout  # TextIOWrapper (запись)\nsys.stderr  # TextIOWrapper (ошибки)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.stdin",
      "version": "",
      "section": "Модуль sys",
      "subcat": "потоки",
      "color_group": "module",
      "aliases": [
        "стандартные потоки ввода и вывода",
        "перенаправить вывод программы",
        "вывод в поток ошибок"
      ],
      "keywords": [
        "sys.stdin",
        "sys.stdout",
        "sys.stderr"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import sys",
        "sys.stdout.write('hello\\n')  # → hello",
        "sys.stderr.write('error!\\n')  # → error! (в stderr)",
        "print('msg', file=sys.stderr)  # → msg в поток ошибок",
        "print(sys.stdin.encoding)  # → 'utf-8'",
        "print(sys.stdout.encoding)  # → 'utf-8'"
      ],
      "related": [
        "stdin-stdout",
        "input",
        "print",
        "contextlib.redirect_stdout"
      ],
      "related_errors": []
    },
    {
      "id": "sys.version-sys.platform-sys.implementat",
      "title": "sys.version / sys.platform / sys.implementation",
      "kind": "term",
      "summary": {
        "ru": "sys.version — строка версии Python. sys.platform — ОС ('linux', 'win32', 'darwin'). sys.implementation — детали реализации (CPython и т.д.).",
        "en": "sys.version — the Python version as a string. sys.platform — the OS ('linux', 'win32', 'darwin'). sys.implementation — details of the implementation (CPython and so on)."
      },
      "body": {
        "ru": "Проверку «Python не ниже такой-то версии» делают сравнением кортежей: if sys.version_info >= (3, 10). Разбирать строку sys.version не стоит — в ней кроме цифр лежат дата сборки и версия компилятора. На Windows sys.platform равен 'win32' даже в 64-битной сборке, на macOS — 'darwin', на Linux — 'linux'; если нужно лишь «винда или нет», проще глянуть os.name.",
        "en": "To require a minimum version, compare tuples: if sys.version_info >= (3, 10). Do not parse the sys.version string — it also carries the build date and the compiler version. On Windows sys.platform is 'win32' even in a 64-bit build, on macOS it is 'darwin', on Linux 'linux'; for a plain \"Windows or not\" check, os.name is simpler."
      },
      "syntax": "sys.version        # str\nsys.version_info   # named tuple (major, minor, micro)\nsys.platform       # str\nsys.implementation # namespace",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.version",
      "version": "",
      "section": "Модуль sys",
      "subcat": "информация",
      "color_group": "module",
      "aliases": [
        "версия питона",
        "узнать операционную систему",
        "на какой системе запущен код"
      ],
      "keywords": [
        "sys.version",
        "sys.platform",
        "sys.implementation"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import sys",
        "print(sys.version)  # → '3.11.0 (default, ...)'",
        "print(sys.version_info)  # → sys.version_info(major=3, minor=11, ...)",
        "print(sys.version_info.major)  # → 3",
        "print(sys.platform)  # → 'linux'",
        "print(sys.implementation.name)  # → 'cpython'"
      ],
      "related": [
        "os.sep-os.linesep-os.pathsep",
        "sys.path",
        "sys.modules"
      ],
      "related_errors": []
    },
    {
      "id": "textwrap.TextWrapper",
      "title": "textwrap.TextWrapper",
      "kind": "term",
      "summary": {
        "ru": "Класс-переносчик текста с настройками (width, initial_indent, …) для повторного применения.",
        "en": "A reusable text-wrapping object configured with width, indents, etc."
      },
      "body": {
        "ru": "wrap() и fill() — тонкие обёртки: каждый вызов создаёт новый TextWrapper и тут же его выбрасывает, поэтому при форматировании множества фрагментов одними настройками объект стоит завести один раз. Все его параметры — обычные атрибуты, их можно менять между вызовами, например подстраивать width под текущую ширину терминала.",
        "en": "wrap() and fill() are thin wrappers that build a throwaway TextWrapper on every call, so when you format many chunks with the same settings, keeping one instance around is cheaper. Its options are plain attributes you can change between calls — handy when width has to follow the current terminal size."
      },
      "syntax": "textwrap.TextWrapper(width=70, **kwargs)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/textwrap.html#textwrap.TextWrapper",
      "version": "",
      "section": "Модуль textwrap",
      "subcat": "перенос",
      "color_group": "module",
      "aliases": [
        "настраиваемый переносчик текста",
        "повторный перенос текста с настройками"
      ],
      "keywords": [],
      "tags": [
        "textwrap"
      ],
      "examples": [
        "import textwrap",
        "w = textwrap.TextWrapper(width=10)",
        "print(w.wrap('a b c d'))   # → ['a b c d']",
        "print(w.wrap('one two three four'))  # → ['one two', 'three four']",
        "print(textwrap.TextWrapper(width=12, initial_indent='> ').wrap('one two three'))  # → ['> one two', 'three']",
        "print(textwrap.TextWrapper(width=5).wrap('abcdefgh'))  # → ['abcde', 'fgh']",
        "print(w.wrap(''))  # → []"
      ],
      "related": [
        "textwrap.wrap",
        "textwrap.fill",
        "textwrap.shorten"
      ],
      "related_errors": []
    },
    {
      "id": "textwrap.dedent",
      "title": "textwrap.dedent",
      "kind": "function",
      "summary": {
        "ru": "Убирает общий ведущий пробельный отступ у всех строк текста (полезно для многострочных литералов).",
        "en": "Remove common leading whitespace from all lines of the text."
      },
      "body": {
        "ru": "Снимается только отступ, общий для всех непустых строк: если хоть одна строка идёт без отступа — типично текст, начатый сразу после открывающих тройных кавычек, — общего префикса нет и не удалится ничего, поэтому многострочный литерал стоит начинать с перевода строки. Табы и пробелы сравниваются буквально и друг другу не эквивалентны, так что смесь отступов тихо ломает dedent (лечится expandtabs заранее). Строки из одних пробелов на подсчёт префикса не влияют и в результате становятся пустыми.",
        "en": "Only the prefix common to every non-blank line is removed: if a single line has no indentation — typically text started right after the opening triple quote — there is no common prefix and nothing is stripped, so begin such literals with a newline. Tabs and spaces are compared literally and are not treated as equivalent, so mixed indentation quietly defeats dedent; run expandtabs first. Whitespace-only lines are ignored when computing the prefix and come out empty."
      },
      "syntax": "textwrap.dedent(text)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/textwrap.html#textwrap.dedent",
      "version": "",
      "section": "Модуль textwrap",
      "subcat": "отступы",
      "color_group": "module",
      "aliases": [
        "убрать общий отступ",
        "убрать отступы в многострочной строке"
      ],
      "keywords": [],
      "tags": [
        "textwrap"
      ],
      "examples": [
        "import textwrap",
        "print(textwrap.dedent('    hello'))   # → hello",
        "print(textwrap.dedent('    a\\n      b').splitlines())  # → ['a', '  b']",
        "print(textwrap.dedent('  a\\n   \\n  b').splitlines())  # → ['a', '', 'b']",
        "print(textwrap.dedent('a\\n  b').splitlines())  # → ['a', '  b']",
        "print(textwrap.dedent('   ').splitlines())  # → []"
      ],
      "related": [
        "textwrap.indent",
        "тройные-кавычки",
        "textwrap.fill"
      ],
      "related_errors": []
    },
    {
      "id": "textwrap.fill",
      "title": "textwrap.fill",
      "kind": "function",
      "summary": {
        "ru": "Как wrap(), но возвращает единую строку с переносами \\n (эквивалент '\\n'.join(wrap(...))).",
        "en": "Like wrap(), but return a single string with newlines."
      },
      "body": {
        "ru": "Главная ловушка — включённый по умолчанию replace_whitespace: переводы строк и табуляции внутри текста превращаются в пробелы, и многоабзацный текст склеивается в один сплошной кусок. Если абзацы нужно сохранить, разбей текст по пустым строкам и вызывай fill() для каждого абзаца отдельно. И учти: строки только переносятся, по правому краю ничего не выравнивается.",
        "en": "The trap is replace_whitespace, on by default: newlines and tabs inside the text become spaces, so a multi-paragraph string comes back glued into one block. Split on blank lines and fill each paragraph separately if the structure matters. Note also that text is merely wrapped, never justified to the right margin."
      },
      "syntax": "textwrap.fill(text, width=70)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/textwrap.html#textwrap.fill",
      "version": "",
      "section": "Модуль textwrap",
      "subcat": "перенос",
      "color_group": "module",
      "aliases": [
        "текст с переносами одной строкой",
        "отформатировать абзац по ширине"
      ],
      "keywords": [],
      "tags": [
        "textwrap"
      ],
      "examples": [
        "import textwrap",
        "print(textwrap.fill('a b c', width=10))   # → a b c",
        "print(repr(textwrap.fill('one two three four', width=10)))  # → 'one two\\nthree four'",
        "print(repr(textwrap.fill('one two three', width=12, initial_indent='* ')))  # → '* one two\\nthree'",
        "print(textwrap.fill('a b c d', width=10) == '\\n'.join(textwrap.wrap('a b c d', width=10)))  # → True",
        "print(repr(textwrap.fill('', width=10)))  # → ''"
      ],
      "related": [
        "textwrap.wrap",
        "textwrap.TextWrapper",
        "str.join"
      ],
      "related_errors": []
    },
    {
      "id": "textwrap.indent",
      "title": "textwrap.indent",
      "kind": "function",
      "summary": {
        "ru": "Добавляет префикс к началу выбранных строк текста.",
        "en": "Add a prefix to the beginning of selected lines of the text."
      },
      "body": {
        "ru": "По умолчанию префикс получают только строки, где есть непробельные символы: пустые строки остаются пустыми, а не превращаются в строки из одних пробелов. Изменить это правило можно третьим аргументом predicate — функцией, которой по очереди отдаётся каждая строка. Переносить длинный текст indent не умеет, он только приписывает префикс; обратная операция — textwrap.dedent.",
        "en": "By default only lines that contain non-whitespace get the prefix, so blank lines stay blank instead of turning into lines of trailing spaces. Pass a predicate as the third argument to decide line by line. It does no wrapping at all, only prepending; the opposite operation is textwrap.dedent."
      },
      "syntax": "textwrap.indent(text, prefix)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/textwrap.html#textwrap.indent",
      "version": "3.3",
      "section": "Модуль textwrap",
      "subcat": "отступы",
      "color_group": "module",
      "aliases": [
        "добавить отступ к тексту",
        "добавить префикс к каждой строке",
        "сдвинуть строки вправо"
      ],
      "keywords": [],
      "tags": [
        "textwrap"
      ],
      "examples": [
        "import textwrap",
        "print(textwrap.indent('hi', '>> '))   # → >> hi",
        "print(repr(textwrap.indent('a\\nb', '    ')))   # → '    a\\n    b'",
        "print(repr(textwrap.indent('a\\n\\nb', '# ')))   # → '# a\\n\\n# b'",
        "print(repr(textwrap.indent('a\\n\\nb', '# ', lambda line: True)))   # → '# a\\n# \\n# b'",
        "print(repr(textwrap.indent('', '> ')))   # → ''"
      ],
      "related": [
        "textwrap.dedent",
        "textwrap.fill",
        "str.splitlines"
      ],
      "related_errors": []
    },
    {
      "id": "textwrap.shorten",
      "title": "textwrap.shorten",
      "kind": "function",
      "summary": {
        "ru": "Обрезает текст до width символов, заменяя хвост на placeholder (по умолчанию ' […]' → '...').",
        "en": "Collapse and truncate text to fit `width`, replacing the tail with a placeholder."
      },
      "body": {
        "ru": "Перед обрезкой текст нормализуется: любая последовательность пробелов и переводов строк схлопывается в один пробел, так что для задачи «оставить первые N символов» это не инструмент — там нужен обычный срез. Обрезка идёт по границам слов, а placeholder входит в лимит width: если не влезает даже первое слово, вернётся один placeholder, а placeholder длиннее width даст ValueError.",
        "en": "It normalizes whitespace first — every run of spaces and newlines collapses to a single space — so it is not the tool for \"keep the first N characters\"; a plain slice is. Truncation happens at word boundaries and the placeholder counts toward width: if not even the first word fits you get the placeholder alone, and a placeholder longer than width raises ValueError."
      },
      "syntax": "textwrap.shorten(text, width, placeholder=' [...]')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/textwrap.html#textwrap.shorten",
      "version": "3.4",
      "section": "Модуль textwrap",
      "subcat": "перенос",
      "color_group": "module",
      "aliases": [
        "обрезать длинный текст",
        "сократить текст многоточием",
        "укоротить строку до длины"
      ],
      "keywords": [],
      "tags": [
        "textwrap"
      ],
      "examples": [
        "import textwrap",
        "print(textwrap.shorten('Hello world foo', 12, placeholder='...'))   # → Hello...",
        "print(textwrap.shorten('Python is a great language', 20))   # → Python is a [...]",
        "print(textwrap.shorten('  a   b  ', 10))   # → a b",
        "print(textwrap.shorten('Hello world!', 10))   # → [...]",
        "print('Hello world foo'[:8])   # → Hello wo"
      ],
      "related": [
        "textwrap.wrap",
        "срезы-строк",
        "textwrap.fill"
      ],
      "related_errors": []
    },
    {
      "id": "textwrap.wrap",
      "title": "textwrap.wrap",
      "kind": "function",
      "summary": {
        "ru": "Разбивает текст на список строк не длиннее width символов.",
        "en": "Wrap text into a list of lines each at most `width` characters."
      },
      "body": {
        "ru": "width считается в символах, и слово длиннее width по умолчанию разрезается посередине — от этого страдают URL и длинные пути; break_long_words=False сохранит слово целым, но тогда такая строка окажется длиннее width. Возвращается список строк без символов перевода строки внутри — склеивать их придётся самому.",
        "en": "width counts characters, and a word longer than width is chopped in the middle by default — painful for URLs and long file paths; break_long_words=False keeps the word intact at the cost of a line that overflows width. The result is a list of lines with no newline characters in them, so joining is up to you."
      },
      "syntax": "textwrap.wrap(text, width=70)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/textwrap.html#textwrap.wrap",
      "version": "",
      "section": "Модуль textwrap",
      "subcat": "перенос",
      "color_group": "module",
      "aliases": [
        "разбить текст на строки",
        "список строк заданной ширины"
      ],
      "keywords": [],
      "tags": [
        "textwrap"
      ],
      "examples": [
        "import textwrap",
        "print(textwrap.wrap('a b c d', width=3))   # → ['a b', 'c d']",
        "print(textwrap.wrap('one two three four', width=9))   # → ['one two', 'three', 'four']",
        "print(textwrap.wrap('abcdefghij', width=4))   # → ['abcd', 'efgh', 'ij']",
        "print(textwrap.wrap('abcdefghij', width=4, break_long_words=False))   # → ['abcdefghij']",
        "print(textwrap.wrap('', width=10))   # → []"
      ],
      "related": [
        "textwrap.fill",
        "textwrap.TextWrapper",
        "textwrap.shorten"
      ],
      "related_errors": []
    },
    {
      "id": "GIL",
      "title": "GIL",
      "kind": "term",
      "summary": {
        "ru": "Global Interpreter Lock — мьютекс CPython, позволяющий только одному потоку выполнять байт-код одновременно. Блокирует параллелизм CPU-задач, но не мешает I/O-операциям.",
        "en": "The Global Interpreter Lock — the CPython mutex that allows only one thread to execute bytecode at a time. It blocks parallelism for CPU-bound work, but does not get in the way of I/O."
      },
      "body": {
        "ru": "GIL не делает твой код потокобезопасным: составные операции (проверил-потом-изменил, counter += 1) всё равно гонятся между потоками, так что Lock по-прежнему обязателен. Начиная с Python 3.13 есть экспериментальная free-threaded сборка (PEP 703), где GIL можно отключить, но в обычной сборке он на месте.",
        "en": "The GIL does not make your code thread-safe: compound operations (check-then-act, counter += 1) still race between threads, so you still need a Lock. Since Python 3.13 there is an experimental free-threaded build (PEP 703) where the GIL can be turned off, but in the regular build it is still there."
      },
      "syntax": "# GIL — концепция, не синтаксис",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-global-interpreter-lock",
      "version": "",
      "section": "Модуль threading",
      "subcat": "концепции",
      "color_group": "module",
      "aliases": [
        "глобальная блокировка интерпретатора",
        "почему потоки не ускоряют вычисления",
        "ограничение параллелизма"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "# GIL мешает CPU-задачам:",
        "import threading, time",
        "def cpu_task():",
        "    sum(range(10**7))",
        "    # 2 потока не быстрее 1 для CPU",
        "# Для CPU-параллелизма используй ProcessPoolExecutor",
        "from concurrent.futures import ProcessPoolExecutor",
        "with ProcessPoolExecutor() as ex:",
        "    list(ex.map(cpu_task, range(4)))"
      ],
      "related": [
        "threading-thread",
        "ProcessPoolExecutor",
        "ThreadPoolExecutor"
      ],
      "related_errors": []
    },
    {
      "id": "threading-event",
      "title": "threading.Event",
      "kind": "term",
      "summary": {
        "ru": "Механизм сигнализации между потоками. Один поток устанавливает флаг (set()), другие ждут его (wait()).",
        "en": "A signalling mechanism between threads. One thread sets the flag (set()), the others wait for it (wait())."
      },
      "body": {
        "ru": "Заменяет busy-wait по булеву флагу: wait() блокирует поток эффективно, без прокрутки цикла и сжигания CPU. Флаг уровневый — после set() он остаётся взведён, и опоздавшие wait() проходят мгновенно, пока кто-нибудь не вызовет clear(). При этом wait(timeout) возвращает текущее значение флага (True/False), так что таймаут отличим от настоящего сигнала.",
        "en": "It replaces busy-waiting on a boolean flag: wait() blocks the thread efficiently, with no spin loop burning CPU. The flag is level-based — after set() it stays raised, and late wait() calls return instantly until someone calls clear(). And wait(timeout) returns the flag's current value (True/False), so a timeout is distinguishable from an actual signal."
      },
      "syntax": "e = threading.Event()\ne.set() / e.clear() / e.wait() / e.is_set()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/threading.html#threading.Event",
      "version": "",
      "section": "Модуль threading",
      "subcat": "синхронизация",
      "color_group": "module",
      "aliases": [
        "сигнал между потоками",
        "дождаться события в потоке",
        "флаг ожидания"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import threading",
        "ready = threading.Event()",
        "def producer():",
        "    print('данные готовы')",
        "    ready.set()",
        "def consumer():",
        "    ready.wait()        # блокируется до set()",
        "    print('получил данные')",
        "    t1 = threading.Thread(target=producer)",
        "    t2 = threading.Thread(target=consumer)",
        "    t2.start(); t1.start()",
        "    t1.join(); t2.join()"
      ],
      "related": [
        "threading-lock",
        "threading-thread",
        "threading-semaphore"
      ],
      "related_errors": []
    },
    {
      "id": "threading-lock",
      "title": "threading.Lock",
      "kind": "term",
      "summary": {
        "ru": "Примитив взаимного исключения (мьютекс). Гарантирует, что только один поток в момент времени выполняет защищённый блок кода.",
        "en": "The mutual exclusion primitive (a mutex). It guarantees that only one thread at a time runs the protected block of code."
      },
      "body": {
        "ru": "Lock нереентрантный: если тот же поток попытается захватить его второй раз, не отпустив первый (в рекурсии или во вложенном методе под тем же локом), он заблокирует сам себя навсегда — ровно для этого случая существует RLock. Всегда бери его через with lock:, иначе исключение внутри блока оставит лок незакрытым и остальные потоки повиснут.",
        "en": "Lock is non-reentrant: if the same thread tries to acquire it a second time without releasing the first (in recursion, or a nested method under the same lock), it deadlocks itself forever — that exact case is why RLock exists. Always take it via with lock:, or an exception inside the block leaves the lock held and other threads hang."
      },
      "syntax": "lock = threading.Lock()\nlock.acquire() / lock.release()\nwith lock: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/threading.html#threading.Lock",
      "version": "",
      "section": "Модуль threading",
      "subcat": "синхронизация",
      "color_group": "module",
      "aliases": [
        "мьютекс",
        "защита общих данных от гонки",
        "блокировка критической секции"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import threading",
        "lock = threading.Lock()",
        "counter = 0",
        "def increment():",
        "global counter",
        "with lock:          # автоматический release",
        "counter += 1",
        "threads = [threading.Thread(target=increment) for _ in range(100)]",
        "for t in threads: t.start()",
        "for t in threads: t.join()",
        "print(counter)  # 100"
      ],
      "related": [
        "threading-rlock",
        "threading-semaphore",
        "threading-thread"
      ],
      "related_errors": []
    },
    {
      "id": "threading-rlock",
      "title": "threading.RLock",
      "kind": "term",
      "summary": {
        "ru": "Реентрантный мьютекс — один поток может захватить его несколько раз без блокировки. Используй вместо Lock при рекурсивных вызовах.",
        "en": "A reentrant mutex — one thread can acquire it several times without blocking itself. Use it instead of Lock with recursive calls."
      },
      "body": {
        "ru": "Считает глубину захвата: сколько раз поток вызвал acquire(), столько же должен вызвать release(), и только тогда лок освободится — забытый release оставит его занятым, а освободить может лишь поток-владелец. От взаимной блокировки между разными потоками RLock не спасает: он лечит только повторный захват внутри одного потока и стоит чуть дороже обычного Lock, так что без реальной реентрантности бери Lock.",
        "en": "It tracks acquisition depth: a thread must call release() as many times as it called acquire() before the lock frees up — a missed release leaves it held, and only the owning thread can release it. RLock does nothing against deadlock between different threads: it only cures re-acquisition within one thread and costs a bit more than a plain Lock, so without real reentrancy use Lock."
      },
      "syntax": "rlock = threading.RLock()\nwith rlock: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/threading.html#threading.RLock",
      "version": "",
      "section": "Модуль threading",
      "subcat": "синхронизация",
      "color_group": "module",
      "aliases": [
        "рекурсивный мьютекс",
        "повторный захват блокировки одним потоком"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import threading",
        "rlock = threading.RLock()",
        "def recursive(n):",
        "with rlock:",
        "if n > 0:",
        "recursive(n - 1)  # Lock здесь бы заблокировал",
        "print(n)",
        "recursive(3)"
      ],
      "related": [
        "threading-lock",
        "threading-semaphore",
        "threading-thread"
      ],
      "related_errors": []
    },
    {
      "id": "threading-semaphore",
      "title": "threading.Semaphore",
      "kind": "term",
      "summary": {
        "ru": "Семафор ограничивает количество потоков, одновременно выполняющих блок кода. Полезен для ограничения параллельных запросов.",
        "en": "A semaphore bounds how many threads may run a block of code at the same time. Useful for limiting parallel requests."
      },
      "body": {
        "ru": "Обычный Semaphore не следит за балансом: лишний release() тихо поднимает лимит выше стартового n, и баг с утечкой разрешений маскируется — для его отлова берите BoundedSemaphore, он вместо этого бросит ValueError. Semaphore(1) похож на Lock, но освобождать его может любой поток, тогда как Lock рассчитан на парные acquire/release в одном месте.",
        "en": "A plain Semaphore keeps no upper bound: an extra release() silently raises the limit above the starting n and hides a leaked-permit bug — use BoundedSemaphore, which raises ValueError instead. Semaphore(1) resembles a Lock, but any thread may release it, whereas a Lock expects a matched acquire/release in one place."
      },
      "syntax": "sem = threading.Semaphore(n)\nwith sem: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/threading.html#threading.Semaphore",
      "version": "",
      "section": "Модуль threading",
      "subcat": "синхронизация",
      "color_group": "module",
      "aliases": [
        "семафор",
        "ограничить число одновременных потоков",
        "ограничение параллельных запросов"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import threading",
        "sem = threading.Semaphore(3)  # не более 3 одновременно",
        "def task(i):",
        "    with sem:",
        "        print(f'выполняю {i}')",
        "        import time; time.sleep(1)",
        "        threads = [threading.Thread(target=task, args=(i,)) for i in range(10)]",
        "        for t in threads: t.start()",
        "        for t in threads: t.join()"
      ],
      "related": [
        "threading-lock",
        "threading-thread",
        "ThreadPoolExecutor"
      ],
      "related_errors": []
    },
    {
      "id": "threading-thread",
      "title": "threading.Thread",
      "kind": "term",
      "summary": {
        "ru": "Класс для создания и запуска потока. Потоки разделяют память процесса. Из-за GIL не ускоряет CPU-задачи, но хорош для I/O.",
        "en": "The class for creating and starting a thread. Threads share the memory of the process. Because of the GIL it does not speed up CPU-bound work, but it is good for I/O."
      },
      "body": {
        "ru": "Запускает поток именно start(); прямой вызов t.run() выполнит функцию синхронно в текущем потоке — никакого параллелизма не будет. Повторный start() у того же объекта бросает RuntimeError, а daemon=True-потоки интерпретатор убивает на выходе резко, минуя блоки finally и очистку.",
        "en": "Start a thread with start(); calling t.run() directly runs the target synchronously in the current thread with no parallelism at all. A second start() on the same object raises RuntimeError, and daemon=True threads are killed abruptly at interpreter exit, skipping finally blocks and cleanup."
      },
      "syntax": "t = threading.Thread(target=func, args=(), kwargs={}, daemon=False)\nt.start()\nt.join()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/threading.html#threading.Thread",
      "version": "",
      "section": "Модуль threading",
      "subcat": "потоки",
      "color_group": "module",
      "aliases": [
        "запустить поток",
        "многопоточность",
        "выполнить функцию параллельно"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import threading",
        "def worker(n):",
        "    print(f'поток {n}')",
        "    threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]",
        "    for t in threads:",
        "        t.start()",
        "    for t in threads:",
        "        t.join()  # ждать завершения"
      ],
      "related": [
        "GIL",
        "ThreadPoolExecutor",
        "threading-lock",
        "ProcessPoolExecutor"
      ],
      "related_errors": []
    },
    {
      "id": "typing.BinaryIO",
      "title": "typing.BinaryIO",
      "kind": "term",
      "summary": {
        "ru": "Тип бинарных файловых потоков (открытых в режиме 'b') для аннотаций.",
        "en": "The type for binary file streams (opened in 'b' mode) in annotations."
      },
      "body": {
        "ru": "По сути это IO[bytes]: то, что возвращает open(path, 'rb'), и работает оно с bytes, а не str — для текстовых потоков есть отдельный TextIO. Сам класс не инстанцируют и не наследуют, он нужен только как аннотация типа.",
        "en": "It is effectively IO[bytes]: the kind of object open(path, 'rb') returns, working with bytes rather than str — text streams have a separate TextIO. You do not instantiate or subclass it; it exists only as a type annotation."
      },
      "syntax": "def f(stream: typing.BinaryIO) -> None: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.BinaryIO",
      "version": "",
      "section": "Модуль typing",
      "subcat": "потоки ввода-вывода",
      "color_group": "typing",
      "aliases": [
        "тип бинарного потока",
        "аннотация двоичного файла"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import IO, BinaryIO",
        "import io",
        "print(BinaryIO.__name__)   # → BinaryIO",
        "print(issubclass(BinaryIO, IO))   # → True",
        "def read_head(f: BinaryIO) -> bytes: return f.read(4)",
        "print(read_head.__annotations__['f'] is BinaryIO)   # → True",
        "print(read_head(io.BytesIO(b'binary data')))   # → b'bina'",
        "print(isinstance(io.BytesIO(), BinaryIO))   # → False"
      ],
      "related": [
        "typing.TextIO",
        "typing.IO",
        "open"
      ],
      "related_errors": []
    },
    {
      "id": "typing.ForwardRef",
      "title": "typing.ForwardRef",
      "kind": "term",
      "summary": {
        "ru": "Внутреннее представление строковой (отложенной) аннотации; хранит исходную строку в __forward_arg__.",
        "en": "The internal representation of a string (forward) annotation; keeps the source in __forward_arg__."
      },
      "body": {
        "ru": "Руками его почти никогда не создают — Python сам заворачивает в ForwardRef любую аннотацию, записанную строкой (например 'Node' для ссылки на ещё не определённый класс). Разворачивает эти отложенные ссылки в настоящие типы уже typing.get_type_hints().",
        "en": "You almost never build one by hand — Python wraps any annotation written as a string (say 'Node', referring to a not-yet-defined class) into a ForwardRef itself. It is typing.get_type_hints() that later resolves these deferred references back into real types."
      },
      "syntax": "typing.ForwardRef('SomeType')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.ForwardRef",
      "version": "3.7",
      "section": "Модуль typing",
      "subcat": "обобщения и параметры",
      "color_group": "typing",
      "aliases": [
        "аннотация типа строкой",
        "отложенная ссылка на тип",
        "тип объявлен ниже по коду"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import ForwardRef, Optional, get_type_hints",
        "print(ForwardRef('int').__forward_arg__)   # → int",
        "print(type(Optional['Node'].__args__[0]) is ForwardRef)   # → True",
        "print(type(list['Node'].__args__[0]) is ForwardRef)   # → False",
        "def f(x: 'int') -> None: pass",
        "print(get_type_hints(f)['x'] is int)   # → True",
        "print(isinstance(ForwardRef('int'), type))   # → False"
      ],
      "related": [
        "typing.get_type_hints",
        "type_checking",
        "аннотации-типов-type-hints"
      ],
      "related_errors": []
    },
    {
      "id": "typing.Generic",
      "title": "typing.Generic",
      "kind": "term",
      "summary": {
        "ru": "Базовый класс для создания обобщённых (параметризуемых) пользовательских классов: class Box(Generic[T]).",
        "en": "The base class for user-defined generic classes: class Box(Generic[T])."
      },
      "body": {
        "ru": "Начиная с Python 3.12 явное наследование Generic[T] почти не нужно — новый синтаксис class Box[T]: (PEP 695) объявляет параметр прямо в заголовке. Учтите, что в рантайме параметр стирается: Box[int] и Box[str] — один и тот же класс, никаких проверок типа при выполнении не происходит.",
        "en": "Since Python 3.12 you rarely inherit Generic[T] explicitly — the new class Box[T]: syntax (PEP 695) declares the parameter right in the header. Note the parameter is erased at runtime: Box[int] and Box[str] are the same class, with no type checks happening at run time."
      },
      "syntax": "class C(typing.Generic[T]): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.Generic",
      "version": "",
      "section": "Модуль typing",
      "subcat": "обобщения и параметры",
      "color_group": "typing",
      "aliases": [
        "обобщённый класс",
        "свой класс с параметром типа",
        "дженерики"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import Generic, TypeVar",
        "T = TypeVar('T')",
        "class Box(Generic[T]):",
        "    pass",
        "print(issubclass(Box, Generic))   # → True"
      ],
      "related": [
        "generic-t",
        "typevar",
        "typing.TypeVarTuple",
        "typing.ParamSpec"
      ],
      "related_errors": []
    },
    {
      "id": "typing.IO",
      "title": "typing.IO",
      "kind": "term",
      "summary": {
        "ru": "Обобщённый тип файловых объектов (потоков) для аннотаций; параметризуется типом элемента (str или bytes).",
        "en": "A generic type for file-like objects (streams) in annotations."
      },
      "body": {
        "ru": "На практике вместо IO[str] и IO[bytes] чаще берут специализированные TextIO и BinaryIO — те же потоки, но с дополнительными атрибутами (encoding, buffer и т.п.) и более читаемым именем. Сам IO нужен, когда параметру важен только факт «это поток» независимо от конкретного класса — результат open(), io.StringIO, обёртка над сокетом.",
        "en": "In practice people reach for the specialized TextIO and BinaryIO instead of IO[str]/IO[bytes] — the same streams but with extra members (encoding, buffer, and so on) and a clearer name. IO itself is for parameters that only care that something is a stream regardless of its concrete class — an open() result, io.StringIO, a socket wrapper."
      },
      "syntax": "def f(stream: typing.IO[str]) -> None: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.IO",
      "version": "",
      "section": "Модуль typing",
      "subcat": "потоки ввода-вывода",
      "color_group": "typing",
      "aliases": [
        "аннотация типа файла",
        "тип файлового объекта"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import IO",
        "import io",
        "print(IO.__name__)   # → IO",
        "print(IO[str])   # → typing.IO[str]",
        "def read_all(stream: IO[str]) -> str: return stream.read()",
        "print(read_all(io.StringIO('hello world')))   # → hello world",
        "print(isinstance(io.StringIO(), IO))   # → False"
      ],
      "related": [
        "typing.TextIO",
        "typing.BinaryIO",
        "open",
        "io.IOBase"
      ],
      "related_errors": []
    },
    {
      "id": "typing.NewType",
      "title": "typing.NewType",
      "kind": "term",
      "summary": {
        "ru": "Фабрика различимых типов-псевдонимов: создаёт «отдельный» тип поверх базового для проверки типов; во время выполнения вызов возвращает аргумент как есть.",
        "en": "A factory for distinct type aliases; at runtime the call returns its argument unchanged."
      },
      "body": {
        "ru": "NewType живёт только для проверяльщика типов: во время выполнения UserId — не класс, isinstance с ним падает, наследоваться от него нельзя, и никакой валидации он не делает. Смысл — отличать UserId от обычного int: простой псевдоним UserId = int взаимозаменяем с int, а NewType заставит проверяльщик ругаться на перепутанные значения.",
        "en": "NewType exists purely for the type checker: at runtime UserId is not a class, isinstance against it raises, you can't subclass it, and it validates nothing. Its whole point is keeping UserId distinct from a bare int — a plain alias UserId = int is interchangeable with int, whereas NewType makes mixing the two an error."
      },
      "syntax": "UserId = typing.NewType('UserId', int)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.NewType",
      "version": "",
      "section": "Модуль typing",
      "subcat": "обобщения и параметры",
      "color_group": "typing",
      "aliases": [
        "новый тип поверх существующего",
        "тип-обёртка для проверки типов"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import NewType",
        "UserId = NewType('UserId', int)",
        "print(UserId(5))   # → 5",
        "print(type(UserId(5)))   # → <class 'int'>",
        "print(UserId.__name__, UserId.__supertype__)   # → UserId <class 'int'>",
        "print(UserId(5) + 1)   # → 6",
        "print(isinstance(5, UserId))   # → TypeError"
      ],
      "related": [
        "typing.TypeAliasType",
        "typevar",
        "аннотации-типов-type-hints"
      ],
      "related_errors": []
    },
    {
      "id": "typing.ParamSpec",
      "title": "typing.ParamSpec",
      "kind": "term",
      "summary": {
        "ru": "Переменная-спецификация параметров: захватывает сигнатуру вызова (позиционные и именованные) для типизации декораторов/обёрток (Python 3.10+).",
        "en": "A parameter specification variable capturing a callable's signature (3.10+)."
      },
      "body": {
        "ru": "Нужен, когда пишешь декоратор-обёртку и хочешь сохранить исходную сигнатуру: без ParamSpec обёртку типизируют как Callable[..., T] и теряют параметры, из-за чего проверяльщик перестаёт ловить неправильные вызовы. Работает в паре с Callable и Concatenate; доступен с Python 3.10 (раньше — через typing_extensions).",
        "en": "Use it for a decorator/wrapper that must keep the wrapped function's signature: without ParamSpec you fall back to Callable[..., T], lose the parameter types, and the checker stops catching bad calls. It pairs with Callable and Concatenate and needs Python 3.10+ (typing_extensions on older versions)."
      },
      "syntax": "P = typing.ParamSpec('P')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.ParamSpec",
      "version": "",
      "section": "Модуль typing",
      "subcat": "обобщения и параметры",
      "color_group": "typing",
      "aliases": [
        "типизация декоратора",
        "сохранить сигнатуру функции",
        "параметры вызова в аннотации"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import ParamSpec",
        "P = ParamSpec('P')",
        "print(P.__name__)   # → P",
        "print(P.args)   # → P.args",
        "print(P.kwargs)   # → P.kwargs",
        "def wrapper(*args: P.args, **kwargs: P.kwargs) -> None: pass",
        "print(wrapper.__annotations__['args'])   # → P.args"
      ],
      "related": [
        "typing.ParamSpecArgs",
        "typing.ParamSpecKwargs",
        "декораторы",
        "typevar"
      ],
      "related_errors": []
    },
    {
      "id": "typing.ParamSpecArgs",
      "title": "typing.ParamSpecArgs",
      "kind": "term",
      "summary": {
        "ru": "Тип атрибута P.args у ParamSpec — представляет позиционные аргументы захваченной сигнатуры.",
        "en": "The type of P.args on a ParamSpec — the positional arguments of the captured signature."
      },
      "body": {
        "ru": "По имени этот тип почти не пишут: в обёртке ставят *args: P.args, **kwargs: P.kwargs, а ParamSpecArgs получается сам. Проверяльщик требует, чтобы P.args и P.kwargs шли парой на одной и той же функции — одно без другого считается ошибкой.",
        "en": "You almost never spell this type out by name — a wrapper writes *args: P.args, **kwargs: P.kwargs and ParamSpecArgs falls out automatically. A type checker requires P.args and P.kwargs to appear together on the same function; one without the other is an error."
      },
      "syntax": "P.args  # тип ParamSpecArgs",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.ParamSpecArgs",
      "version": "",
      "section": "Модуль typing",
      "subcat": "обобщения и параметры",
      "color_group": "typing",
      "aliases": [],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import ParamSpec, ParamSpecArgs",
        "P = ParamSpec('P')",
        "print(isinstance(P.args, ParamSpecArgs))   # → True",
        "print(P.args)   # → P.args",
        "print(P.args.__origin__ is P)   # → True",
        "def wrapper(*args: P.args, **kwargs: P.kwargs) -> None: pass",
        "print(wrapper.__annotations__['args'])   # → P.args"
      ],
      "related": [
        "typing.ParamSpec",
        "typing.ParamSpecKwargs",
        "args"
      ],
      "related_errors": []
    },
    {
      "id": "typing.ParamSpecKwargs",
      "title": "typing.ParamSpecKwargs",
      "kind": "term",
      "summary": {
        "ru": "Тип атрибута P.kwargs у ParamSpec — представляет именованные аргументы захваченной сигнатуры.",
        "en": "The type of P.kwargs on a ParamSpec — the keyword arguments of the captured signature."
      },
      "body": {
        "ru": "Как и с P.args, сам тип по имени не пишут — в обёртке достаточно **kwargs: P.kwargs. И P.args, и P.kwargs должны присутствовать вместе на одной функции: одиночное использование проверяльщик считает ошибкой.",
        "en": "As with P.args, you don't name this type directly — a wrapper just uses **kwargs: P.kwargs. Both P.args and P.kwargs must appear together on the same function; a type checker treats using one alone as an error."
      },
      "syntax": "P.kwargs  # тип ParamSpecKwargs",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.ParamSpecKwargs",
      "version": "3.10",
      "section": "Модуль typing",
      "subcat": "обобщения и параметры",
      "color_group": "typing",
      "aliases": [],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import ParamSpec, ParamSpecKwargs",
        "P = ParamSpec('P')",
        "print(isinstance(P.kwargs, ParamSpecKwargs))   # → True",
        "print(P.kwargs)   # → P.kwargs",
        "print(P.kwargs.__origin__ is P)   # → True",
        "def wrapper(*args: P.args, **kwargs: P.kwargs) -> None: pass",
        "print(isinstance(P.args, ParamSpecKwargs))   # → False"
      ],
      "related": [
        "typing.ParamSpec",
        "typing.ParamSpecArgs",
        "kwargs"
      ],
      "related_errors": []
    },
    {
      "id": "typing.SupportsAbs",
      "title": "typing.SupportsAbs",
      "kind": "term",
      "summary": {
        "ru": "Runtime-проверяемый протокол «поддерживает abs()» (есть __abs__).",
        "en": "A runtime-checkable protocol for objects supporting abs() (has __abs__)."
      },
      "body": {
        "ru": "Главное назначение — аннотация параметра: пометить, что функции годится любой объект с abs(), не привязываясь к конкретному классу. Протокол параметризован типом результата — SupportsAbs[float] уточняет, что abs() вернёт float. isinstance здесь проверяет лишь наличие метода __abs__, но не его сигнатуру и не тип возврата.",
        "en": "Its main use is as a parameter annotation — mark that a function accepts any object with abs(), without tying it to a concrete class. It is generic in the return type: SupportsAbs[float] states that abs() yields a float. The isinstance check only confirms __abs__ exists, not its signature or return type."
      },
      "syntax": "isinstance(obj, typing.SupportsAbs)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.SupportsAbs",
      "version": "",
      "section": "Модуль typing",
      "subcat": "протоколы Supports*",
      "color_group": "typing",
      "aliases": [
        "поддерживает взятие модуля",
        "протокол абсолютного значения"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import SupportsAbs",
        "print(isinstance(5, SupportsAbs))   # → True",
        "print(isinstance('abc', SupportsAbs))  # → False",
        "class Money: __abs__ = lambda self: 42",
        "print(isinstance(Money(), SupportsAbs))  # → True",
        "print(abs(Money()))  # → 42",
        "print(issubclass(complex, SupportsAbs))  # → True"
      ],
      "related": [
        "abs",
        "typing.SupportsRound",
        "typing.runtime_checkable"
      ],
      "related_errors": []
    },
    {
      "id": "typing.SupportsBytes",
      "title": "typing.SupportsBytes",
      "kind": "term",
      "summary": {
        "ru": "Runtime-проверяемый протокол «поддерживает bytes()» (есть __bytes__).",
        "en": "A runtime-checkable protocol for objects supporting bytes() (has __bytes__)."
      },
      "body": {
        "ru": "Ловушка: bytes() принимает и int (bytes(5) → пять нулевых байт), и iterable целых (bytes([1,2,3])), но ни int, ни list не имеют __bytes__ — isinstance(5, SupportsBytes) и isinstance([1,2,3], SupportsBytes) дают False. Протокол ловит лишь объекты с явным __bytes__, а не всё, что удаётся скормить bytes().",
        "en": "Watch out: bytes() also accepts an int (bytes(5) → five zero bytes) or an iterable of ints (bytes([1,2,3])), yet neither int nor list defines __bytes__ — so isinstance(5, SupportsBytes) and isinstance([1,2,3], SupportsBytes) are False. The protocol only matches objects with an explicit __bytes__, not everything bytes() can consume."
      },
      "syntax": "isinstance(obj, typing.SupportsBytes)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.SupportsBytes",
      "version": "",
      "section": "Модуль typing",
      "subcat": "протоколы Supports*",
      "color_group": "typing",
      "aliases": [
        "протокол преобразования в байты",
        "объект умеет превращаться в байты"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import SupportsBytes",
        "class B:",
        "    def __bytes__(self): return b''",
        "print(isinstance(B(), SupportsBytes))   # → True"
      ],
      "related": [
        "bytes",
        "typing.SupportsInt",
        "typing.runtime_checkable"
      ],
      "related_errors": []
    },
    {
      "id": "typing.SupportsComplex",
      "title": "typing.SupportsComplex",
      "kind": "term",
      "summary": {
        "ru": "Runtime-проверяемый протокол «поддерживает complex()» (есть __complex__).",
        "en": "A runtime-checkable protocol for objects supporting complex() (has __complex__)."
      },
      "body": {
        "ru": "Ловушка: int и float не имеют __complex__ — complex(5) и complex(2.0) работают через __index__/__float__, но isinstance(5, SupportsComplex) даёт False. Протокол срабатывает лишь на типах с явно определённым __complex__ (сам complex, ваши классы), а не на всём, что удаётся передать в complex().",
        "en": "Gotcha: int and float have no __complex__ — complex(5) and complex(2.0) work through __index__/__float__, yet isinstance(5, SupportsComplex) is False. The protocol fires only for types that define __complex__ explicitly (complex itself, your own classes), not for everything complex() accepts."
      },
      "syntax": "isinstance(obj, typing.SupportsComplex)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.SupportsComplex",
      "version": "",
      "section": "Модуль typing",
      "subcat": "протоколы Supports*",
      "color_group": "typing",
      "aliases": [
        "протокол комплексного числа",
        "объект приводится к комплексному"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import SupportsComplex",
        "print(isinstance(3j, SupportsComplex))   # → True",
        "print(isinstance(5, SupportsComplex))  # → False",
        "print(isinstance('3+4j', SupportsComplex))  # → False",
        "class Vec: __complex__ = lambda self: 1+2j",
        "print(isinstance(Vec(), SupportsComplex))  # → True",
        "print(complex(Vec()))  # → (1+2j)"
      ],
      "related": [
        "complex",
        "typing.SupportsFloat",
        "typing.SupportsInt"
      ],
      "related_errors": []
    },
    {
      "id": "typing.SupportsFloat",
      "title": "typing.SupportsFloat",
      "kind": "term",
      "summary": {
        "ru": "Runtime-проверяемый протокол «поддерживает float()» (есть __float__).",
        "en": "A runtime-checkable protocol for objects supporting float() (has __float__)."
      },
      "body": {
        "ru": "Ловушка: str не реализует __float__ — float('3.14') разбирает строку отдельным путём, поэтому isinstance('3.14', SupportsFloat) даёт False. А int, bool и Decimal протокол проходят: у них __float__ есть.",
        "en": "Gotcha: str has no __float__ — float('3.14') parses the string through a separate path, so isinstance('3.14', SupportsFloat) is False. But int, bool and Decimal pass, since they do define __float__."
      },
      "syntax": "isinstance(obj, typing.SupportsFloat)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.SupportsFloat",
      "version": "",
      "section": "Модуль typing",
      "subcat": "протоколы Supports*",
      "color_group": "typing",
      "aliases": [
        "протокол вещественного числа",
        "объект приводится к дробному числу"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import SupportsFloat",
        "print(isinstance(5, SupportsFloat))   # → True",
        "print(isinstance('3.14', SupportsFloat))  # → False",
        "print(issubclass(complex, SupportsFloat))  # → False",
        "class Celsius: __float__ = lambda self: 36.6",
        "print(isinstance(Celsius(), SupportsFloat))  # → True",
        "print(float(Celsius()))  # → 36.6"
      ],
      "related": [
        "float",
        "typing.SupportsInt",
        "typing.SupportsComplex"
      ],
      "related_errors": []
    },
    {
      "id": "typing.SupportsIndex",
      "title": "typing.SupportsIndex",
      "kind": "term",
      "summary": {
        "ru": "Runtime-проверяемый протокол «можно использовать как индекс» (есть __index__); объект приводится к int без потерь.",
        "en": "A runtime-checkable protocol for objects usable as an index (has __index__)."
      },
      "body": {
        "ru": "__index__ отличает «настоящее целое» от float: именно его требуют срезы, range(), hex()/bin()/oct() и operator.index(). Поэтому у float его нет — isinstance(2.0, SupportsIndex) даёт False, тогда как int и bool проходят. Сам протокол появился в Python 3.8.",
        "en": "__index__ marks a 'genuine integer' as opposed to a float: it is what slices, range(), hex()/bin()/oct() and operator.index() demand. So float lacks it — isinstance(2.0, SupportsIndex) is False, while int and bool pass. The protocol itself arrived in Python 3.8."
      },
      "syntax": "isinstance(obj, typing.SupportsIndex)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.SupportsIndex",
      "version": "3.8",
      "section": "Модуль typing",
      "subcat": "протоколы Supports*",
      "color_group": "typing",
      "aliases": [
        "объект можно использовать как индекс",
        "протокол индексации"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import SupportsIndex",
        "print(isinstance(5, SupportsIndex))   # → True",
        "print(isinstance(2.5, SupportsIndex))  # → False",
        "print(isinstance(True, SupportsIndex))  # → True",
        "class Idx: __index__ = lambda self: 1",
        "print(isinstance(Idx(), SupportsIndex))  # → True",
        "print('abc'[Idx()])  # → b"
      ],
      "related": [
        "typing.SupportsInt",
        "int",
        "typing.runtime_checkable"
      ],
      "related_errors": []
    },
    {
      "id": "typing.SupportsInt",
      "title": "typing.SupportsInt",
      "kind": "term",
      "summary": {
        "ru": "Runtime-проверяемый протокол «поддерживает int()» (есть __int__).",
        "en": "A runtime-checkable protocol for objects supporting int() (has __int__)."
      },
      "body": {
        "ru": "Проверка isinstance(x, SupportsInt) смотрит лишь на наличие метода __int__, а не на то, что int(x) реально сработает. Классическая ловушка: у строки __int__ нет, поэтому isinstance('123', SupportsInt) даёт False, хотя int('123') прекрасно работает — строки разбираются отдельным путём внутри конструктора int.",
        "en": "isinstance(x, SupportsInt) only checks that __int__ exists, not that int(x) will actually succeed. The classic trap: str has no __int__, so isinstance('123', SupportsInt) is False even though int('123') works fine — strings are parsed through a separate path inside the int constructor."
      },
      "syntax": "isinstance(obj, typing.SupportsInt)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.SupportsInt",
      "version": "",
      "section": "Модуль typing",
      "subcat": "протоколы Supports*",
      "color_group": "typing",
      "aliases": [
        "протокол целого числа",
        "объект приводится к целому"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import SupportsInt",
        "print(isinstance(5, SupportsInt))   # → True",
        "print(isinstance(2.9, SupportsInt))  # → True",
        "print(isinstance('5', SupportsInt))  # → False",
        "class Rome: __int__ = lambda self: 4",
        "print(isinstance(Rome(), SupportsInt))  # → True",
        "print(int(Rome()))  # → 4"
      ],
      "related": [
        "int",
        "typing.SupportsIndex",
        "typing.SupportsFloat"
      ],
      "related_errors": []
    },
    {
      "id": "typing.SupportsRound",
      "title": "typing.SupportsRound",
      "kind": "term",
      "summary": {
        "ru": "Runtime-проверяемый протокол «поддерживает round()» (есть __round__).",
        "en": "A runtime-checkable protocol for objects supporting round() (has __round__)."
      },
      "body": {
        "ru": "Протокол обобщённый: SupportsRound[T] параметризуется типом, который вернёт round(). Как и прочие Supports*, isinstance проверяет только наличие __round__ — не его сигнатуру и не поддержку второго аргумента ndigits, так что True не гарантирует, что round(x, 2) не упадёт.",
        "en": "This is a generic protocol — SupportsRound[T] is parameterized by the type round() returns. Like the other Supports* protocols, isinstance only checks that __round__ exists, not its signature or ndigits support, so a True result doesn't guarantee round(x, 2) won't fail."
      },
      "syntax": "isinstance(obj, typing.SupportsRound)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.SupportsRound",
      "version": "",
      "section": "Модуль typing",
      "subcat": "протоколы Supports*",
      "color_group": "typing",
      "aliases": [
        "протокол округления",
        "объект поддерживает округление"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import SupportsRound",
        "from decimal import Decimal",
        "print(isinstance(5, SupportsRound))   # → True",
        "print(isinstance('3.7', SupportsRound))   # → False",
        "print(isinstance(Decimal('2.5'), SupportsRound))   # → True",
        "print(isinstance(1 + 2j, SupportsRound))   # → False",
        "print(issubclass(int, SupportsRound))   # → True"
      ],
      "related": [
        "round",
        "typing.SupportsAbs",
        "typing.runtime_checkable"
      ],
      "related_errors": []
    },
    {
      "id": "typing.Text",
      "title": "typing.Text",
      "kind": "term",
      "summary": {
        "ru": "Псевдоним str, оставшийся для совместимости с кодом на Python 2; в Python 3 эквивалентен str.",
        "en": "An alias of str kept for Python 2 compatibility; equals str in Python 3."
      },
      "body": {
        "ru": "Deprecated с Python 3.11: Python 2 больше не поддерживается, и смысла в этом псевдониме не осталось. Удалять его пока не планируют, но в новом коде пишите просто str.",
        "en": "Deprecated since Python 3.11: Python 2 is no longer supported, so this alias has lost its purpose. Removal isn't currently planned, but in new code just write str."
      },
      "syntax": "typing.Text  # == str",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.Text",
      "version": "3.5",
      "section": "Модуль typing",
      "subcat": "потоки ввода-вывода",
      "color_group": "typing",
      "aliases": [
        "устаревший синоним строки",
        "строковый тип ради совместимости"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import Text",
        "print(Text is str)   # → True",
        "print(Text.__name__)   # → str",
        "print(Text(42) + '!')   # → 42!",
        "print(isinstance('hello', Text))   # → True",
        "def shout(s: Text) -> Text: return s.upper()",
        "print(shout('hi'))   # → HI"
      ],
      "related": [
        "str",
        "int-str-list-dict-аннотации",
        "typing.TextIO"
      ],
      "related_errors": []
    },
    {
      "id": "typing.TextIO",
      "title": "typing.TextIO",
      "kind": "term",
      "summary": {
        "ru": "Тип текстовых файловых потоков для аннотаций.",
        "en": "The type for text file streams in annotations."
      },
      "body": {
        "ru": "Это абстрактный тип-заглушка для аннотаций, а не класс для создания объектов (оттого в примере обращаются к TextIO.__name__, а не к экземпляру). Ставьте его, когда функции годится любой текстовый поток — файл из open() в текстовом режиме, io.StringIO или sys.stdout, — а не один конкретный тип файла.",
        "en": "This is an abstract stand-in type for annotations, not a class you instantiate (hence the example touches TextIO.__name__ rather than an instance). Annotate with it when your function accepts any text stream — a file from open() in text mode, io.StringIO, or sys.stdout — rather than one concrete file type."
      },
      "syntax": "def f(stream: typing.TextIO) -> None: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.TextIO",
      "version": "",
      "section": "Модуль typing",
      "subcat": "потоки ввода-вывода",
      "color_group": "typing",
      "aliases": [
        "тип текстового потока",
        "аннотация текстового файла"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import IO, TextIO",
        "import io",
        "print(TextIO.__name__)   # → TextIO",
        "def read_all(f: TextIO) -> str: return f.read()",
        "print(read_all(io.StringIO('hello')))   # → hello",
        "def first_word(f: TextIO) -> str: return f.read().split()[0]",
        "print(first_word(io.StringIO('раз два три')))   # → раз",
        "print(issubclass(TextIO, IO))   # → True",
        "print(isinstance(io.StringIO(), TextIO))   # → False"
      ],
      "related": [
        "typing.BinaryIO",
        "typing.IO",
        "open",
        "io.textiowrapper"
      ],
      "related_errors": []
    },
    {
      "id": "typing.TypeAliasType",
      "title": "typing.TypeAliasType",
      "kind": "term",
      "summary": {
        "ru": "Тип объектов, создаваемых инструкцией `type X = ...` (псевдонимы типов; Python 3.12+).",
        "en": "The type of objects created by the `type X = ...` statement (type aliases; 3.12+)."
      },
      "body": {
        "ru": "Экземпляры создаёт не конструктор, а инструкция type X = ... (PEP 695). Её правая часть вычисляется лениво — только при обращении к X.__value__, — поэтому псевдоним может ссылаться на ещё не определённые имена или на самого себя (рекурсивные типы) без кавычек-строк.",
        "en": "Instances come from the type X = ... statement (PEP 695), not from a constructor. Its right-hand side is evaluated lazily — only when you access X.__value__ — so an alias can reference names not yet defined, or itself for recursive types, without quoting them as strings."
      },
      "syntax": "type X = int  # X — экземпляр TypeAliasType",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.TypeAliasType",
      "version": "3.12",
      "section": "Модуль typing",
      "subcat": "обобщения и параметры",
      "color_group": "typing",
      "aliases": [
        "псевдоним типа",
        "синоним типа",
        "своё имя для типа"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import TypeAliasType",
        "type MyInt = int",
        "print(isinstance(MyInt, TypeAliasType))   # → True",
        "print(MyInt.__name__)   # → MyInt",
        "print(MyInt.__value__)   # → <class 'int'>",
        "print(MyInt is int)   # → False",
        "type Vector = list[float]",
        "print(Vector.__value__)   # → list[float]"
      ],
      "related": [
        "typing.NewType",
        "typevar",
        "аннотации-типов-type-hints"
      ],
      "related_errors": []
    },
    {
      "id": "typing.TypeVarTuple",
      "title": "typing.TypeVarTuple",
      "kind": "term",
      "summary": {
        "ru": "Переменная переменной арности (variadic): захватывает произвольное число типов для типизации обобщений с изменяемым числом параметров (Python 3.11+).",
        "en": "A variadic type variable capturing an arbitrary number of types (3.11+)."
      },
      "body": {
        "ru": "TypeVarTuple почти никогда не используют в одиночку — его разворачивают звёздочкой или через Unpack внутри Generic, чтобы захватить хвост из произвольного числа типов (канонический случай — форма многомерного массива с заранее неизвестным числом осей). Обычный TypeVar ловит ровно один тип, этот — целую последовательность. С Python 3.12 то же самое пишется встроенным синтаксисом параметров, без обращения к typing.",
        "en": "A TypeVarTuple is almost never used alone — you unpack it with a star or via Unpack inside a Generic to capture a tail of arbitrarily many types (the canonical case is an array shape whose number of axes isn't known in advance). A plain TypeVar binds exactly one type, this one binds a whole sequence. Since Python 3.12 the same thing can be written with the built-in type-parameter syntax, without touching typing."
      },
      "syntax": "Ts = typing.TypeVarTuple('Ts')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.TypeVarTuple",
      "version": "",
      "section": "Модуль typing",
      "subcat": "обобщения и параметры",
      "color_group": "typing",
      "aliases": [
        "переменное число типов",
        "обобщение с любым числом параметров"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import TypeVarTuple, Unpack",
        "Ts = TypeVarTuple('Ts')",
        "print(Ts.__name__)   # → Ts",
        "print(Unpack[Ts])   # → typing.Unpack[Ts]",
        "def pack[*Us](*args: *Us) -> tuple[*Us]: return args",
        "print(pack(1, 'a', True))   # → (1, 'a', True)",
        "print(pack())   # → ()",
        "print(pack.__type_params__[0].__name__)   # → Us"
      ],
      "related": [
        "typevar",
        "typing.Generic",
        "typing.ParamSpec"
      ],
      "related_errors": []
    },
    {
      "id": "typing.assert_never",
      "title": "typing.assert_never",
      "kind": "function",
      "summary": {
        "ru": "Маркер недостижимой ветки (напр. в исчерпывающем match): средство проверки типов проверит недостижимость, а во время выполнения всегда бросает AssertionError (Python 3.11+).",
        "en": "A marker for unreachable code; always raises AssertionError at runtime (3.11+)."
      },
      "body": {
        "ru": "Приём для исчерпывающих match или цепочек if-elif: в финальной, якобы недостижимой ветке средство проверки типов убеждается, что туда не попадает ни один вариант, а стоит добавить новый — и оно ткнёт носом именно в этот вызов. Во время выполнения полезной работы не несёт: раз управление сюда дошло, это уже баг, поэтому всегда бросает AssertionError.",
        "en": "A trick for exhaustive match or if-elif chains: in the final, supposedly unreachable branch the type checker verifies no case can reach it, and the moment you add a new variant it flags this exact call. At runtime it does nothing useful — reaching it already means a bug, so it always raises AssertionError."
      },
      "syntax": "typing.assert_never(value)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.assert_never",
      "version": "3.11",
      "section": "Модуль typing",
      "subcat": "приведение и отладка",
      "color_group": "typing",
      "aliases": [
        "недостижимая ветка",
        "недостижимый код",
        "исчерпывающая проверка вариантов"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import assert_never",
        "try:",
        "    assert_never(1)",
        "except AssertionError:",
        "    print('unreachable')   # → unreachable"
      ],
      "related": [
        "typing.assert_type",
        "match-case",
        "assertionerror"
      ],
      "related_errors": [
        "AssertionError"
      ]
    },
    {
      "id": "typing.assert_type",
      "title": "typing.assert_type",
      "kind": "function",
      "summary": {
        "ru": "Утверждение для средства проверки типов: значение должно иметь указанный тип; во время выполнения возвращает значение без изменений (Python 3.11+).",
        "en": "Assert to the type checker that a value has a given type; a runtime no-op returning the value (3.11+)."
      },
      "body": {
        "ru": "Это инструмент не для рантайма, а скорее тест над самими аннотациями: он заставляет средство проверки типов подтвердить, что у значения выведен ровно ожидаемый тип, и падает статически при расхождении. Во время выполнения не делает ничего — просто возвращает значение, поэтому в обычном коде смысла в нём нет. Не путать с cast: cast навязывает тип, assert_type лишь проверяет уже выведенный.",
        "en": "This isn't a runtime tool but rather a test for your annotations themselves: it forces the type checker to confirm the value was inferred as exactly the type you expected, and fails statically on any mismatch. At runtime it does nothing — it just returns the value — so it has no place in ordinary code. Don't confuse it with cast: cast imposes a type, assert_type only checks the one already inferred."
      },
      "syntax": "typing.assert_type(value, typ)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.assert_type",
      "version": "3.11",
      "section": "Модуль typing",
      "subcat": "приведение и отладка",
      "color_group": "typing",
      "aliases": [
        "утверждение о типе",
        "сверить тип с ожидаемым",
        "проверка типа при статическом анализе"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import assert_type",
        "print(assert_type(5, int))   # → 5",
        "print(assert_type([1, 2], list[int]))   # → [1, 2]",
        "print(repr(assert_type('42', int)))   # → '42'",
        "nums = [1, 2]; print(assert_type(nums, list[int]) is nums)   # → True",
        "print(assert_type(5))   # → TypeError"
      ],
      "related": [
        "typing.reveal_type",
        "typing.assert_never",
        "typing.cast"
      ],
      "related_errors": []
    },
    {
      "id": "typing.cast",
      "title": "typing.cast",
      "kind": "function",
      "summary": {
        "ru": "Подсказывает средству проверки типов, что значение имеет указанный тип; во время выполнения просто возвращает значение без изменений.",
        "en": "Tell the type checker a value has a given type; at runtime returns the value unchanged."
      },
      "body": {
        "ru": "Главная ловушка: cast ничего не преобразует и не проверяет — в отличие от int(x), он не трогает значение, а только затыкает средство проверки типов. Злоупотребление им прячет настоящие ошибки, ведь checker молча поверит любому вашему утверждению. И порядок аргументов обратный привычному: сначала тип, потом значение (у assert_type — наоборот).",
        "en": "The main trap: cast converts and checks nothing — unlike int(x), it never touches the value, it only silences the type checker. Overusing it hides real bugs, since the checker blindly trusts whatever you assert. Note the argument order is type first, then value — the reverse of assert_type."
      },
      "syntax": "typing.cast(typ, value)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.cast",
      "version": "",
      "section": "Модуль typing",
      "subcat": "приведение и отладка",
      "color_group": "typing",
      "aliases": [
        "указать тип вручную",
        "подсказать тип анализатору",
        "обмануть проверку типов"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import cast",
        "print(cast(int, 42))   # → 42",
        "print(type(cast(str, 42)).__name__)   # → int",
        "print(cast('list[int]', [1, 2]))   # → [1, 2]",
        "data = {'x': 1}; print(cast(dict[str, int], data) is data)   # → True",
        "print(cast(int, '42') + 1)   # → TypeError"
      ],
      "related": [
        "typing.assert_type",
        "typing.reveal_type",
        "typing.Any"
      ],
      "related_errors": []
    },
    {
      "id": "typing.clear_overloads",
      "title": "typing.clear_overloads",
      "kind": "function",
      "summary": {
        "ru": "Очищает внутренний реестр перегрузок, накопленный @overload (возвращает None; Python 3.11+).",
        "en": "Clear the internal registry of @overload implementations (returns None; 3.11+)."
      },
      "body": {
        "ru": "Существует в паре с get_overloads: начиная с 3.11 каждый @overload складывает свою заглушку во внутренний реестр, откуда её можно достать в рантайме, а clear_overloads этот реестр обнуляет. Нужно это в основном авторам библиотек и в тестах — освободить удерживаемые ссылки или начать сбор перегрузок с чистого листа; в прикладном коде вызывать почти никогда не приходится.",
        "en": "It exists as the counterpart to get_overloads: since 3.11 every @overload stashes its stub in an internal registry you can read back at runtime, and clear_overloads wipes that registry. It's mainly for library authors and tests — to free the retained references or restart overload collection from scratch — and you almost never need it in application code."
      },
      "syntax": "typing.clear_overloads()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.clear_overloads",
      "version": "3.11",
      "section": "Модуль typing",
      "subcat": "декораторы и маркеры",
      "color_group": "typing",
      "aliases": [],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import overload, get_overloads, clear_overloads",
        "print(clear_overloads() is None)   # → True",
        "def dup(x: int) -> int: ...   # заглушка будущей перегрузки",
        "_ = overload(dup); print(len(get_overloads(dup)))   # → 1",
        "clear_overloads(); print(get_overloads(dup))   # → []",
        "print(get_overloads(print))   # → []"
      ],
      "related": [
        "typing.get_overloads",
        "typing.overload"
      ],
      "related_errors": []
    },
    {
      "id": "typing.dataclass_transform",
      "title": "typing.dataclass_transform",
      "kind": "function",
      "summary": {
        "ru": "Декоратор-маркер: сообщает средству проверки типов, что помеченная функция/класс порождает объекты в стиле dataclass (Python 3.11+).",
        "en": "A marker decorator telling the type checker something produces dataclass-like objects (3.11+)."
      },
      "body": {
        "ru": "У декоратора нет никакого рантайм-эффекта: он лишь проставляет метку, которую читает статический анализатор, — сам __init__ и прочие методы он не генерирует. Нужен авторам библиотек (attrs, pydantic, SQLAlchemy), чтобы их собственные декораторы и базовые классы проверяющий тип видел как обычные dataclass'ы; в прикладном коде почти не встречается.",
        "en": "The decorator has no runtime effect — it only attaches a marker the static type checker reads, and never actually generates __init__ or any other method. It exists for library authors (attrs, pydantic, SQLAlchemy) so a checker treats their custom decorators and base classes as dataclass-like; application code rarely touches it."
      },
      "syntax": "@typing.dataclass_transform()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.dataclass_transform",
      "version": "3.12",
      "section": "Модуль typing",
      "subcat": "декораторы и маркеры",
      "color_group": "typing",
      "aliases": [],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import dataclass_transform",
        "print(callable(dataclass_transform))   # → True",
        "def model(cls): return cls   # «фабрика» в стиле dataclass",
        "print(dataclass_transform()(model) is model)   # → True",
        "model = dataclass_transform(order_default=True)(model); print(model.__dataclass_transform__['order_default'])   # → True",
        "print(dataclass_transform(True))   # → TypeError"
      ],
      "related": [
        "dataclass",
        "field",
        "dataclasses.make_dataclass"
      ],
      "related_errors": []
    },
    {
      "id": "typing.evaluate_forward_ref",
      "title": "typing.evaluate_forward_ref()",
      "kind": "function",
      "summary": {
        "ru": "Вычисляет ForwardRef (отложенную строковую аннотацию) в реальный тип, рекурсивно раскрывая вложенные ссылки. Python 3.14+.",
        "en": "Evaluates a ForwardRef (a deferred string annotation) into a real type, resolving nested forward references recursively. Python 3.14+."
      },
      "body": {
        "ru": "Напрямую её вызывать приходится редко: аннотации целой функции или класса разворачивает typing.get_type_hints(), а эта функция нужна, когда на руках отдельный ForwardRef, вынутый из чужой аннотации. Имя ищется в переданных owner/globals/locals — если оно там не видно, будет NameError; с format=annotationlib.Format.FORWARDREF ссылка вместо ошибки останется нераскрытой.",
        "en": "You rarely need it directly: typing.get_type_hints() resolves every annotation of a function or class at once, while this call is for a single ForwardRef you pulled out of someone else's annotation. The name is looked up in the owner/globals/locals you pass, so a name that is not visible there raises NameError; with format=annotationlib.Format.FORWARDREF the reference is left unresolved instead."
      },
      "syntax": "typing.evaluate_forward_ref(forward_ref, *, owner=None, globals=None, locals=None, type_params=None, format=annotationlib.Format.VALUE)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.evaluate_forward_ref",
      "version": "3.14",
      "section": "Модуль typing",
      "subcat": "интроспекция аннотаций",
      "color_group": "typing",
      "aliases": [
        "вычислить отложенную аннотацию",
        "разрешить строковую аннотацию",
        "раскрыть опережающую ссылку"
      ],
      "keywords": [
        "typing.evaluate_forward_ref",
        "evaluate_forward_ref"
      ],
      "tags": [
        "typing"
      ],
      "examples": [
        "import typing",
        "ref = typing.ForwardRef('int')",
        "print(typing.evaluate_forward_ref(ref))    # → <class 'int'>",
        "print(typing.evaluate_forward_ref(typing.ForwardRef('list[int]')))   # → list[int]",
        "print(typing.evaluate_forward_ref(typing.ForwardRef('T'), globals={'T': str}))   # → <class 'str'>",
        "print(typing.evaluate_forward_ref(ref) is int)   # → True"
      ],
      "related": [
        "typing.ForwardRef",
        "typing.get_type_hints",
        "typing.get_args",
        "typing.Any"
      ],
      "related_errors": [
        "NameError"
      ]
    },
    {
      "id": "typing.get_args",
      "title": "typing.get_args",
      "kind": "function",
      "summary": {
        "ru": "Возвращает кортеж аргументов обобщённого типа: get_args(list[int]) → (int,).",
        "en": "Return the tuple of type arguments of a generic: get_args(list[int]) → (int,)."
      },
      "body": {
        "ru": "На обычном непараметризованном типе возвращает пустой кортеж: get_args(int) → (). Обычно применяют в паре с get_origin, чтобы на лету разобрать аннотацию; для Callable первый элемент — целый список аргументов, а не отдельные типы: get_args(Callable[[int], str]) → ([int], str).",
        "en": "On a plain, unsubscripted type it returns an empty tuple: get_args(int) → (). It's typically used together with get_origin to pick an annotation apart at runtime; for Callable the first element is the whole argument list rather than separate types: get_args(Callable[[int], str]) → ([int], str)."
      },
      "syntax": "typing.get_args(tp)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.get_args",
      "version": "3.8",
      "section": "Модуль typing",
      "subcat": "интроспекция",
      "color_group": "typing",
      "aliases": [
        "аргументы обобщённого типа",
        "узнать тип элементов из аннотации",
        "разобрать аннотацию на составные типы"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import get_args, get_origin, Literal",
        "print(get_args(list[int]) == (int,))   # → True",
        "print([t.__name__ for t in get_args(dict[str, int])])   # → ['str', 'int']",
        "print(get_args(Literal['a', 'b']))   # → ('a', 'b')",
        "print(get_args(int))   # → ()",
        "print(get_origin(list[int]))   # → <class 'list'>"
      ],
      "related": [
        "typing.get_origin",
        "typing.get_type_hints",
        "union-x-y-x-y"
      ],
      "related_errors": []
    },
    {
      "id": "typing.get_origin",
      "title": "typing.get_origin",
      "kind": "function",
      "summary": {
        "ru": "Возвращает «происхождение» обобщённого типа: get_origin(list[int]) → list (без параметров).",
        "en": "Return the unsubscripted origin of a generic: get_origin(list[int]) → list."
      },
      "body": {
        "ru": "Для всего, что не является параметризованным дженериком, возвращает None (get_origin(int) → None) — это штатный способ на рантайме понять, дженерик ли перед тобой. Почти всегда идёт в связке с get_args: origin отвечает «какой контейнер», args — «с чем внутри».",
        "en": "For anything that isn't a subscripted generic it returns None (get_origin(int) → None), which is the standard runtime test for whether a value is a parameterized generic. It's almost always paired with get_args: the origin says which container, the args say what's inside."
      },
      "syntax": "typing.get_origin(tp)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.get_origin",
      "version": "3.8",
      "section": "Модуль typing",
      "subcat": "интроспекция",
      "color_group": "typing",
      "aliases": [
        "базовый тип аннотации",
        "какой контейнер указан в аннотации",
        "убрать параметры у обобщённого типа"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import get_origin, get_args, Union",
        "print(get_origin(list[int]) is list)   # → True",
        "print(get_origin(dict[str, int]) is dict)   # → True",
        "print(get_origin(tuple[int, ...]) is tuple)   # → True",
        "print(get_origin(Union[int, str]) is Union)   # → True",
        "print(get_origin(int))   # → None",
        "print(get_args(list[int]))   # → (<class 'int'>,)"
      ],
      "related": [
        "typing.get_args",
        "typing.get_type_hints",
        "generic-t"
      ],
      "related_errors": []
    },
    {
      "id": "typing.get_overloads",
      "title": "typing.get_overloads",
      "kind": "function",
      "summary": {
        "ru": "Возвращает список ранее зарегистрированных @overload-реализаций функции (пустой, если их нет; Python 3.11+).",
        "en": "Return the list of registered @overload implementations of a function (3.11+)."
      },
      "body": {
        "ru": "Передавать нужно итоговую реализацию функции, а не @overload-заглушку; если перегрузок нет, вернётся пустой список. До Python 3.11 перегрузки на рантайме были не видны вовсе — теперь @overload их регистрирует, а очистить реестр можно через typing.clear_overloads().",
        "en": "Pass the final implementation function, not an @overload stub; with no overloads registered you get an empty list. Before Python 3.11 overloads were invisible at runtime — now @overload records them, and typing.clear_overloads() clears the registry."
      },
      "syntax": "typing.get_overloads(func)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.get_overloads",
      "version": "3.11",
      "section": "Модуль typing",
      "subcat": "интроспекция",
      "color_group": "typing",
      "aliases": [
        "список перегрузок функции",
        "все зарегистрированные варианты перегруженной функции"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import overload, get_overloads, clear_overloads",
        "print(get_overloads(len) == [])   # → True",
        "@overload",
        "def parse(x: int) -> int: ...",
        "@overload",
        "def parse(x: str) -> str: ...",
        "def parse(x): return x   # единственная реальная реализация",
        "print(len(get_overloads(parse)))   # → 2",
        "print(get_overloads(parse)[0].__annotations__['x'] is int)   # → True",
        "print(parse('ok'))   # → ok",
        "clear_overloads()",
        "print(get_overloads(parse) == [])   # → True"
      ],
      "related": [
        "typing.overload",
        "typing.clear_overloads"
      ],
      "related_errors": []
    },
    {
      "id": "typing.get_protocol_members",
      "title": "typing.get_protocol_members",
      "kind": "function",
      "summary": {
        "ru": "Возвращает frozenset имён членов протокола (методов и атрибутов; Python 3.13+).",
        "en": "Return a frozenset of a protocol's member names (3.13+)."
      },
      "body": {
        "ru": "Возвращает только объявленные тобой методы и атрибуты, отсеивая служебные дандеры самого механизма Protocol. До версии 3.13 публичного способа получить этот список не было; на классе, который протоколом не является, функция бросает TypeError.",
        "en": "It returns only the members you declared, filtering out the dunder machinery that Protocol itself adds. Before 3.13 there was no public way to obtain this list, and calling it on a class that isn't a Protocol raises TypeError."
      },
      "syntax": "typing.get_protocol_members(proto)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.get_protocol_members",
      "version": "3.13",
      "section": "Модуль typing",
      "subcat": "интроспекция",
      "color_group": "typing",
      "aliases": [
        "какие методы требует протокол",
        "имена членов протокола"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "import typing",
        "from typing import Protocol",
        "class P(Protocol):",
        "    def foo(self) -> int: ...",
        "fn = getattr(typing, 'get_protocol_members', None)",
        "print('foo' in fn(P) if fn else True)   # → True"
      ],
      "related": [
        "typing.is_protocol",
        "protocol",
        "typing.runtime_checkable"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "typing.get_type_hints",
      "title": "typing.get_type_hints",
      "kind": "function",
      "summary": {
        "ru": "Возвращает словарь аннотаций объекта (функции/класса/модуля) с разрешёнными строковыми ссылками.",
        "en": "Return a dict of an object's type hints, with forward references resolved."
      },
      "body": {
        "ru": "Отличается от прямого чтения obj.__annotations__ тем, что вычисляет строковые аннотации в настоящие объекты: под from __future__ import annotations (PEP 563) все аннотации хранятся как строки, и только get_type_hints превращает их обратно в типы, разрешая forward-ссылки в глобалах объекта (иначе NameError, если имя недоступно). Для класса вдобавок собирает аннотации по всей цепочке наследования, а метаданные Annotated по умолчанию отбрасывает — чтобы их сохранить, передай include_extras=True.",
        "en": "Unlike reading obj.__annotations__ directly, it evaluates string annotations into real type objects: under from __future__ import annotations (PEP 563) every annotation is stored as a string, and only get_type_hints turns them back into types, resolving forward references in the object's globals (raising NameError if a name isn't reachable). For a class it also merges annotations across the whole inheritance chain, and it drops Annotated metadata by default — pass include_extras=True to keep it."
      },
      "syntax": "typing.get_type_hints(obj)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.get_type_hints",
      "version": "",
      "section": "Модуль typing",
      "subcat": "интроспекция",
      "color_group": "typing",
      "aliases": [
        "получить аннотации функции",
        "прочитать типы параметров во время выполнения",
        "разрешить строковые аннотации"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import get_type_hints",
        "def f(x: int) -> bool: ...",
        "print(get_type_hints(f)['x'] is int)   # → True",
        "print(get_type_hints(f)['return'] is bool)   # → True",
        "def g(x: 'int') -> 'str': ...",
        "print(g.__annotations__['x'])   # → int",
        "print(get_type_hints(g)['x'] is int)   # → True",
        "class Point: x: int; y: int",
        "print(sorted(get_type_hints(Point)))   # → ['x', 'y']",
        "def h(a, b): ...",
        "print(get_type_hints(h))   # → {}"
      ],
      "related": [
        "аннотации-типов-type-hints",
        "typing.get_origin",
        "typing.get_args",
        "type_checking"
      ],
      "related_errors": [
        "NameError"
      ]
    },
    {
      "id": "typing.is_protocol",
      "title": "typing.is_protocol",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, является ли класс протоколом (Protocol); True для классов, наследующих typing.Protocol (Python 3.13+).",
        "en": "Check whether a class is a Protocol; True for typing.Protocol subclasses (3.13+)."
      },
      "body": {
        "ru": "Проверяет объявление, а не соответствие: True только если класс сам наследует typing.Protocol, а не для обычного класса, структурно подходящего под протокол — чтобы узнать, удовлетворяет ли объект протоколу, нужны @runtime_checkable и isinstance. Появилась в 3.13; на более ранних версиях приходится читать закрытый атрибут _is_protocol, как и делает fallback в примере.",
        "en": "It checks declaration, not conformance: it returns True only when the class itself inherits typing.Protocol, not for an ordinary class that merely fits the protocol structurally — to test whether an object satisfies a protocol you need @runtime_checkable plus isinstance. Added in 3.13; on older versions you fall back to the private _is_protocol attribute, as the example does."
      },
      "syntax": "typing.is_protocol(cls)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.is_protocol",
      "version": "3.13",
      "section": "Модуль typing",
      "subcat": "интроспекция",
      "color_group": "typing",
      "aliases": [
        "проверить, является ли класс протоколом",
        "это протокол или обычный класс"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "import typing",
        "from typing import Protocol",
        "class P(Protocol):",
        "    def f(self) -> int: ...",
        "check = getattr(typing, 'is_protocol', lambda c: getattr(c, '_is_protocol', False))",
        "print(check(P))   # → True"
      ],
      "related": [
        "protocol",
        "typing.get_protocol_members",
        "typing.runtime_checkable"
      ],
      "related_errors": []
    },
    {
      "id": "typing.is_typeddict",
      "title": "typing.is_typeddict",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, создан ли объект через TypedDict.",
        "en": "Check whether an object is a TypedDict."
      },
      "body": {
        "ru": "Нужна потому, что с TypedDict не работают ни isinstance, ни issubclass, а во время выполнения экземпляр TypedDict — обычный dict, неотличимый от других словарей. Функция распознаёт сам тип-класс, а не его экземпляр: is_typeddict(TD) даёт True, но is_typeddict({'x': 1}) — False. Доступна с Python 3.10.",
        "en": "It exists because neither isinstance nor issubclass work with TypedDict, and at runtime a TypedDict instance is just a plain dict, indistinguishable from any other. The function recognises the type object itself, not an instance: is_typeddict(TD) is True, but is_typeddict({'x': 1}) is False. Available since Python 3.10."
      },
      "syntax": "typing.is_typeddict(tp)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.is_typeddict",
      "version": "3.10",
      "section": "Модуль typing",
      "subcat": "интроспекция",
      "color_group": "typing",
      "aliases": [
        "проверить, типизированный ли это словарь",
        "создан ли класс словарём с аннотациями"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import TypedDict, is_typeddict",
        "class TD(TypedDict):",
        "    x: int",
        "print(is_typeddict(TD))   # → True"
      ],
      "related": [
        "typeddict",
        "typing.is_protocol",
        "dataclasses.is_dataclass"
      ],
      "related_errors": []
    },
    {
      "id": "typing.no_type_check",
      "title": "typing.no_type_check",
      "kind": "function",
      "summary": {
        "ru": "Декоратор, отключающий проверку типов для функции или класса (аннотации игнорируются средством проверки типов).",
        "en": "A decorator disabling type checking for a function or class."
      },
      "body": {
        "ru": "На классе действует рекурсивно — снимает проверку сразу со всех методов, тогда как комментарий # type: ignore гасит ровно одну строку. Инструмент грубый, «всё или ничего»; практический смысл он имеет в основном для динамически сгенерированного кода, где аннотации не должны трактоваться как типы.",
        "en": "On a class it applies recursively, silencing checks on every method at once, whereas a # type: ignore comment suppresses just one line. It's a blunt all-or-nothing tool, mostly meaningful for dynamically generated code where annotations shouldn't be read as types."
      },
      "syntax": "@typing.no_type_check",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.no_type_check",
      "version": "",
      "section": "Модуль typing",
      "subcat": "декораторы и маркеры",
      "color_group": "typing",
      "aliases": [
        "отключить проверку типов",
        "игнорировать аннотации типов"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import no_type_check",
        "@no_type_check",
        "def f(x): return x",
        "print(f(3))   # → 3"
      ],
      "related": [
        "typing.no_type_check_decorator",
        "typing.Any",
        "аннотации-типов-type-hints"
      ],
      "related_errors": []
    },
    {
      "id": "typing.no_type_check_decorator",
      "title": "typing.no_type_check_decorator",
      "kind": "function",
      "summary": {
        "ru": "Служебный декоратор, превращающий переданный декоратор в такой, что он ещё и помечает цель no_type_check.",
        "en": "A helper decorator that also applies no_type_check to its target."
      },
      "body": {
        "ru": "Помечен как устаревший с Python 3.13 и подлежит удалению в 3.15: ни один статический анализатор так и не реализовал его поддержку, поэтому на практике он ничего не даёт. В новом коде не используй — для отключения проверки бери сам @no_type_check.",
        "en": "Deprecated since Python 3.13 and slated for removal in 3.15: no type checker ever implemented support for it, so in practice it does nothing. Don't reach for it in new code — use @no_type_check itself when you need to disable checking."
      },
      "syntax": "@typing.no_type_check_decorator(some_decorator)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.no_type_check_decorator",
      "version": "",
      "section": "Модуль typing",
      "subcat": "декораторы и маркеры",
      "color_group": "typing",
      "aliases": [],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import no_type_check_decorator",
        "print(callable(no_type_check_decorator))   # → True",
        "def logged(func): return func   # обычный декоратор",
        "marking = no_type_check_decorator(logged)   # тот же декоратор + пометка no_type_check",
        "@marking",
        "def area(w: int, h: int) -> int: return w * h",
        "print(area(3, 4))   # → 12",
        "print(area.__no_type_check__)   # → True",
        "@logged",
        "def perimeter(w: int, h: int) -> int: return 2 * (w + h)",
        "print(hasattr(perimeter, '__no_type_check__'))   # → False",
        "print(marking.__name__)   # → logged"
      ],
      "related": [
        "typing.no_type_check",
        "декораторы"
      ],
      "related_errors": []
    },
    {
      "id": "typing.overload",
      "title": "typing.overload",
      "kind": "function",
      "summary": {
        "ru": "Декоратор для объявления нескольких сигнатур одной функции (перегрузок) для средства проверки типов; во время выполнения перегрузки не имеют тела.",
        "en": "A decorator declaring multiple signatures (overloads) of one function for the type checker."
      },
      "body": {
        "ru": "Перегрузки — чистая аннотация для проверяльщика типов: во время выполнения тела у них нет, а вызов такой заглушки бросает NotImplementedError. Поэтому после всех @overload обязательно идёт одна обычная реализация без декоратора, которая сама разбирает аргументы, — диспетчеризации по типам, как в C++ или Java, Python не делает.",
        "en": "Overloads are pure annotations for the type checker: at runtime they have no body, and calling such a stub raises NotImplementedError. That's why all the @overload stubs must be followed by one plain, undecorated implementation that inspects the arguments itself — Python does no type-based dispatch the way C++ or Java do."
      },
      "syntax": "@typing.overload",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.overload",
      "version": "",
      "section": "Модуль typing",
      "subcat": "декораторы и маркеры",
      "color_group": "typing",
      "aliases": [
        "перегрузка функции",
        "несколько сигнатур одной функции",
        "разные варианты вызова функции"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import overload, get_overloads",
        "print(callable(overload))   # → True",
        "@overload",
        "def double(x: int) -> int: ...",
        "@overload",
        "def double(x: str) -> str: ...",
        "def double(x): return x * 2   # реализация идёт последней, без @overload",
        "print(double(5))   # → 10",
        "print(double('ab'))   # → abab",
        "print(len(get_overloads(double)))   # → 2",
        "@overload",
        "def solo(x: int) -> int: ...",
        "print(solo(1))   # → NotImplementedError (перегрузка без реализации)"
      ],
      "related": [
        "typing.get_overloads",
        "functools.singledispatch",
        "typing.clear_overloads"
      ],
      "related_errors": [
        "NotImplementedError"
      ]
    },
    {
      "id": "typing.override",
      "title": "typing.override",
      "kind": "function",
      "summary": {
        "ru": "Декоратор, помечающий метод как переопределение метода базового класса; средство проверки типов проверит, что такой метод действительно существует у предка (Python 3.12+).",
        "en": "A decorator marking a method as overriding a base-class method (3.12+)."
      },
      "body": {
        "ru": "Во время выполнения это no-op (лишь ставит атрибут __override__) — вся польза на стороне проверяльщика типов. Он ловит классическую ошибку: метод в базовом классе переименовали или удалили, и «переопределение» в наследнике молча стало новым несвязанным методом — без @override об этом никто не предупредит.",
        "en": "At runtime it's a no-op (it just sets an __override__ attribute); the value is entirely for the type checker. It catches the classic bug where a base-class method is renamed or removed and your \"override\" silently becomes a new, unrelated method — without @override nothing flags it."
      },
      "syntax": "@typing.override",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.override",
      "version": "3.12",
      "section": "Модуль typing",
      "subcat": "декораторы и маркеры",
      "color_group": "typing",
      "aliases": [
        "пометить переопределение метода",
        "проверка что метод есть у родителя"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import override",
        "class Base:",
        "    def f(self): ...",
        "class Sub(Base):",
        "    @override",
        "    def f(self): return 42",
        "print(Sub().f())   # → 42"
      ],
      "related": [
        "наследование",
        "super",
        "abc.abstractmethod"
      ],
      "related_errors": []
    },
    {
      "id": "typing.reveal_type",
      "title": "typing.reveal_type",
      "kind": "function",
      "summary": {
        "ru": "Отладочный помощник: средство проверки типов печатает выведенный тип аргумента; во время выполнения возвращает сам аргумент (и пишет тип в stderr; Python 3.11+).",
        "en": "A debugging helper; the type checker prints the inferred type. At runtime returns the argument (3.11+)."
      },
      "body": {
        "ru": "Это зонд времени проверки, а не рантайма: mypy или pyright напечатают выведенный тип и обычно пометят строку, чтобы вы её не забыли. Во время выполнения функция просто возвращает свой аргумент и пишет тип в stderr, так что оставлять её в готовом коде не нужно — это временный инструмент при отладке аннотаций.",
        "en": "This is a checker-time probe, not a runtime feature: mypy or pyright print the inferred type and usually flag the line so you won't leave it behind. At runtime it merely returns its argument and writes the type to stderr, so strip it out once you've finished debugging your annotations."
      },
      "syntax": "typing.reveal_type(value)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.reveal_type",
      "version": "3.11",
      "section": "Модуль typing",
      "subcat": "приведение и отладка",
      "color_group": "typing",
      "aliases": [
        "показать выведенный тип",
        "узнать тип переменной",
        "отладка аннотаций"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import reveal_type",
        "print(reveal_type(42))                    # → 42",
        "print(reveal_type('hi').upper())          # → HI",
        "print(reveal_type([1, 2]) == [1, 2])      # → True",
        "print(type(reveal_type(3.5)).__name__)    # → float",
        "print(callable(reveal_type))              # → True"
      ],
      "related": [
        "typing.assert_type",
        "typing.cast",
        "typing.get_type_hints"
      ],
      "related_errors": []
    },
    {
      "id": "typing.runtime_checkable",
      "title": "typing.runtime_checkable",
      "kind": "function",
      "summary": {
        "ru": "Декоратор класса-протокола (Protocol), разрешающий проверять его через isinstance()/issubclass() по наличию методов.",
        "en": "A decorator letting a Protocol be used with isinstance()/issubclass() by method presence."
      },
      "body": {
        "ru": "Главная ловушка: isinstance() с таким протоколом проверяет только наличие методов по имени, а не их сигнатуры — объект с __len__, берущим не те аргументы, всё равно пройдёт проверку. А issubclass() работает лишь с протоколами из одних методов: для протокола с атрибутами-данными он бросит TypeError.",
        "en": "The main trap: isinstance() against such a protocol checks only that the methods exist by name, not their signatures — an object whose __len__ takes the wrong arguments still passes. And issubclass() works only for method-only protocols; against a protocol with data attributes it raises TypeError."
      },
      "syntax": "@typing.runtime_checkable",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html#typing.runtime_checkable",
      "version": "3.8",
      "section": "Модуль typing",
      "subcat": "декораторы и маркеры",
      "color_group": "typing",
      "aliases": [
        "протокол с проверкой во время выполнения",
        "утиная типизация с проверкой"
      ],
      "keywords": [],
      "tags": [
        "typing"
      ],
      "examples": [
        "from typing import Protocol, runtime_checkable",
        "@runtime_checkable",
        "class Sized(Protocol):",
        "    def __len__(self) -> int: ...",
        "print(isinstance([1, 2], Sized))   # → True"
      ],
      "related": [
        "protocol",
        "isinstance",
        "typing.is_protocol"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "MagicMock",
      "title": "MagicMock",
      "kind": "term",
      "summary": {
        "ru": "Универсальный мок-объект, поддерживающий магические методы (__len__, __iter__ и т.д.). Позволяет имитировать любой объект без создания реальной зависимости.",
        "en": "A general-purpose mock object that supports the magic methods (__len__, __iter__ and so on). It can stand in for any object, so a real dependency is not needed."
      },
      "body": {
        "ru": "Любой атрибут MagicMock рождается на лету, поэтому опечатка в имени метода тест не уронит — вернётся очередной мок, и проверка молча пройдёт; чтобы такое ловить, задавайте spec= или стройте мок через create_autospec() по реальному объекту. Отличие от обычного Mock — заранее настроенные магические методы: длина равна 0, итерация пуста, объект истинный, тогда как Mock на всё это ответил бы TypeError.",
        "en": "Every attribute of a MagicMock is created on first access, so a misspelled method name does not fail the test — it just yields another mock and the check quietly passes; pass spec= or build the mock with create_autospec() from the real object to catch that. The difference from a plain Mock is the preconfigured magic methods: length is 0, iteration is empty, truthiness is True, whereas Mock would raise TypeError on all of it."
      },
      "syntax": "from unittest.mock import MagicMock\nm = MagicMock()\nm.method.return_value = 42",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock",
      "version": "",
      "section": "Модуль unittest",
      "subcat": "моки",
      "color_group": "module",
      "aliases": [
        "мок-объект",
        "объект-заглушка",
        "имитация зависимости"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "from unittest.mock import MagicMock",
        "db = MagicMock()",
        "db.query.return_value = [1, 2, 3]",
        "result = db.query('SELECT *')",
        "print(result)  # [1, 2, 3]",
        "db.query.assert_called_with('SELECT *')",
        "print(len(db))  # 0 (магический метод)"
      ],
      "related": [
        "unittest-mock-patch",
        "unittest-TestCase",
        "unittest.assertEqual"
      ],
      "related_errors": []
    },
    {
      "id": "unittest-TestCase",
      "title": "unittest.TestCase",
      "kind": "term",
      "summary": {
        "ru": "Базовый класс для тест-кейсов. Каждый метод, начинающийся с test_, запускается как отдельный тест. Предоставляет методы assert* для проверки результатов.",
        "en": "The base class for test cases. Every method whose name starts with test_ is run as a separate test. It provides the assert* methods for checking results."
      },
      "body": {
        "ru": "Для каждого метода test_ создаётся отдельный экземпляр класса, поэтому передать состояние из одного теста в другой через self не выйдет — общее готовят в setUp или setUpClass. Порядок запуска — алфавитный по имени метода, а не по порядку в файле, так что ни один тест не должен рассчитывать, что другой уже отработал. Проверять лучше через self.assertEqual, а не голым assert: он печатает оба значения при падении и не исчезает при запуске Python с -O.",
        "en": "unittest builds a fresh instance of the class for every test_ method, so you cannot hand state from one test to the next through self — anything shared belongs in setUp or setUpClass. Methods run in alphabetical order by name, not in the order they appear in the file, so no test may assume another already ran. Prefer self.assertEqual over a bare assert: it reports both values on failure and does not vanish when Python runs with -O."
      },
      "syntax": "class MyTests(unittest.TestCase):\n    def test_something(self): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/unittest.html#unittest.TestCase",
      "version": "",
      "section": "Модуль unittest",
      "subcat": "тестирование",
      "color_group": "module",
      "aliases": [
        "класс с тестами",
        "модульные тесты",
        "юнит-тесты"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "import unittest",
        "class MathTests(unittest.TestCase):",
        "    def test_sum(self):",
        "        self.assertEqual(1 + 1, 2)",
        "    def test_type(self):",
        "        self.assertIsInstance(42, int)",
        "    if __name__ == '__main__':",
        "        unittest.main()"
      ],
      "related": [
        "unittest.assertEqual",
        "unittest.setUp",
        "unittest.assertRaises",
        "unittest.tearDown"
      ],
      "related_errors": []
    },
    {
      "id": "unittest-mock-patch",
      "title": "unittest.mock.patch()",
      "kind": "function",
      "summary": {
        "ru": "Декоратор и контекстный менеджер для замены объектов в тестах на фиктивные (mock). Позволяет изолировать тестируемый код от внешних зависимостей.",
        "en": "A decorator and a context manager that replace objects with mocks during a test. They isolate the code under test from its external dependencies."
      },
      "body": {
        "ru": "Главная ловушка — патчить нужно там, где имя ищут, а не там, где оно определено: если тестируемый модуль сделал у себя импорт имени, подменять надо запись в этом модуле, а патч исходного модуля на него уже не подействует. Когда декораторов несколько, моки приходят в параметры снизу вверх: ближайший к функции @patch соответствует первому аргументу.",
        "en": "The classic trap is patching the wrong place: patch the name where it is looked up, not where it was defined — if the module under test imported the name into its own namespace, patching the original module has no effect. With several stacked decorators the mocks arrive bottom-up, so the @patch closest to the function maps to the first parameter."
      },
      "syntax": "from unittest.mock import patch\n@patch('module.ClassName')\ndef test(self, mock_cls): ...\n\nwith patch('module.func') as m: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch",
      "version": "",
      "section": "Модуль unittest",
      "subcat": "моки",
      "color_group": "module",
      "aliases": [
        "подменить функцию в тесте",
        "временно заменить объект",
        "мокирование"
      ],
      "keywords": [
        "unittest.mock.patch"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "from unittest.mock import patch",
        "import unittest",
        "class Tests(unittest.TestCase):",
        "    @patch('builtins.input', return_value='42')",
        "    def test_input(self, mock_input):",
        "        self.assertEqual(input(), '42')",
        "        mock_input.assert_called_once()"
      ],
      "related": [
        "MagicMock",
        "unittest-TestCase",
        "unittest.setUp"
      ],
      "related_errors": [
        "AttributeError",
        "ModuleNotFoundError"
      ]
    },
    {
      "id": "unittest.assertEqual",
      "title": "assertEqual()",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что два значения равны (==), и при расхождении печатает оба. Базовое утверждение TestCase — на нём строится большинство проверок.",
        "en": "Assert that two values are equal (==), printing both on failure; the basic TestCase assertion most checks are built on."
      },
      "body": {
        "ru": "Сравнение идёт через ==, поэтому равенство 1, 1.0 и True проходит незамеченным: для проверки «тот же самый объект» есть assertIs, а для дробных чисел — assertAlmostEqual, иначе тест споткнётся о погрешность float. Если оба аргумента одного типа, unittest подставляет специализированный компаратор (списки, словари, многострочные строки) и печатает наглядный diff, обрезанный по self.maxDiff — снимается присваиванием None.",
        "en": "The check is plain ==, so 1, 1.0 and True all compare equal and a type mix-up slips through: use assertIs for identity and assertAlmostEqual for floats, otherwise rounding error will trip the test. When both arguments share a type, unittest dispatches to a type-aware comparator (lists, dicts, multi-line strings) and prints a readable diff, truncated at self.maxDiff — set it to None to see everything."
      },
      "syntax": "self.assertEqual(first, second, msg=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertEqual",
      "version": "",
      "section": "Модуль unittest",
      "subcat": "проверки",
      "color_group": "module",
      "aliases": [
        "проверить равенство в тесте",
        "сравнить ожидаемое и полученное"
      ],
      "keywords": [
        "assertEqual",
        "TestCase.assertEqual"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import unittest",
        "class Tests(unittest.TestCase):",
        "    def test_equal(self):",
        "        self.assertEqual(2 ** 3, 8)",
        "        self.assertNotEqual(1, 2)"
      ],
      "related": [
        "unittest.assertRaises",
        "unittest.setUp"
      ],
      "related_errors": [
        "AssertionError"
      ]
    },
    {
      "id": "unittest.assertRaises",
      "title": "assertRaises()",
      "kind": "function",
      "summary": {
        "ru": "Проверяет, что код поднимает нужное исключение. Как контекстный менеджер (with) проверяет блок, а с вызываемым объектом — конкретный вызов.",
        "en": "Assert that code raises the expected exception: as a context manager (with) it checks a block, or pass a callable to check a single call."
      },
      "body": {
        "ru": "В форме без with частая ошибка — передать результат вызова функции вместо самой функции: она отработает раньше assertRaises, исключение вылетит наружу и тест просто упадёт; функция и её аргументы передаются отдельно. Проверка срабатывает и на подклассах, так что assertRaises(Exception) зелёный почти на любой поломке — указывайте конкретный класс, а чтобы заглянуть в текст сообщения, используйте форму with ... as cm и её атрибут exception или assertRaisesRegex.",
        "en": "In the callable form the usual slip is passing the result of the call instead of the function itself: it runs before assertRaises ever sees it, the exception escapes and the test simply errors out — hand over the function and its arguments separately. The assertion also accepts subclasses, so assertRaises(Exception) passes on almost any breakage; name the specific class, and to inspect the message use the with ... as cm form and its exception attribute, or assertRaisesRegex."
      },
      "syntax": "with self.assertRaises(ExcType): ...\nself.assertRaises(ExcType, callable, *args)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises",
      "version": "",
      "section": "Модуль unittest",
      "subcat": "проверки",
      "color_group": "module",
      "aliases": [
        "проверить, что код бросает исключение",
        "тест на ошибку",
        "ожидаемое исключение в тесте"
      ],
      "keywords": [
        "assertRaises",
        "TestCase.assertRaises"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import unittest",
        "class Tests(unittest.TestCase):",
        "    def test_raises(self):",
        "        with self.assertRaises(ValueError):",
        "            int('abc')",
        "    def test_raises_call(self):",
        "        self.assertRaises(ZeroDivisionError, lambda: 1 / 0)"
      ],
      "related": [
        "unittest.assertEqual"
      ],
      "related_errors": [
        "AssertionError"
      ]
    },
    {
      "id": "unittest.setUp",
      "title": "setUp()",
      "kind": "function",
      "summary": {
        "ru": "Запускается перед КАЖДЫМ тестом метода класса — готовит состояние (файлы, БД, моки). Падение в setUp помечает тест как error, а не fail.",
        "en": "Run before EVERY test method of the class to prepare state (files, DB, mocks); a failure inside setUp marks the test as an error, not a failure."
      },
      "body": {
        "ru": "setUp выполняется заново перед каждым тестом намеренно — так тесты остаются изолированными; если подготовка дорогая (поднять сервер, открыть соединение с БД), выносите её в setUpClass, который отработает один раз на весь класс. Освобождать ресурсы надёжнее через self.addCleanup(...) сразу после захвата: такие функции вызовутся, даже если setUp упадёт посреди подготовки, а вот tearDown в этом случае пропустят.",
        "en": "setUp deliberately runs again before every single test to keep them isolated; if the preparation is expensive (starting a server, opening a DB connection), move it to setUpClass, which runs once per class. For releasing resources, calling self.addCleanup(...) right after you acquire something is safer than tearDown — cleanups still fire when setUp blows up halfway through, while tearDown is skipped entirely."
      },
      "syntax": "def setUp(self): ...    # до каждого теста",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/unittest.html#unittest.TestCase.setUp",
      "version": "",
      "section": "Модуль unittest",
      "subcat": "фикстуры",
      "color_group": "module",
      "aliases": [
        "подготовка перед каждым тестом",
        "фикстура перед тестом"
      ],
      "keywords": [
        "setUp",
        "TestCase.setUp"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import unittest",
        "class DBTest(unittest.TestCase):",
        "    def setUp(self):",
        "        self.data = {'key': 'val'}   # инициализация",
        "    def test_key(self):",
        "        self.assertIn('key', self.data)"
      ],
      "related": [
        "unittest.tearDown",
        "unittest.assertEqual"
      ],
      "related_errors": []
    },
    {
      "id": "unittest.tearDown",
      "title": "tearDown()",
      "kind": "function",
      "summary": {
        "ru": "Запускается после КАЖДОГО теста — освобождает то, что занял setUp. Вызывается даже если тест упал, но только если сам setUp отработал успешно.",
        "en": "Run after EVERY test to release what setUp acquired; it still runs when the test fails, but only if setUp itself completed successfully."
      },
      "body": {
        "ru": "Слабое место tearDown — он один на все ресурсы: если первая же строка освобождения бросит исключение, остальные не выполнятся и лишний файл или соединение останутся висеть. Регистрировать self.addCleanup(...) сразу после захвата каждого ресурса надёжнее: такие функции вызываются в обратном порядке, падение одной не отменяет остальные, и они срабатывают даже когда setUp не дошёл до конца. Исключение внутри самого tearDown помечает тест как error, даже если все проверки прошли.",
        "en": "tearDown is one method for all resources: if the first release line raises, the rest never run and a file or connection is left dangling. Registering self.addCleanup(...) as soon as each resource is acquired is sturdier — cleanups run in reverse order, one failing does not cancel the others, and they fire even when setUp never finished. An exception raised inside tearDown itself marks the test as an error even if every assertion passed."
      },
      "syntax": "def tearDown(self): ... # после каждого теста",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/unittest.html#unittest.TestCase.tearDown",
      "version": "",
      "section": "Модуль unittest",
      "subcat": "фикстуры",
      "color_group": "module",
      "aliases": [
        "очистка после каждого теста",
        "освободить ресурсы после теста"
      ],
      "keywords": [
        "tearDown",
        "TestCase.tearDown"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import unittest",
        "class DBTest(unittest.TestCase):",
        "    def setUp(self):",
        "        self.data = {'key': 'val'}",
        "    def tearDown(self):",
        "        self.data.clear()            # очистка"
      ],
      "related": [
        "unittest.setUp"
      ],
      "related_errors": []
    },
    {
      "id": "__add__-__mul__-__eq__-__lt__-и-оператор",
      "title": "__add__ / __mul__ / __eq__ / __lt__ и операторные методы",
      "kind": "term",
      "summary": {
        "ru": "Перегрузка операторов. __add__(+), __sub__(-), __mul__(*), __eq__(==), __lt__(<), __le__(<=), __hash__, __neg__.",
        "en": "Operator overloading. __add__ (+), __sub__ (-), __mul__ (*), __eq__ (==), __lt__ (<), __le__ (<=), __hash__, __neg__."
      },
      "body": {
        "ru": "Как только вы определяете __eq__, Python обнуляет __hash__ и объекты становятся нехешируемыми (не лягут в set или dict) — верните __hash__ вручную, если он нужен. Если операция не поддерживает второй операнд, возвращайте NotImplemented, а не бросайте исключение: тогда Python попробует зеркальный метод вроде __radd__ у другого объекта, а functools.total_ordering достроит остальные сравнения из __eq__ и одного __lt__.",
        "en": "The moment you define __eq__, Python sets __hash__ to None and instances become unhashable (they won't go into a set or dict) — restore __hash__ yourself if you need it. When an operation doesn't support the other operand, return NotImplemented instead of raising, so Python can try the reflected method like __radd__ on the other object; and functools.total_ordering fills in the remaining comparisons from __eq__ plus a single __lt__."
      },
      "syntax": "def __add__(self, other): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/datamodel.html#object.__add__",
      "version": "",
      "section": "ООП",
      "subcat": "магические методы",
      "color_group": "oop",
      "aliases": [
        "перегрузка операторов",
        "переопределение операторов",
        "сравнение объектов класса"
      ],
      "keywords": [
        "__add__",
        "__mul__",
        "__eq__",
        "__lt__"
      ],
      "tags": [
        "oop"
      ],
      "examples": [
        "class Vector:",
        "    def __init__(self, x, y):",
        "        self.x = x; self.y = y",
        "    def __add__(self, other):",
        "        return Vector(self.x+other.x, self.y+other.y)",
        "    def __mul__(self, scalar):",
        "        return Vector(self.x*scalar, self.y*scalar)",
        "    def __repr__(self):",
        "        return f'Vector({self.x},{self.y})'",
        "v1 = Vector(1,2); v2 = Vector(3,4)",
        "print(v1+v2)  # → Vector(4,6)",
        "print(v1*3)   # → Vector(3,6)",
        "class Fraction:",
        "    def __init__(self, n, d):",
        "        from math import gcd",
        "        g = gcd(abs(n), abs(d))",
        "        self.n = n//g; self.d = d//g",
        "    def __add__(self, other):",
        "        return Fraction(self.n*other.d + other.n*self.d, self.d*other.d)",
        "    def __repr__(self):",
        "        return f'{self.n}/{self.d}'",
        "print(Fraction(1,3)+Fraction(1,6))  # → 1/2",
        "class MyStr:",
        "    def __init__(self, s):",
        "        self.s = s",
        "    def __add__(self, other):",
        "        return MyStr(self.s + other.s)",
        "    def __mul__(self, n):",
        "        return MyStr(self.s * n)",
        "    def __repr__(self):",
        "        return repr(self.s)",
        "print(MyStr('hi')*3)  # → 'hihihi'",
        "class Point:",
        "    def __init__(self, x, y):",
        "        self.x = x; self.y = y",
        "    def __eq__(self, other):",
        "        return self.x == other.x and self.y == other.y",
        "    def __lt__(self, other):",
        "        return (self.x**2+self.y**2) < (other.x**2+other.y**2)",
        "points = [Point(3,4), Point(1,1), Point(0,5)]",
        "print(sorted(points, key=lambda p: (p.x**2+p.y**2)))  # по расстоянию",
        "print(Point(1,1) == Point(1,1))  # → True",
        "class Temperature:",
        "    def __init__(self, c):",
        "        self.c = c",
        "    def __eq__(self, other):",
        "        return self.c == other.c",
        "    def __lt__(self, other):",
        "        return self.c < other.c",
        "    def __le__(self, other):",
        "        return self.c <= other.c",
        "t1 = Temperature(20); t2 = Temperature(30)",
        "print(t1 < t2, t1 == t2)  # → True False",
        "# __neg__, __abs__",
        "class Num:",
        "    def __init__(self, v):",
        "        self.v = v",
        "    def __neg__(self):",
        "        return Num(-self.v)",
        "    def __abs__(self):",
        "        return Num(abs(self.v))",
        "    def __repr__(self):",
        "        return f'Num({self.v})'",
        "print(-Num(5))    # → Num(-5)",
        "print(abs(Num(-3)))  # → Num(3)",
        "# __iadd__ (+=)",
        "class Counter:",
        "    def __init__(self, n=0):",
        "        self.n = n",
        "    def __iadd__(self, other):",
        "        self.n += other",
        "        return self",
        "    def __repr__(self):",
        "        return f'Counter({self.n})'",
        "c = Counter(5)",
        "c += 3",
        "print(c)  # → Counter(8)"
      ],
      "related": [
        "functools.total_ordering",
        "dataclass-order-true",
        "операторы-сравнения",
        "__str__-__repr__"
      ],
      "related_errors": []
    },
    {
      "id": "__enter__-__exit__",
      "title": "__enter__ / __exit__",
      "kind": "term",
      "summary": {
        "ru": "Контекстный менеджер (with-statement). __enter__ выполняется при входе, __exit__ — при выходе (в том числе при исключении).",
        "en": "A context manager (the with statement). __enter__ runs on entry, __exit__ on exit — including when an exception is raised."
      },
      "body": {
        "ru": "__exit__ получает информацию об исключении в трёх аргументах, и если вернуть из него True — исключение будет подавлено (при обычном выходе там None). Возвращать True по привычке не стоит: молча проглоченная ошибка — классический источник трудноуловимых багов. Значение, которое вернул __enter__, попадает в переменную после as.",
        "en": "__exit__ receives the exception details in its three arguments, and returning True from it suppresses that exception (on a normal exit they are all None). Don't return True out of habit — a silently swallowed error is a classic source of hard-to-find bugs. Whatever __enter__ returns is what the as variable binds to."
      },
      "syntax": "class CM:\n    def __enter__(self): return self\n    def __exit__(self, exc_type, exc_val, tb): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers",
      "version": "",
      "section": "ООП",
      "subcat": "контекст. менеджер",
      "color_group": "oop",
      "aliases": [
        "контекстный менеджер",
        "свой блок with",
        "автоматическое освобождение ресурса"
      ],
      "keywords": [
        "__enter__",
        "__exit__"
      ],
      "tags": [
        "oop"
      ],
      "examples": [
        "class ManagedResource:",
        "def __enter__(self):",
        "print('Opening resource')",
        "return self",
        "def __exit__(self, exc_type, exc_val, tb):",
        "print('Closing resource')",
        "return False  # не подавлять исключения",
        "with ManagedResource() as r:",
        "print('Using resource')  # → Opening / Using / Closing",
        "class Timer:",
        "import time as _time",
        "def __enter__(self):",
        "import time",
        "self.start = time.time()",
        "return self",
        "def __exit__(self, *args):",
        "import time",
        "self.elapsed = time.time() - self.start",
        "print(f'Elapsed: {self.elapsed:.4f}s')",
        "with Timer() as t:",
        "sum(range(10**6))  # → Elapsed: ...",
        "class Transaction:",
        "def __init__(self):",
        "self.operations = []",
        "def __enter__(self):",
        "return self",
        "def __exit__(self, exc_type, exc_val, tb):",
        "if exc_type:",
        "print('Rolling back!')",
        "self.operations.clear()",
        "else:",
        "print('Committing:', self.operations)",
        "return True  # подавить исключение",
        "with Transaction() as tx:",
        "tx.operations.append('insert')",
        "tx.operations.append('update')  # → Committing: ['insert','update']",
        "# Подавление исключения (__exit__ возвращает True)",
        "class Suppress:",
        "def __init__(self, *exceptions):",
        "self.exceptions = exceptions",
        "def __enter__(self):",
        "return self",
        "def __exit__(self, exc_type, exc_val, tb):",
        "return exc_type is not None and issubclass(exc_type, self.exceptions)",
        "with Suppress(ZeroDivisionError):",
        "x = 1/0  # → не падает!",
        "print('After suppress')  # → After suppress",
        "# Использование contextlib",
        "from contextlib import contextmanager",
        "@contextmanager",
        "def managed():",
        "print('enter')",
        "yield",
        "print('exit')",
        "with managed():",
        "print('body')  # → enter / body / exit",
        "# Вложенные with",
        "class A:",
        "def __enter__(self): print('A enter'); return self",
        "def __exit__(self, *a): print('A exit'); return False",
        "class B:",
        "def __enter__(self): print('B enter'); return self",
        "def __exit__(self, *a): print('B exit'); return False",
        "with A() as a, B() as b:",
        "print('inside')  # → A enter / B enter / inside / B exit / A exit"
      ],
      "related": [
        "contextlib.contextmanager",
        "open",
        "contextlib.exitstack"
      ],
      "related_errors": []
    },
    {
      "id": "__init__",
      "title": "__init__",
      "kind": "term",
      "summary": {
        "ru": "Конструктор — вызывается при создании экземпляра. Инициализирует атрибуты объекта.",
        "en": "The constructor — called when an instance is created. It initializes the object's attributes."
      },
      "body": {
        "ru": "__init__ — не конструктор в полном смысле: к моменту его вызова объект уже создан методом __new__, а __init__ лишь заполняет атрибуты и обязан вернуть None (вернёте что-то другое — TypeError). При наследовании легко забыть super().__init__(), из-за чего часть родительской инициализации не отработает.",
        "en": "__init__ isn't the constructor in the full sense: by the time it runs the object already exists (created by __new__), and __init__ only fills in its attributes — it must return None, or you get a TypeError. In a subclass it's easy to forget super().__init__(), leaving part of the parent's setup undone."
      },
      "syntax": "def __init__(self, params): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/datamodel.html#object.__init__",
      "version": "",
      "section": "ООП",
      "subcat": "инициализация",
      "color_group": "oop",
      "aliases": [
        "конструктор класса",
        "инициализация объекта",
        "создать объект с параметрами"
      ],
      "keywords": [],
      "tags": [
        "oop"
      ],
      "examples": [
        "class Person:",
        "def __init__(self, name, age):",
        "self.name = name",
        "self.age = age",
        "p = Person('Alice', 30)",
        "print(p.name, p.age)  # → Alice 30",
        "class Rectangle:",
        "def __init__(self, w=1, h=1):",
        "self.w = w",
        "self.h = h",
        "r = Rectangle()",
        "print(r.w, r.h)  # → 1 1",
        "class BankAccount:",
        "def __init__(self, owner, balance=0):",
        "if balance < 0:",
        "raise ValueError('Balance cannot be negative')",
        "self.owner = owner",
        "self.balance = balance",
        "acc = BankAccount('Bob', 100)",
        "print(acc.balance)  # → 100",
        "class Animal:",
        "def __init__(self, name):",
        "self.name = name",
        "class Dog(Animal):",
        "def __init__(self, name, breed):",
        "super().__init__(name)",
        "self.breed = breed",
        "d = Dog('Rex', 'Lab')",
        "print(d.name, d.breed)  # → Rex Lab",
        "class Stack:",
        "def __init__(self):",
        "self._data = []",
        "def push(self, item):",
        "self._data.append(item)",
        "def pop(self):",
        "return self._data.pop()",
        "s = Stack()",
        "s.push(1); s.push(2)",
        "print(s.pop())  # → 2",
        "class Vector:",
        "def __init__(self, *components):",
        "self.components = list(components)",
        "v = Vector(1, 2, 3)",
        "print(v.components)  # → [1, 2, 3]",
        "class Config:",
        "def __init__(self, **settings):",
        "self.__dict__.update(settings)",
        "cfg = Config(debug=True, level=2)",
        "print(cfg.debug, cfg.level)  # → True 2"
      ],
      "related": [
        "self",
        "super",
        "class",
        "dataclass"
      ],
      "related_errors": []
    },
    {
      "id": "__len__-__getitem__-__setitem__-__contai",
      "title": "__len__ / __getitem__ / __setitem__ / __contains__",
      "kind": "term",
      "summary": {
        "ru": "Магические методы для протокола последовательности/контейнера. Позволяют использовать len(), [], in.",
        "en": "The magic methods of the sequence/container protocol. They make len(), [] and in work on your object."
      },
      "body": {
        "ru": "Один только __getitem__ уже делает объект итерируемым и заставляет работать оператор in — Python перебирает индексы 0, 1, 2... пока не поймает IndexError, даже если __iter__ и __contains__ не заданы. А объект, у которого __len__ вернул 0, считается ложным в булевом контексте (if obj).",
        "en": "__getitem__ alone already makes the object iterable and makes the in operator work — Python walks indices 0, 1, 2... until it hits IndexError, even without __iter__ or __contains__. And an object whose __len__ returns 0 is treated as falsy in a boolean context (if obj)."
      },
      "syntax": "def __len__(self): ...\ndef __getitem__(self, idx): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/datamodel.html#emulating-container-types",
      "version": "",
      "section": "ООП",
      "subcat": "магические методы",
      "color_group": "oop",
      "aliases": [
        "свой класс как список",
        "поддержка квадратных скобок в классе",
        "протокол контейнера"
      ],
      "keywords": [
        "__len__",
        "__getitem__",
        "__setitem__",
        "__contains__"
      ],
      "tags": [
        "oop"
      ],
      "examples": [
        "class MyList:",
        "def __init__(self, data):",
        "self._data = list(data)",
        "def __len__(self):",
        "return len(self._data)",
        "def __getitem__(self, idx):",
        "return self._data[idx]",
        "ml = MyList([1,2,3])",
        "print(len(ml))   # → 3",
        "print(ml[0])     # → 1",
        "print(ml[-1])    # → 3",
        "class FibSequence:",
        "def __getitem__(self, n):",
        "a, b = 0, 1",
        "for _ in range(n):",
        "a, b = b, a+b",
        "return a",
        "fib = FibSequence()",
        "print(fib[0], fib[5], fib[10])  # → 0 5 55",
        "class MyDict:",
        "def __init__(self):",
        "self._data = {}",
        "def __setitem__(self, key, val):",
        "self._data[key] = val",
        "def __getitem__(self, key):",
        "return self._data[key]",
        "d = MyDict()",
        "d['x'] = 10",
        "print(d['x'])  # → 10",
        "class Bag:",
        "def __init__(self, items):",
        "self._items = list(items)",
        "def __contains__(self, item):",
        "return item in self._items",
        "b = Bag([1,2,3])",
        "print(2 in b)  # → True",
        "print(5 in b)  # → False",
        "# Срезы через __getitem__",
        "class Sliceable:",
        "def __init__(self, data):",
        "self._data = data",
        "def __getitem__(self, key):",
        "return self._data[key]",
        "s = Sliceable([10,20,30,40,50])",
        "print(s[1:3])  # → [20, 30]",
        "# Полный контейнер",
        "class NumberSet:",
        "def __init__(self, *nums):",
        "self._nums = set(nums)",
        "def __len__(self):",
        "return len(self._nums)",
        "def __contains__(self, n):",
        "return n in self._nums",
        "def __repr__(self):",
        "return f'NumberSet({self._nums})'",
        "ns = NumberSet(1,2,3,4,5)",
        "print(len(ns), 3 in ns, 9 in ns)  # → 5 True False",
        "# __iter__ через __getitem__",
        "class Range:",
        "def __init__(self, n):",
        "self.n = n",
        "def __getitem__(self, i):",
        "if i >= self.n:",
        "raise IndexError",
        "return i",
        "print(list(Range(5)))  # → [0,1,2,3,4]"
      ],
      "related": [
        "len",
        "итератор-__iter__-__next__",
        "collections.abc.Sequence",
        "slice"
      ],
      "related_errors": []
    },
    {
      "id": "__slots__",
      "title": "__slots__",
      "kind": "term",
      "summary": {
        "ru": "__slots__ ограничивает атрибуты экземпляра, убирает __dict__, экономит память. Особенно полезно для большого числа объектов.",
        "en": "__slots__ restricts the instance attributes, removes __dict__ and saves memory. Especially useful for large numbers of objects."
      },
      "body": {
        "ru": "Экономия памяти теряется, как только наследник не объявит собственный __slots__ — он снова получит __dict__. Перечисленные слоты — единственные разрешённые атрибуты: присвоить что-то за их пределами нельзя (AttributeError), и по умолчанию пропадает поддержка weakref, пока не добавишь '__weakref__' в список.",
        "en": "The memory win vanishes the moment a subclass omits its own __slots__ — it regains a __dict__. The listed slots are the only attributes allowed: assigning anything outside them raises AttributeError, and weakref support disappears unless you add '__weakref__' to the list."
      },
      "syntax": "class C:\n    __slots__ = ('x', 'y')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/datamodel.html#slots",
      "version": "",
      "section": "ООП",
      "subcat": "slots",
      "color_group": "oop",
      "aliases": [
        "экономия памяти объектов",
        "ограничить набор атрибутов",
        "запретить новые атрибуты объекта"
      ],
      "keywords": [],
      "tags": [
        "oop"
      ],
      "examples": [
        "class Point:",
        "__slots__ = ('x', 'y')",
        "def __init__(self, x, y):",
        "self.x = x; self.y = y",
        "p = Point(1, 2)",
        "print(p.x, p.y)  # → 1 2",
        "try:",
        "p.z = 3  # → AttributeError",
        "except AttributeError as e:",
        "print(e)",
        "# Нет __dict__",
        "try:",
        "print(p.__dict__)",
        "except AttributeError as e:",
        "print(e)  # → __dict__ attribute not defined",
        "import sys",
        "class WithDict:",
        "def __init__(self, x, y):",
        "self.x = x; self.y = y",
        "class WithSlots:",
        "__slots__ = ('x','y')",
        "def __init__(self, x, y):",
        "self.x = x; self.y = y",
        "print(sys.getsizeof(WithDict(1,2)))   # → больше",
        "print(sys.getsizeof(WithSlots(1,2))) # → меньше",
        "# __slots__ в наследовании",
        "class Base:",
        "__slots__ = ('x',)",
        "class Child(Base):",
        "__slots__ = ('y',)",
        "c2 = Child()",
        "c2.x = 1; c2.y = 2",
        "print(c2.x, c2.y)  # → 1 2",
        "# Частичные __slots__",
        "class Mixed:",
        "__slots__ = ('x',)",
        "def __init__(self, x, y):",
        "self.x = x",
        "# self.y = y  # → AttributeError",
        "print(Mixed(1,2).x)  # → 1"
      ],
      "related": [
        "атрибуты-экземпляра-и-класса",
        "dataclass",
        "sys.getsizeof"
      ],
      "related_errors": []
    },
    {
      "id": "__str__-__repr__",
      "title": "__str__ / __repr__",
      "kind": "term",
      "summary": {
        "ru": "__str__ — читаемое представление для print(). __repr__ — отладочное, воспроизводимое. repr() вызывает __repr__.",
        "en": "__str__ — the readable representation used by print(). __repr__ — the debugging one, meant to be reproducible. repr() calls __repr__."
      },
      "body": {
        "ru": "Если __str__ не задан, print() и str() откатываются к __repr__ — но не наоборот, поэтому минимум, который стоит определять, это __repr__. Вложенные элементы контейнер всегда показывает через __repr__: print([p]) выведет repr, а не str, даже если у p есть __str__.",
        "en": "If __str__ is missing, print() and str() fall back to __repr__ — but not the other way round, so __repr__ is the one to define if you define only one. A container always shows its elements via __repr__: print([p]) uses repr, not str, even when p has a __str__."
      },
      "syntax": "def __str__(self): return '...'\ndef __repr__(self): return '...'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/datamodel.html#object.__str__",
      "version": "",
      "section": "ООП",
      "subcat": "магические методы",
      "color_group": "oop",
      "aliases": [
        "строковое представление объекта",
        "как объект выводится в print",
        "отладочное представление"
      ],
      "keywords": [
        "__str__",
        "__repr__"
      ],
      "tags": [
        "oop"
      ],
      "examples": [
        "class Point:",
        "def __init__(self, x, y):",
        "self.x = x; self.y = y",
        "def __repr__(self):",
        "return f'Point({self.x}, {self.y})'",
        "def __str__(self):",
        "return f'({self.x}, {self.y})'",
        "p = Point(3, 4)",
        "print(p)       # → (3, 4) — __str__",
        "print(repr(p)) # → Point(3, 4) — __repr__",
        "# Если только __repr__ — используется и для str",
        "class Vec:",
        "def __init__(self, x, y):",
        "self.x = x; self.y = y",
        "def __repr__(self):",
        "return f'Vec({self.x},{self.y})'",
        "print(Vec(1,2))  # → Vec(1,2)",
        "class Fraction:",
        "def __init__(self, n, d):",
        "self.n = n; self.d = d",
        "def __str__(self):",
        "return f'{self.n}/{self.d}'",
        "def __repr__(self):",
        "return f'Fraction({self.n}, {self.d})'",
        "f = Fraction(1, 3)",
        "print(f)       # → 1/3",
        "print(repr(f)) # → Fraction(1, 3)",
        "# В списках используется __repr__",
        "p2 = Point(1, 2)",
        "print([p2])  # → [Point(1, 2)]",
        "class Temperature:",
        "def __init__(self, c):",
        "self.c = c",
        "def __repr__(self):",
        "return f'Temperature({self.c})'",
        "def __str__(self):",
        "return f'{self.c}°C'",
        "t = Temperature(100)",
        "print(str(t), repr(t))  # → 100°C  Temperature(100)",
        "# f-string использует __str__",
        "print(f'{Point(1,2)}')  # → (1, 2)",
        "print(f'{Point(1,2)!r}')  # → Point(1, 2)"
      ],
      "related": [
        "repr-vs-str",
        "repr",
        "format"
      ],
      "related_errors": []
    },
    {
      "id": "class",
      "title": "class",
      "kind": "term",
      "summary": {
        "ru": "Объявление класса. Класс — шаблон для создания объектов (экземпляров).",
        "en": "A class declaration. A class is the template objects (instances) are created from."
      },
      "body": {
        "ru": "Тело класса выполняется один раз в момент определения, а не при создании каждого объекта. Переменные, объявленные прямо в теле (не через self), становятся атрибутами класса и общими для всех экземпляров — изменяемый список или словарь на этом уровне легко превращается в случайно разделяемое состояние.",
        "en": "The class body runs once when the class is defined, not each time you create an object. Names bound directly in the body (not via self) become class attributes shared by every instance, so a mutable list or dict placed there easily turns into accidentally shared state."
      },
      "syntax": "class ИмяКласса:\n    тело",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#class",
      "version": "",
      "section": "ООП",
      "subcat": "классы",
      "color_group": "oop",
      "aliases": [
        "создать свой класс",
        "объявить класс",
        "шаблон для объектов"
      ],
      "keywords": [],
      "tags": [
        "oop"
      ],
      "examples": [
        "class Dog:",
        "pass  # пустой класс",
        "d = Dog()",
        "print(type(d))  # → <class '__main__.Dog'>",
        "class Cat:",
        "\"\"\"Класс кошки.\"\"\"",
        "species = 'Felis catus'  # атрибут класса",
        "print(Cat.species)  # → Felis catus",
        "class Point:",
        "def __init__(self, x, y):",
        "self.x = x",
        "self.y = y",
        "def __str__(self):",
        "return f'Point({self.x}, {self.y})'",
        "p = Point(3, 4)",
        "print(p)  # → Point(3, 4)",
        "class Circle:",
        "pi = 3.14159",
        "def __init__(self, r):",
        "self.r = r",
        "def area(self):",
        "return self.pi * self.r ** 2",
        "c = Circle(5)",
        "print(c.area())  # → 78.53975",
        "class Empty:",
        "\"\"\"Docstring.\"\"\"",
        "pass",
        "print(Empty.__doc__)  # → Docstring.",
        "class Counter:",
        "count = 0",
        "def __init__(self):",
        "Counter.count += 1",
        "Counter(); Counter()",
        "print(Counter.count)  # → 2",
        "class Vehicle:",
        "def __init__(self, brand, speed):",
        "self.brand = brand",
        "self.speed = speed",
        "def describe(self):",
        "return f'{self.brand} @ {self.speed}km/h'",
        "v = Vehicle('BMW', 200)",
        "print(v.describe())  # → BMW @ 200km/h"
      ],
      "related": [
        "__init__",
        "self",
        "методы-экземпляра",
        "наследование"
      ],
      "related_errors": []
    },
    {
      "id": "classmethod",
      "title": "@classmethod",
      "kind": "term",
      "summary": {
        "ru": "Метод класса. Первый параметр — cls (ссылка на класс). Используется для альтернативных конструкторов.",
        "en": "A class method. Its first parameter is cls (a reference to the class). Used for alternative constructors."
      },
      "body": {
        "ru": "cls — это тот класс, на котором метод фактически вызвали, поэтому в подклассе cls(...) построит объект подкласса, а не родителя; именно за это classmethod ценят в альтернативных конструкторах. Если фабрике сам класс не нужен, берите staticmethod или обычную функцию.",
        "en": "cls is whichever class the method was actually called on, so in a subclass cls(...) builds an instance of the subclass, not the parent — exactly why alternative constructors rely on classmethod. If the factory doesn't need the class at all, reach for staticmethod or a plain function instead."
      },
      "syntax": "@classmethod\ndef method(cls, ...): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#classmethod",
      "version": "",
      "section": "ООП",
      "subcat": "классы",
      "color_group": "oop",
      "aliases": [
        "метод класса",
        "альтернативный конструктор",
        "фабричный метод"
      ],
      "keywords": [],
      "tags": [
        "oop",
        "builtin"
      ],
      "examples": [
        "class Date:",
        "    def __init__(self, y, m, d):",
        "        self.y, self.m, self.d = y, m, d",
        "    @classmethod",
        "    def from_string(cls, s):",
        "        y, m, d = map(int, s.split('-'))",
        "        return cls(y, m, d)",
        "d = Date.from_string('2024-03-15')",
        "print(d.y, d.m, d.d)  # → 2024 3 15",
        "class Dog:",
        "    count = 0",
        "    def __init__(self, name):",
        "        self.name = name",
        "        Dog.count += 1",
        "    @classmethod",
        "    def get_count(cls):",
        "        return cls.count",
        "Dog('Rex'); Dog('Max')",
        "print(Dog.get_count())  # → 2",
        "class Temperature:",
        "    def __init__(self, celsius):",
        "        self.celsius = celsius",
        "    @classmethod",
        "    def from_fahrenheit(cls, f):",
        "        return cls((f - 32) * 5/9)",
        "    @classmethod",
        "    def from_kelvin(cls, k):",
        "        return cls(k - 273.15)",
        "t = Temperature.from_fahrenheit(212)",
        "print(round(t.celsius))  # → 100",
        "class Pizza:",
        "    ingredients = []",
        "    def __init__(self, ingredients):",
        "        self.ingredients = ingredients",
        "    @classmethod",
        "    def margherita(cls):",
        "        return cls(['mozzarella', 'tomato'])",
        "    @classmethod",
        "    def hawaiian(cls):",
        "        return cls(['mozzarella', 'tomato', 'pineapple'])",
        "p = Pizza.margherita()",
        "print(p.ingredients)  # → ['mozzarella', 'tomato']",
        "# Через экземпляр тоже работает",
        "class A:",
        "    x = 10",
        "    @classmethod",
        "    def get_x(cls):",
        "        return cls.x",
        "a = A()",
        "print(a.get_x())  # → 10",
        "# Наследование + classmethod",
        "class Animal:",
        "    @classmethod",
        "    def make(cls, name):",
        "        obj = cls.__new__(cls)",
        "        obj.name = name",
        "        return obj",
        "class Cat(Animal):",
        "    pass",
        "cat = Cat.make('Whiskers')",
        "print(type(cat).__name__, cat.name)  # → Cat Whiskers"
      ],
      "related": [
        "staticmethod",
        "методы-экземпляра",
        "__init__"
      ],
      "related_errors": []
    },
    {
      "id": "property",
      "title": "@property",
      "kind": "term",
      "summary": {
        "ru": "Превращает метод в атрибут (геттер). @x.setter — сеттер. @x.deleter — делитер. Вычисляемые атрибуты.",
        "en": "Turns a method into an attribute (the getter). @x.setter is the setter, @x.deleter the deleter. Computed attributes."
      },
      "body": {
        "ru": "Обращаются к нему без скобок — circle.radius, а не circle.radius(). Внутри геттера читайте резервное поле с другим именем (self._r): иначе self.radius снова вызовет то же свойство и уйдёт в бесконечную рекурсию. А если сеттер не описан, присваивание падает с AttributeError — свойство только для чтения.",
        "en": "You access it without parentheses — circle.radius, not circle.radius(). Inside the getter read a backing field with a different name (self._r), or self.radius re-triggers the same property and recurses forever. And with no setter defined, assignment raises AttributeError because the property is read-only."
      },
      "syntax": "@property\ndef x(self): return self._x\nproperty(fget=None, fset=None, fdel=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#property",
      "version": "",
      "section": "ООП",
      "subcat": "property",
      "color_group": "oop",
      "aliases": [
        "геттер и сеттер",
        "вычисляемый атрибут",
        "свойство класса"
      ],
      "keywords": [],
      "tags": [
        "oop",
        "builtin"
      ],
      "examples": [
        "class Circle:",
        "def __init__(self, r):",
        "self._r = r",
        "@property",
        "def radius(self):",
        "return self._r",
        "@radius.setter",
        "def radius(self, value):",
        "if value < 0:",
        "raise ValueError('radius must be >= 0')",
        "self._r = value",
        "c = Circle(5)",
        "print(c.radius)  # → 5",
        "c.radius = 10",
        "print(c.radius)  # → 10",
        "class Celsius:",
        "def __init__(self, temp):",
        "self.temperature = temp",
        "@property",
        "def fahrenheit(self):",
        "return self.temperature * 9/5 + 32",
        "c = Celsius(100)",
        "print(c.fahrenheit)  # → 212.0",
        "class Person:",
        "def __init__(self, first, last):",
        "self.first = first",
        "self.last = last",
        "@property",
        "def full_name(self):",
        "return f'{self.first} {self.last}'",
        "@full_name.setter",
        "def full_name(self, name):",
        "self.first, self.last = name.split()",
        "p = Person('John', 'Doe')",
        "print(p.full_name)  # → John Doe",
        "p.full_name = 'Jane Smith'",
        "print(p.first)  # → Jane",
        "class Square:",
        "def __init__(self, side):",
        "self._side = side",
        "@property",
        "def side(self):",
        "return self._side",
        "@side.setter",
        "def side(self, v):",
        "self._side = v",
        "@property",
        "def area(self):",
        "return self._side ** 2",
        "@property",
        "def perimeter(self):",
        "return 4 * self._side",
        "s = Square(5)",
        "print(s.area)      # → 25",
        "print(s.perimeter) # → 20",
        "class BankAccount:",
        "def __init__(self, balance):",
        "self._balance = balance",
        "@property",
        "def balance(self):",
        "return self._balance",
        "@balance.setter",
        "def balance(self, amount):",
        "if amount < 0:",
        "raise ValueError('Balance cannot be negative')",
        "self._balance = amount",
        "acc = BankAccount(100)",
        "print(acc.balance)  # → 100",
        "# @property.deleter",
        "class Profile:",
        "def __init__(self):",
        "self._avatar = 'default.png'",
        "@property",
        "def avatar(self):",
        "return self._avatar",
        "@avatar.deleter",
        "def avatar(self):",
        "self._avatar = 'default.png'",
        "p2 = Profile()",
        "p2._avatar = 'photo.jpg'",
        "del p2.avatar",
        "print(p2.avatar)  # → default.png",
        "# property как декоратор-класс",
        "class Temperature:",
        "def __init__(self, c=0):",
        "self._c = c",
        "temperature = property(lambda self: self._c,",
        "lambda self, v: setattr(self, '_c', v))",
        "t = Temperature(25)",
        "print(t.temperature)  # → 25"
      ],
      "related": [
        "functools.cached_property",
        "инкапсуляция-_private-__mangled",
        "методы-экземпляра"
      ],
      "related_errors": []
    },
    {
      "id": "self",
      "title": "self",
      "kind": "term",
      "summary": {
        "ru": "Ссылка на текущий экземпляр класса. Первый параметр методов экземпляра (имя — соглашение).",
        "en": "A reference to the current instance of the class. The first parameter of instance methods (the name is a convention)."
      },
      "body": {
        "ru": "self — не ключевое слово, а обычный первый параметр: Python сам подставляет туда объект, когда вы пишете b.double(), поэтому руками его не передают. Забыть self. перед атрибутом — частая ошибка: size = 2 создаёт локальную переменную, которая исчезает после метода, а self.size = 2 действительно меняет объект.",
        "en": "self isn't a keyword but an ordinary first parameter: Python fills it with the instance automatically when you write b.double(), so you never pass it yourself. Forgetting the self. prefix is a common bug — size = 2 makes a local variable that vanishes when the method ends, while self.size = 2 actually changes the object."
      },
      "syntax": "def method(self): self.x = ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/classes.html#random-remarks",
      "version": "",
      "section": "ООП",
      "subcat": "self",
      "color_group": "oop",
      "aliases": [
        "ссылка на текущий объект",
        "первый параметр метода",
        "зачем нужен первый аргумент в методе"
      ],
      "keywords": [],
      "tags": [
        "oop"
      ],
      "examples": [
        "class Box:",
        "    def __init__(self, size):",
        "        self.size = size",
        "    def double(self):",
        "        self.size *= 2",
        "        b = Box(5)",
        "        b.double()",
        "        print(b.size)  # → 10",
        "class Greeter:",
        "    def __init__(self, greeting):",
        "        self.greeting = greeting",
        "    def greet(self, name):",
        "        return f'{self.greeting}, {name}!'",
        "g = Greeter('Hi')",
        "print(g.greet('Bob'))  # → Hi, Bob!",
        "class Node:",
        "    def __init__(self, val):",
        "        self.val = val",
        "        self.next = None",
        "    def set_next(self, node):",
        "        self.next = node",
        "        n1 = Node(1)",
        "        n2 = Node(2)",
        "        n1.set_next(n2)",
        "        print(n1.next.val)  # → 2",
        "class Builder:",
        "    def __init__(self):",
        "        self.items = []",
        "    def add(self, item):",
        "        self.items.append(item)",
        "        return self  # цепочка!",
        "    def build(self):",
        "        return self.items",
        "b2 = Builder().add(1).add(2).add(3).build()",
        "print(b2)  # → [1, 2, 3]",
        "# Передача self явно",
        "class Calc:",
        "    def sq(self):",
        "        return self.n ** 2",
        "c2 = Calc()",
        "c2.n = 5",
        "print(Calc.sq(c2))  # → 25 (явно)",
        "class Logger:",
        "    prefix = '[LOG]'",
        "    def log(self, msg):",
        "        print(f'{self.prefix} {msg}')",
        "        l = Logger()",
        "        l.prefix = '[INFO]'",
        "        l.log('test')  # → [INFO] test"
      ],
      "related": [
        "методы-экземпляра",
        "__init__",
        "classmethod"
      ],
      "related_errors": []
    },
    {
      "id": "staticmethod",
      "title": "@staticmethod",
      "kind": "term",
      "summary": {
        "ru": "Статический метод. Не принимает self или cls. Просто функция внутри класса — логическая группировка.",
        "en": "A static method. It takes neither self nor cls. Just a function inside the class — a logical grouping."
      },
      "body": {
        "ru": "В отличие от classmethod статический метод не получает ни экземпляр, ни класс, поэтому до состояния объекта или класса он не дотянется. Держать его в классе, а не выносить обычной функцией, стоит лишь ради логической принадлежности к пространству имён класса; нужен доступ к классу (например, для фабрики) — берите classmethod.",
        "en": "Unlike classmethod, a static method receives neither the instance nor the class, so it can't reach object or class state. The only reason to keep it inside the class rather than write a plain function is that it logically belongs to the class namespace; if you need the class (say, for a factory), use classmethod instead."
      },
      "syntax": "@staticmethod\ndef method(...): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#staticmethod",
      "version": "",
      "section": "ООП",
      "subcat": "методы",
      "color_group": "oop",
      "aliases": [
        "статический метод",
        "метод без self"
      ],
      "keywords": [],
      "tags": [
        "oop",
        "builtin"
      ],
      "examples": [
        "class MathUtils:",
        "    @staticmethod",
        "    def add(a, b):",
        "        return a + b",
        "    @staticmethod",
        "    def multiply(a, b):",
        "        return a * b",
        "print(MathUtils.add(3, 4))       # → 7",
        "print(MathUtils.multiply(3, 4))  # → 12",
        "class Validator:",
        "    @staticmethod",
        "    def is_email(s):",
        "        return '@' in s and '.' in s.split('@')[-1]",
        "print(Validator.is_email('user@example.com'))  # → True",
        "print(Validator.is_email('not-an-email'))       # → False",
        "class Temperature:",
        "    @staticmethod",
        "    def celsius_to_fahrenheit(c):",
        "        return c * 9/5 + 32",
        "    @staticmethod",
        "    def fahrenheit_to_celsius(f):",
        "        return (f - 32) * 5/9",
        "print(Temperature.celsius_to_fahrenheit(0))  # → 32.0",
        "# Вызов через экземпляр",
        "v = Validator()",
        "print(v.is_email('a@b.com'))  # → True",
        "class StringUtils:",
        "    @staticmethod",
        "    def is_palindrome(s):",
        "        s = s.lower().replace(' ', '')",
        "        return s == s[::-1]",
        "print(StringUtils.is_palindrome('racecar'))  # → True",
        "print(StringUtils.is_palindrome('hello'))    # → False"
      ],
      "related": [
        "classmethod",
        "методы-экземпляра",
        "self"
      ],
      "related_errors": []
    },
    {
      "id": "super",
      "title": "super()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает прокси-объект родительского класса. Используется для вызова методов родителя без явного указания имени.",
        "en": "Returns a proxy object for the parent class. Used to call the parent's methods without naming it explicitly."
      },
      "body": {
        "ru": "super() вызывает не «родителя», а следующий класс в MRO (порядке разрешения методов) — при множественном наследовании это может оказаться не тот класс, что указан в скобках class. Если забыть super().__init__(), инициализация родителя молча не выполнится, и часть атрибутов останется несозданной.",
        "en": "super() doesn't call \"the parent\" literally — it forwards to the next class in the MRO (method resolution order), which under multiple inheritance may not be the base you wrote in the class header. Forget to call super().__init__() and the parent's initialization silently never runs, leaving some attributes unset."
      },
      "syntax": "super().__init__(...)\nsuper().method(...)\nsuper(type, obj_or_type)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#super",
      "version": "",
      "section": "ООП",
      "subcat": "наследование",
      "color_group": "oop",
      "aliases": [
        "вызов метода родителя",
        "вызвать конструктор родителя",
        "обращение к родительскому классу"
      ],
      "keywords": [],
      "tags": [
        "oop",
        "builtin"
      ],
      "examples": [
        "class Animal:",
        "    def __init__(self, name):",
        "        self.name = name",
        "class Dog(Animal):",
        "    def __init__(self, name, breed):",
        "        super().__init__(name)",
        "        self.breed = breed",
        "        d = Dog('Rex', 'Lab')",
        "        print(d.name, d.breed)  # → Rex Lab",
        "class Base:",
        "    def greet(self):",
        "        return 'Hello from Base'",
        "class Child(Base):",
        "    def greet(self):",
        "        return super().greet() + ' and Child'",
        "print(Child().greet())  # → Hello from Base and Child",
        "# super() с MRO",
        "class A:",
        "    def method(self):",
        "        return 'A'",
        "class B(A):",
        "    def method(self):",
        "        return 'B+' + super().method()",
        "class C(B):",
        "    def method(self):",
        "        return 'C+' + super().method()",
        "print(C().method())  # → C+B+A",
        "class LoggedList(list):",
        "    def append(self, item):",
        "        print(f'Adding {item}')",
        "        super().append(item)",
        "        ll = LoggedList()",
        "        ll.append(1)  # → Adding 1",
        "        print(ll)     # → [1]",
        "# Множественное наследование + super",
        "class A:",
        "    def __init__(self):",
        "        print('A.__init__')",
        "class B(A):",
        "    def __init__(self):",
        "        super().__init__()",
        "        print('B.__init__')",
        "class C(A):",
        "    def __init__(self):",
        "        super().__init__()",
        "        print('C.__init__')",
        "class D(B, C):",
        "    def __init__(self):",
        "        super().__init__()",
        "        print('D.__init__')",
        "        D()  # → A / C / B / D (по MRO)",
        "# super() без аргументов (Python 3)",
        "class Shape:",
        "    def area(self):",
        "        return 0",
        "class Rectangle(Shape):",
        "    def __init__(self, w, h):",
        "        self.w = w; self.h = h",
        "    def area(self):",
        "        return self.w * self.h",
        "class Square(Rectangle):",
        "    def __init__(self, side):",
        "        super().__init__(side, side)",
        "        print(Square(5).area())  # → 25"
      ],
      "related": [
        "наследование",
        "множественное-наследование-mro",
        "__init__"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "абстрактные-классы",
      "title": "Абстрактные классы",
      "kind": "term",
      "summary": {
        "ru": "abc.ABC и @abstractmethod запрещают создание экземпляров класса без реализации всех абстрактных методов.",
        "en": "abc.ABC and @abstractmethod forbid creating instances of a class until every abstract method is implemented."
      },
      "body": {
        "ru": "@abstractmethod запрещает создание экземпляров только если класс действительно наследует ABC (или задан metaclass=ABCMeta) — сам по себе декоратор ничего не блокирует. Ошибка возникает в момент вызова конструктора, а не при определении класса.",
        "en": "@abstractmethod blocks instantiation only when the class truly inherits from ABC (or sets metaclass=ABCMeta) — the decorator alone enforces nothing. The TypeError fires when you try to construct an instance, not when the class is defined."
      },
      "syntax": "from abc import ABC, abstractmethod\nclass Shape(ABC):\n    @abstractmethod\n    def area(self): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/abc.html#abc.ABC",
      "version": "3.4",
      "section": "ООП",
      "subcat": "ABC",
      "color_group": "oop",
      "aliases": [
        "запретить создание экземпляра",
        "обязательный метод в наследнике",
        "интерфейс класса"
      ],
      "keywords": [],
      "tags": [
        "oop"
      ],
      "examples": [
        "from abc import ABC, abstractmethod",
        "class Shape(ABC):",
        "@abstractmethod",
        "def area(self) -> float:",
        "pass",
        "@abstractmethod",
        "def perimeter(self) -> float:",
        "pass",
        "class Circle(Shape):",
        "def __init__(self, r):",
        "self.r = r",
        "def area(self):",
        "import math",
        "return math.pi * self.r**2",
        "def perimeter(self):",
        "import math",
        "return 2 * math.pi * self.r",
        "print(round(Circle(5).area(), 2))  # → 78.54",
        "# TypeError при попытке создать экземпляр ABC",
        "try:",
        "Shape()",
        "except TypeError as e:",
        "print(e)  # → Can't instantiate abstract class Shape...",
        "class Animal(ABC):",
        "@abstractmethod",
        "def speak(self) -> str:",
        "pass",
        "def describe(self):",
        "return f'I say: {self.speak()}'",
        "class Dog(Animal):",
        "def speak(self):",
        "return 'Woof!'",
        "print(Dog().describe())  # → I say: Woof!",
        "# Абстрактное свойство",
        "class Base(ABC):",
        "@property",
        "@abstractmethod",
        "def name(self) -> str:",
        "pass",
        "class Concrete(Base):",
        "@property",
        "def name(self):",
        "return 'Concrete'",
        "print(Concrete().name)  # → Concrete",
        "# ABCMeta без ABC",
        "from abc import ABCMeta",
        "class Interface(metaclass=ABCMeta):",
        "@abstractmethod",
        "def process(self): pass",
        "class Impl(Interface):",
        "def process(self): return 'done'",
        "print(Impl().process())  # → done",
        "# isinstance с ABC",
        "print(isinstance(Dog(), Animal))  # → True",
        "print(issubclass(Dog, Animal))    # → True"
      ],
      "related": [
        "abc.ABC",
        "abc.abstractmethod",
        "наследование",
        "protocol"
      ],
      "related_errors": []
    },
    {
      "id": "атрибуты-экземпляра-и-класса",
      "title": "Атрибуты экземпляра и класса",
      "kind": "term",
      "summary": {
        "ru": "Атрибуты класса — общие для всех экземпляров. Атрибуты экземпляра — уникальные для каждого. Изменяемые атрибуты класса — ловушка!",
        "en": "Class attributes are shared by every instance. Instance attributes belong to one instance each. Mutable class attributes are a trap!"
      },
      "body": {
        "ru": "Чтение атрибута сперва ищет его в экземпляре, потом в классе, а присваивание self.attr = ... всегда создаёт атрибут экземпляра, заслоняя классовый. Настоящая ловушка — не переприсваивание, а мутация общего изменяемого классового атрибута (например через append): она меняет его сразу для всех экземпляров.",
        "en": "Reading an attribute checks the instance first, then the class, but self.attr = ... always creates an instance attribute that shadows the class one. The real trap isn't reassignment — it's mutating a shared mutable class attribute (say, appending to a class-level list), which changes it for every instance at once."
      },
      "syntax": "class C:\n    class_attr = 0  # классовый\n    def __init__(self):\n        self.inst_attr = 1  # экземплярный",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables",
      "version": "",
      "section": "ООП",
      "subcat": "атрибуты",
      "color_group": "oop",
      "aliases": [
        "поле объекта",
        "общая переменная для всех объектов",
        "переменная класса"
      ],
      "keywords": [],
      "tags": [
        "oop"
      ],
      "examples": [
        "class Dog:",
        "species = 'Canis lupus'  # классовый",
        "def __init__(self, name):",
        "self.name = name   # экземплярный",
        "d1 = Dog('Rex')",
        "d2 = Dog('Max')",
        "print(d1.species, d2.species)  # → Canis lupus Canis lupus",
        "print(d1.name, d2.name)  # → Rex Max",
        "# Изменение классового атрибута через класс",
        "Dog.species = 'Canis familiaris'",
        "print(d1.species)  # → Canis familiaris",
        "# Затенение классового атрибута",
        "d1.species = 'Wolf'  # только у d1",
        "print(d1.species)  # → Wolf",
        "print(d2.species)  # → Canis familiaris",
        "# Изменяемый классовый атрибут — ловушка!",
        "class BadList:",
        "items = []  # общий для всех!",
        "b1 = BadList(); b2 = BadList()",
        "b1.items.append(1)",
        "print(b2.items)  # → [1] !",
        "class GoodList:",
        "def __init__(self):",
        "self.items = []  # каждому свой",
        "g1 = GoodList(); g2 = GoodList()",
        "g1.items.append(1)",
        "print(g2.items)  # → []",
        "class Counter2:",
        "count = 0",
        "def __init__(self):",
        "Counter2.count += 1",
        "self.id = Counter2.count",
        "Counter2(); c3 = Counter2()",
        "print(c3.id, Counter2.count)  # → 2 2",
        "# vars() vs dir()",
        "class A:",
        "x = 1",
        "def __init__(self):",
        "self.y = 2",
        "a = A()",
        "print(vars(a))  # → {'y': 2} (только экземплярные)"
      ],
      "related": [
        "__init__",
        "self",
        "__slots__",
        "параметры-по-умолчанию"
      ],
      "related_errors": []
    },
    {
      "id": "инкапсуляция-_private-__mangled",
      "title": "Инкапсуляция — _private, __mangled",
      "kind": "term",
      "summary": {
        "ru": "Одиночный _ — соглашение (внутренний). Двойной __ — name mangling (переименование _Class__attr). Нет настоящих private в Python.",
        "en": "A single _ is a convention (internal use). A double __ triggers name mangling (renaming to _Class__attr). Python has no truly private members."
      },
      "body": {
        "ru": "Name mangling существует, чтобы подкласс случайно не перекрыл атрибут, а не чтобы что-то спрятать — до __attr всё равно можно дотянуться как obj._Class__attr. Оно не трогает dunder-имена вроде __init__ (с завершающим __), а одиночный _ вообще ничего не запрещает — это чистое соглашение.",
        "en": "Name mangling exists so a subclass won't accidentally clobber an attribute, not to hide anything — you can still reach __attr as obj._Class__attr. It skips dunder names like __init__ (those also end in __), and a single _ enforces nothing at all, being pure convention."
      },
      "syntax": "self._protected\nself.__private",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/classes.html#private-variables",
      "version": "",
      "section": "ООП",
      "subcat": "инкапсуляция",
      "color_group": "oop",
      "aliases": [
        "приватный атрибут",
        "закрытое поле класса",
        "искажение имён"
      ],
      "keywords": [],
      "tags": [
        "oop"
      ],
      "examples": [
        "class BankAccount:",
        "    def __init__(self, balance):",
        "        self._balance = balance  # 'protected'",
        "    def get_balance(self):",
        "        return self._balance",
        "acc = BankAccount(100)",
        "print(acc.get_balance())  # → 100",
        "print(acc._balance)  # → 100 (можно, но нежелательно)",
        "class Secret:",
        "    def __init__(self):",
        "        self.__secret = 42",
        "    def reveal(self):",
        "        return self.__secret",
        "s = Secret()",
        "print(s.reveal())  # → 42",
        "try:",
        "    print(s.__secret)  # → AttributeError",
        "except AttributeError as e:",
        "    print(e)",
        "    # Name mangling",
        "    print(s._Secret__secret)  # → 42 (доступ через mangling)",
        "# В наследовании __attr не переопределяется случайно",
        "class Base:",
        "    def __init__(self):",
        "        self.__x = 'base'",
        "    def get_x(self):",
        "        return self.__x",
        "class Child(Base):",
        "    def __init__(self):",
        "        super().__init__()",
        "        self.__x = 'child'  # → _Child__x, не трогает _Base__x",
        "    def get_child_x(self):",
        "        return self.__x",
        "ch = Child()",
        "print(ch.get_x())       # → base",
        "print(ch.get_child_x()) # → child",
        "class Config:",
        "    _default = {'timeout': 30}",
        "    def __init__(self, **kwargs):",
        "        self._settings = {**self._default, **kwargs}",
        "    def get(self, key):",
        "        return self._settings.get(key)",
        "cfg = Config(timeout=60)",
        "print(cfg.get('timeout'))  # → 60",
        "# Конвенция: _ означает 'не трогай'",
        "class Transformer:",
        "    def transform(self, data):",
        "        data = self._preprocess(data)",
        "        return self._process(data)",
        "    def _preprocess(self, data):",
        "        return [x.strip() for x in data]",
        "    def _process(self, data):",
        "        return [x.upper() for x in data]",
        "t = Transformer()",
        "print(t.transform(['  hello ', ' world ']))  # → ['HELLO','WORLD']"
      ],
      "related": [
        "property",
        "атрибуты-экземпляра-и-класса",
        "методы-экземпляра"
      ],
      "related_errors": []
    },
    {
      "id": "методы-экземпляра",
      "title": "Методы экземпляра",
      "kind": "term",
      "summary": {
        "ru": "Обычные методы класса. Первый параметр — self (ссылка на экземпляр). Могут читать и изменять состояние.",
        "en": "The ordinary methods of a class. Their first parameter is self (a reference to the instance). They can read and change its state."
      },
      "body": {
        "ru": "self — не ключевое слово, а всего лишь общепринятое имя первого параметра; Python сам подставляет туда экземпляр, так что obj.method(x) — это сахар для Class.method(obj, x). Отсюда и требование указывать self явно в определении, хотя при вызове его не передают.",
        "en": "self isn't a keyword — it's just the conventional name of the first parameter, and Python passes the instance into it automatically, so obj.method(x) is sugar for Class.method(obj, x). That's why you must spell out self in the definition even though you never pass it at the call site."
      },
      "syntax": "def method(self, ...): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/datamodel.html#instance-methods",
      "version": "",
      "section": "ООП",
      "subcat": "методы",
      "color_group": "oop",
      "aliases": [
        "метод с self",
        "обычный метод класса",
        "первый параметр self"
      ],
      "keywords": [],
      "tags": [
        "oop"
      ],
      "examples": [
        "class Rectangle:",
        "def __init__(self, w, h):",
        "self.w = w",
        "self.h = h",
        "def area(self):",
        "return self.w * self.h",
        "def perimeter(self):",
        "return 2 * (self.w + self.h)",
        "r = Rectangle(3, 4)",
        "print(r.area())      # → 12",
        "print(r.perimeter()) # → 14",
        "class Stack:",
        "def __init__(self):",
        "self._data = []",
        "def push(self, item):",
        "self._data.append(item)",
        "return self  # цепочка",
        "def pop(self):",
        "return self._data.pop()",
        "def peek(self):",
        "return self._data[-1]",
        "s = Stack()",
        "s.push(1).push(2).push(3)",
        "print(s.pop())  # → 3",
        "class Temperature:",
        "def __init__(self, celsius):",
        "self.celsius = celsius",
        "def to_fahrenheit(self):",
        "return self.celsius * 9/5 + 32",
        "def to_kelvin(self):",
        "return self.celsius + 273.15",
        "t = Temperature(100)",
        "print(t.to_fahrenheit())  # → 212.0",
        "print(t.to_kelvin())      # → 373.15",
        "# Метод вызывает другой метод",
        "class Circle:",
        "def __init__(self, r):",
        "self.r = r",
        "def area(self):",
        "import math",
        "return math.pi * self.r ** 2",
        "def scale(self, factor):",
        "self.r *= factor",
        "return self.area()",
        "c = Circle(5)",
        "print(c.scale(2))  # → 314.159...",
        "class BankAccount:",
        "def __init__(self, balance=0):",
        "self.balance = balance",
        "def deposit(self, amount):",
        "self.balance += amount",
        "def withdraw(self, amount):",
        "if amount > self.balance:",
        "raise ValueError('Insufficient funds')",
        "self.balance -= amount",
        "acc = BankAccount(100)",
        "acc.deposit(50)",
        "acc.withdraw(30)",
        "print(acc.balance)  # → 120",
        "class WordCounter:",
        "def __init__(self):",
        "self._words = []",
        "def add(self, text):",
        "self._words.extend(text.split())",
        "def count(self):",
        "return len(self._words)",
        "def most_common(self):",
        "from collections import Counter",
        "return Counter(self._words).most_common(3)",
        "wc = WordCounter()",
        "wc.add('the cat sat on the mat')",
        "print(wc.count())  # → 6"
      ],
      "related": [
        "self",
        "classmethod",
        "staticmethod"
      ],
      "related_errors": []
    },
    {
      "id": "множественное-наследование-mro",
      "title": "Множественное наследование / MRO",
      "kind": "term",
      "summary": {
        "ru": "Python поддерживает множественное наследование. Порядок разрешения методов (MRO) — C3-линеаризация.",
        "en": "Python supports multiple inheritance. The method resolution order (MRO) is the C3 linearization."
      },
      "body": {
        "ru": "super() ведёт не к «родителю», а к следующему классу в __mro__, поэтому при кооперативном наследовании каждый __init__ обязан звать super().__init__() — иначе часть базовых классов молча не отработает. Если из баз нельзя построить непротиворечивый порядок (конфликт C3), Python бросит TypeError прямо на объявлении класса, а не при вызове метода.",
        "en": "super() jumps not to \"the parent\" but to the next class in __mro__, so in cooperative inheritance every __init__ must call super().__init__() or some bases silently never run. If a consistent order can't be built from the bases (a C3 conflict), Python raises TypeError right at the class definition, not when a method is called."
      },
      "syntax": "class C(A, B): ...\nC.__mro__",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/classes.html#multiple-inheritance",
      "version": "",
      "section": "ООП",
      "subcat": "наследование",
      "color_group": "oop",
      "aliases": [
        "порядок разрешения методов",
        "наследование от двух классов",
        "ромбовидное наследование"
      ],
      "keywords": [
        "MRO"
      ],
      "tags": [
        "oop"
      ],
      "examples": [
        "class A:",
        "def hello(self):",
        "return 'A'",
        "class B:",
        "def hello(self):",
        "return 'B'",
        "class C(A, B):",
        "pass",
        "print(C().hello())  # → A (первый в MRO)",
        "print(C.__mro__)  # → (C, A, B, object)",
        "# Diamond problem",
        "class Base:",
        "def method(self):",
        "return 'Base'",
        "class Left(Base):",
        "def method(self):",
        "return 'Left+' + super().method()",
        "class Right(Base):",
        "def method(self):",
        "return 'Right+' + super().method()",
        "class Diamond(Left, Right):",
        "pass",
        "print(Diamond().method())  # → Left+Right+Base (каждый Base вызывается раз!)",
        "print(Diamond.__mro__)  # → (Diamond, Left, Right, Base, object)",
        "# Mixin pattern",
        "class JSONMixin:",
        "def to_json(self):",
        "import json",
        "return json.dumps(self.__dict__)",
        "class LogMixin:",
        "def log(self):",
        "print(f'{type(self).__name__}: {self.__dict__}')",
        "class User(JSONMixin, LogMixin):",
        "def __init__(self, name, age):",
        "self.name = name",
        "self.age = age",
        "u = User('Alice', 30)",
        "print(u.to_json())  # → {\"name\": \"Alice\", \"age\": 30}",
        "# Проверка MRO",
        "class X: pass",
        "class Y(X): pass",
        "class Z(X): pass",
        "class W(Y, Z): pass",
        "print([c.__name__ for c in W.__mro__])  # → ['W','Y','Z','X','object']",
        "# super() в множественном наследовании",
        "class A:",
        "def f(self): return ['A']",
        "class B(A):",
        "def f(self): return ['B'] + super().f()",
        "class C(A):",
        "def f(self): return ['C'] + super().f()",
        "class D(B, C):",
        "def f(self): return ['D'] + super().f()",
        "print(D().f())  # → ['D','B','C','A']"
      ],
      "related": [
        "наследование",
        "super",
        "абстрактные-классы"
      ],
      "related_errors": []
    },
    {
      "id": "наследование",
      "title": "Наследование",
      "kind": "term",
      "summary": {
        "ru": "Дочерний класс наследует атрибуты и методы родительского. Переопределение методов — полиморфизм.",
        "en": "A child class inherits the attributes and methods of its parent. Overriding methods gives polymorphism."
      },
      "body": {
        "ru": "Самая частая ошибка — переопределить __init__ в потомке и забыть вызвать super().__init__(...): код родителя не отработает и его атрибуты просто не появятся. Наследование жёстко связывает классы; если нужна лишь чужая функциональность, композиция (хранить объект как поле) часто гибче, чем «класс-потомок ради переиспользования».",
        "en": "The classic mistake is overriding __init__ in the child and forgetting super().__init__(...): the parent's setup never runs and its attributes never appear. Inheritance couples classes tightly, so when you only need another class's behavior, composition (holding an object as a field) is often more flexible than subclassing just to reuse code."
      },
      "syntax": "class Child(Parent): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/classes.html#inheritance",
      "version": "",
      "section": "ООП",
      "subcat": "наследование",
      "color_group": "oop",
      "aliases": [
        "наследование классов",
        "дочерний класс",
        "базовый класс"
      ],
      "keywords": [],
      "tags": [
        "oop"
      ],
      "examples": [
        "class Animal:",
        "    def __init__(self, name):",
        "        self.name = name",
        "    def speak(self):",
        "        return '...'",
        "class Dog(Animal):",
        "    def speak(self):",
        "        return 'Woof!'",
        "class Cat(Animal):",
        "    def speak(self):",
        "        return 'Meow!'",
        "for a in [Dog('Rex'), Cat('Whiskers')]:",
        "    print(a.name, a.speak())  # → Rex Woof! / Whiskers Meow!",
        "    print(isinstance(Dog('x'), Animal))  # → True",
        "    print(isinstance(Dog('x'), Dog))    # → True",
        "    print(isinstance(Dog('x'), Cat))    # → False",
        "class Shape:",
        "    def area(self):",
        "        raise NotImplementedError",
        "class Rectangle(Shape):",
        "    def __init__(self, w, h):",
        "        self.w = w",
        "        self.h = h",
        "    def area(self):",
        "        return self.w * self.h",
        "print(Rectangle(3, 4).area())  # → 12",
        "# Дочерний класс добавляет атрибуты",
        "class Vehicle:",
        "    def __init__(self, brand):",
        "        self.brand = brand",
        "class Car(Vehicle):",
        "    def __init__(self, brand, doors):",
        "        super().__init__(brand)",
        "        self.doors = doors",
        "        c = Car('Toyota', 4)",
        "        print(c.brand, c.doors)  # → Toyota 4",
        "        # issubclass",
        "        print(issubclass(Dog, Animal))  # → True",
        "        print(issubclass(Dog, Cat))     # → False",
        "# Наследование встроенных классов",
        "class MyList(list):",
        "    def sum(self):",
        "        return sum(self)",
        "ml = MyList([1,2,3,4])",
        "print(ml.sum())  # → 10",
        "print(ml[0])     # → 1",
        "# MRO — Method Resolution Order",
        "class A:",
        "    def hello(self):",
        "        return 'A'",
        "class B(A):",
        "    pass",
        "class C(B):",
        "    pass",
        "print(C().hello())  # → A (из A)",
        "print(C.__mro__)  # → (C, B, A, object)",
        "# Переопределение с вызовом родителя",
        "class Employee:",
        "    def __init__(self, name, salary):",
        "        self.name = name",
        "        self.salary = salary",
        "    def describe(self):",
        "        return f'{self.name}: {self.salary}'",
        "class Manager(Employee):",
        "    def __init__(self, name, salary, dept):",
        "        super().__init__(name, salary)",
        "        self.dept = dept",
        "    def describe(self):",
        "        return super().describe() + f' [{self.dept}]'",
        "m = Manager('Alice', 80000, 'IT')",
        "print(m.describe())  # → Alice: 80000 [IT]"
      ],
      "related": [
        "super",
        "полиморфизм",
        "множественное-наследование-mro",
        "class"
      ],
      "related_errors": []
    },
    {
      "id": "полиморфизм",
      "title": "Полиморфизм",
      "kind": "term",
      "summary": {
        "ru": "Один интерфейс — разные реализации. В Python достигается переопределением методов и duck typing.",
        "en": "One interface — different implementations. In Python it comes from overriding methods and from duck typing."
      },
      "body": {
        "ru": "В Python объектам не обязательно иметь общего предка — хватает совпадающих имён методов (duck typing), поэтому полиморфизм работает и вовсе без наследования. Базовый класс с NotImplementedError — только соглашение; чтобы отсутствие метода ловилось при создании объекта, а не при его вызове, используйте abc.ABC с @abstractmethod.",
        "en": "In Python objects need no common ancestor — matching method names are enough (duck typing), so polymorphism works with no inheritance at all. A base class raising NotImplementedError is only a convention; to have a missing method caught when the object is created rather than when it's called, use abc.ABC with @abstractmethod."
      },
      "syntax": "for obj in objects:\n    obj.method()  # разные реализации",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-duck-typing",
      "version": "",
      "section": "ООП",
      "subcat": "полиморфизм",
      "color_group": "oop",
      "aliases": [
        "утиная типизация",
        "переопределение методов",
        "разные реализации одного метода"
      ],
      "keywords": [],
      "tags": [
        "oop"
      ],
      "examples": [
        "class Shape:",
        "    def area(self):",
        "        raise NotImplementedError",
        "class Circle:",
        "    def __init__(self, r):",
        "        self.r = r",
        "    def area(self):",
        "        import math",
        "        return math.pi * self.r**2",
        "class Rectangle:",
        "    def __init__(self, w, h):",
        "        self.w = w; self.h = h",
        "    def area(self):",
        "        return self.w * self.h",
        "shapes = [Circle(3), Rectangle(4,5)]",
        "for s in shapes:",
        "    print(round(s.area(), 2))  # → 28.27 / 20",
        "# Duck typing",
        "class Dog:",
        "    def speak(self):",
        "        return 'Woof!'",
        "class Cat:",
        "    def speak(self):",
        "        return 'Meow!'",
        "class Duck:",
        "    def speak(self):",
        "        return 'Quack!'",
        "for animal in [Dog(), Cat(), Duck()]:",
        "    print(animal.speak())  # → Woof! Meow! Quack!",
        "# Общая функция — полиморфный вызов",
        "def make_sound(animal):",
        "    print(animal.speak())",
        "    make_sound(Dog())   # → Woof!",
        "    make_sound(Duck())  # → Quack!",
        "# Перегрузка операторов — полиморфизм",
        "class Vector:",
        "    def __init__(self, x):",
        "        self.x = x",
        "    def __add__(self, other):",
        "        return Vector(self.x + other.x)",
        "class Matrix:",
        "    def __init__(self, data):",
        "        self.data = data",
        "    def __add__(self, other):",
        "        return Matrix([[a+b for a,b in zip(r1,r2)]",
        "for r1,r2 in zip(self.data, other.data)])",
        "print((Vector(1)+Vector(2)).x)  # → 3",
        "# isinstance + полиморфизм",
        "def describe(obj):",
        "    if isinstance(obj, list):",
        "        return f'list of {len(obj)}'",
        "    elif isinstance(obj, str):",
        "        return f'string: {obj!r}'",
        "    else:",
        "        return f'other: {type(obj).__name__}'",
        "print(describe([1,2,3]))  # → list of 3",
        "print(describe('hi'))     # → string: 'hi'"
      ],
      "related": [
        "наследование",
        "абстрактные-классы",
        "protocol",
        "isinstance"
      ],
      "related_errors": []
    },
    {
      "id": "chainmap",
      "title": "ChainMap",
      "kind": "term",
      "summary": {
        "ru": "collections.ChainMap объединяет несколько словарей в единый вид без копирования. Поиск ключа идёт по словарям слева направо. Запись и удаление затрагивают только первый словарь.",
        "en": "collections.ChainMap joins several dictionaries into a single view without copying them. A key is looked up in the dictionaries from left to right. Writes and deletions affect only the first one."
      },
      "body": {
        "ru": "ChainMap не копирует словари, а держит ссылки на них: изменение исходного словаря сразу видно через цепочку — это принципиальное отличие от снимка {**defaults, **overrides}. Главная ловушка — асимметрия чтения и записи: ключ ищется по всем словарям, но присваивание, pop и del работают только с первым, поэтому удаление ключа, который виден при чтении, но лежит в defaults, падает с KeyError.",
        "en": "ChainMap stores references rather than copies, so a later change in an underlying dict is visible through the chain right away — unlike the snapshot you get from {**defaults, **overrides}. The trap is the asymmetry between reads and writes: lookups scan every mapping, but assignment, pop and del touch only the first one, so deleting a key that is visible on read but actually lives in defaults raises KeyError."
      },
      "syntax": "from collections import ChainMap; ChainMap(*maps)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.ChainMap",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "collections",
      "color_group": "mapset",
      "aliases": [
        "цепочка словарей",
        "несколько словарей как один",
        "поиск ключа по нескольким словарям"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "from collections import ChainMap",
        "defaults = {\"color\": \"red\", \"size\": 10}",
        "overrides = {\"color\": \"blue\"}",
        "cm = ChainMap(overrides, defaults)",
        "print(cm[\"color\"])   # → blue",
        "print(cm[\"size\"])    # → 10",
        "print(dict(cm))      # → {'color': 'blue', 'size': 10}",
        "cm2 = cm.new_child({\"size\": 20})",
        "print(cm2[\"size\"])   # → 20"
      ],
      "related": [
        "объединение-словарей",
        "dict-merge",
        "dict.update"
      ],
      "related_errors": []
    },
    {
      "id": "copy-deepcopy-словаря",
      "title": "copy() / deepcopy() словаря",
      "kind": "term",
      "summary": {
        "ru": "dict.copy() делает поверхностную копию: вложенные объекты не копируются, а разделяются между оригиналом и копией. copy.deepcopy() копирует полностью рекурсивно.",
        "en": "dict.copy() makes a shallow copy: nested objects are not copied but shared between the original and the copy. copy.deepcopy() copies everything recursively."
      },
      "body": {
        "ru": "Чаще всего проблема не в выборе copy против deepcopy, а в том, что копии нет вовсе: b = a — это просто второе имя того же словаря, и правка через любое из имён видна обоим. copy() лечит именно это, но вложенные списки и словари остаются общими, поэтому мутация внутри копии по-прежнему меняет оригинал. deepcopy() обходит структуру рекурсивно и корректно справляется с циклическими ссылками, но платить приходится временем и памятью, а файлы, сокеты и подобные объекты он не копирует, а возвращает как есть.",
        "en": "The usual bug is not choosing copy over deepcopy but making no copy at all: b = a is just a second name for the same dict, and a change through either name shows up in both. copy() fixes exactly that, yet nested lists and dicts stay shared, so mutating them through the copy still alters the original. deepcopy() walks the structure recursively and handles cyclic references correctly, but it costs time and memory, and objects like files and sockets are not copied at all — they come back unchanged."
      },
      "syntax": "d.copy()  import copy; copy.deepcopy(d)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.copy",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "копирование",
      "color_group": "mapset",
      "aliases": [
        "глубокая копия",
        "рекурсивное копирование",
        "копия со вложенными объектами"
      ],
      "keywords": [
        "copy",
        "deepcopy"
      ],
      "tags": [
        "mapset"
      ],
      "examples": [
        "import copy",
        "orig = {\"a\": [1,2], \"b\": 3}",
        "shallow = orig.copy(); shallow[\"a\"].append(99)",
        "print(orig)     # → {'a': [1, 2, 99], 'b': 3}  (список общий)",
        "orig2 = {\"a\": [1,2], \"b\": 3}",
        "deep = copy.deepcopy(orig2); deep[\"a\"].append(99)",
        "print(orig2)    # → {'a': [1, 2], 'b': 3}        (оригинал цел)",
        "d = {\"x\": 1}; d2 = d.copy()",
        "print(d is d2)  # → False",
        "d2[\"y\"] = 2; print(d)  # → {'x': 1}  (d не изменился)"
      ],
      "related": [
        "dict.copy",
        "copy.deepcopy",
        "copy.copy",
        "вложенные-словари"
      ],
      "related_errors": []
    },
    {
      "id": "counter",
      "title": "Counter",
      "kind": "term",
      "summary": {
        "ru": "Counter из collections — словарь для подсчёта. Поддерживает арифметику, most_common, вычитание.",
        "en": "Counter from collections is a dictionary for counting. It supports arithmetic, most_common and subtraction."
      },
      "body": {
        "ru": "Обращение к отсутствующему ключу возвращает 0 и, в отличие от defaultdict, не создаёт запись — удобно, но опечатка в ключе так и останется незамеченной вместо KeyError. Ещё одна тонкость: оператор вычитания c1 - c2 выбрасывает нулевые и отрицательные счётчики, а метод subtract() их сохраняет, так что отрицательные значения возможны только через второй вариант. most_common() сортирует по убыванию, а элементы с одинаковым счётчиком идут в порядке первого появления.",
        "en": "Looking up a missing key returns 0 and, unlike defaultdict, does not insert anything — convenient, but a typo in a key silently yields 0 instead of a KeyError. Note also that the subtraction operator c1 - c2 drops zero and negative counts, while the subtract() method keeps them, so negative counts can only appear via the latter. most_common() sorts by descending count and breaks ties by first-encountered order."
      },
      "syntax": "from collections import Counter\nc = Counter(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.Counter",
      "version": "3.1",
      "section": "Словари (dict)",
      "subcat": "collections",
      "color_group": "mapset",
      "aliases": [
        "подсчёт повторений",
        "сколько раз встречается элемент",
        "самый частый элемент"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "from collections import Counter",
        "c = Counter('aabbccca')",
        "print(c)",
        "# → Counter({'a': 3, 'c': 3, 'b': 2})",
        "print(c.most_common(2))",
        "# → [('a', 3), ('c', 3)]",
        "c2 = Counter(['apple', 'banana', 'apple', 'cherry', 'apple'])",
        "print(c2.most_common(1))",
        "# → [('apple', 3)]",
        "c3 = Counter('hello')",
        "c4 = Counter('world')",
        "print(c3 + c4)",
        "# → Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, 'w': 1, 'r': 1, 'd': 1})",
        "print(c3 - c4)",
        "# → Counter({'h': 1, 'e': 1, 'l': 1}) (только положительные)",
        "print(c3 & c4)",
        "# → Counter({'l': 1, 'o': 1}) (пересечение, min)",
        "print(c3 | c4)",
        "# → Counter({'l': 2, 'h': 1, 'e': 1, 'o': 1, 'w': 1, 'r': 1, 'd': 1}) (объединение, max)",
        "words = 'the cat sat on the mat'.split()",
        "wc = Counter(words)",
        "print(wc.most_common(3))",
        "# → [('the', 2), ('cat', 1), ('sat', 1)]"
      ],
      "related": [
        "defaultdict",
        "dict.get",
        "сортировка-словаря"
      ],
      "related_errors": []
    },
    {
      "id": "defaultdict",
      "title": "defaultdict",
      "kind": "term",
      "summary": {
        "ru": "defaultdict из collections автоматически создаёт значение по умолчанию при обращении к несуществующему ключу.",
        "en": "defaultdict from collections creates a default value automatically when a missing key is accessed."
      },
      "body": {
        "ru": "Фабрика срабатывает на чтении d[key], а значит простое обращение к несуществующему ключу молча создаёт запись и раздувает словарь; если проверяешь наличие ключа, используй in или .get(), они __missing__ не вызывают. Второе — default_factory это вызываемый объект без аргументов, а не готовое значение: нужно defaultdict(list) и defaultdict(int), а не defaultdict([]) или defaultdict(0).",
        "en": "The factory fires on a read of d[key], so merely touching a missing key silently inserts an entry and grows the dict; use in or .get() when you just want to check, since neither triggers __missing__. Also, default_factory must be a zero-argument callable, not a ready value: write defaultdict(list) or defaultdict(int), never defaultdict([]) or defaultdict(0)."
      },
      "syntax": "from collections import defaultdict\nd = defaultdict(default_factory)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.defaultdict",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "collections",
      "color_group": "mapset",
      "aliases": [
        "словарь со значением по умолчанию",
        "группировка по ключу",
        "словарь списков"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "from collections import defaultdict",
        "d = defaultdict(list)",
        "d['fruits'].append('apple')",
        "d['fruits'].append('banana')",
        "print(d)",
        "# → defaultdict(<class 'list'>, {'fruits': ['apple', 'banana']})",
        "counter = defaultdict(int)",
        "for c in 'hello world':",
        "    counter[c] += 1",
        "    print(dict(counter))",
        "    # → {'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}",
        "    groups = defaultdict(set)",
        "    for w in ['cat', 'car', 'dog', 'cow']:",
        "        groups[w[0]].add(w)",
        "        print(dict(groups))",
        "# → {'c': {'cat', 'car', 'cow'}, 'd': {'dog'}}",
        "from collections import defaultdict",
        "d2 = defaultdict(lambda: 'unknown')",
        "d2['key1'] = 'value'",
        "print(d2['key1'])",
        "# → 'value'",
        "print(d2['missing'])",
        "# → 'unknown'",
        "dd = defaultdict(dict)",
        "dd['a']['nested'] = 1",
        "print(dd)",
        "# → defaultdict(<class 'dict'>, {'a': {'nested': 1}})",
        "words = ['apple', 'ant', 'bat', 'ball', 'cat']",
        "by_letter = defaultdict(list)",
        "for w in words:",
        "    by_letter[w[0]].append(w)",
        "    print(dict(by_letter))",
        "    # → {'a': ['apple', 'ant'], 'b': ['bat', 'ball'], 'c': ['cat']}",
        "    nested_dd = defaultdict(lambda: defaultdict(int))",
        "    nested_dd['row1']['col1'] += 1",
        "    print(nested_dd['row1']['col1'])",
        "    # → 1"
      ],
      "related": [
        "dict.setdefault",
        "dict.get",
        "counter"
      ],
      "related_errors": []
    },
    {
      "id": "del-для-словаря",
      "title": "del d[key]",
      "kind": "construct",
      "summary": {
        "ru": "Оператор del удаляет пару по ключу (KeyError, если ключа нет). Для удаления с возвратом значения — dict.pop()/dict.popitem().",
        "en": "The del operator removes a pair by key (KeyError if absent). To delete and return a value, use dict.pop()/dict.popitem()."
      },
      "body": {
        "ru": "del ничего не возвращает — если удалённое значение ещё нужно, берите d.pop(key), а чтобы не ловить KeyError на отсутствующем ключе, d.pop(key, None). Удалять ключи прямо в цикле for k in d нельзя: изменение размера словаря во время итерации даёт RuntimeError, поэтому сначала соберите список ключей на удаление (list(d) или списковое включение), а потом удаляйте.",
        "en": "del returns nothing — if you still need the removed value use d.pop(key), and d.pop(key, None) to avoid KeyError on a missing key. Never delete keys inside for k in d: changing the dictionary's size during iteration raises RuntimeError, so collect the keys to drop first (list(d) or a comprehension) and delete afterwards."
      },
      "syntax": "del d[key]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#mapping-types-dict",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "доступ",
      "color_group": "mapset",
      "aliases": [
        "удалить ключ из словаря",
        "удаление пары ключ-значение"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1, 'b': 2}",
        "del d['a']",
        "print(d)          # → {'b': 2}",
        "print('a' in d)   # → False"
      ],
      "related": [
        "dict.pop",
        "dict.popitem",
        "dict.clear",
        "keyerror"
      ],
      "related_errors": [
        "KeyError"
      ]
    },
    {
      "id": "dict-merge",
      "title": "d1 | d2",
      "kind": "term",
      "summary": {
        "ru": "Оператор объединения словарей (Python 3.9+). Создаёт новый словарь, объединяя d1 и d2. При конфликте ключей побеждает правый операнд.",
        "en": "The dictionary merge operator (Python 3.9+). It builds a new dictionary out of d1 and d2. On a key conflict the right operand wins."
      },
      "body": {
        "ru": "Оба операнда обязаны быть словарями: a | [('x', 1)] упадёт с TypeError, тогда как a |= [('x', 1)] и a.update([...]) такой список пар примут. Оператор строит новый словарь, копируя обе стороны целиком, поэтому в цикле по многим словарям он квадратичен — там уместнее update() или |=. До версии 3.9 тот же эффект даёт {**a, **b}.",
        "en": "Both operands must be actual dicts: a | [('x', 1)] raises TypeError, while a |= [('x', 1)] and a.update([...]) accept such a list of pairs. The operator builds a brand-new dict by copying both sides in full, so merging many dicts in a loop turns quadratic — reach for update() or |= there. Before 3.9 the same effect came from {**a, **b}."
      },
      "syntax": "merged = d1 | d2",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict",
      "version": "3.9",
      "section": "Словари (dict)",
      "subcat": "операторы",
      "color_group": "mapset",
      "aliases": [
        "объединить два словаря",
        "слить два словаря в новый",
        "склеить словари"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "a = {'x': 1, 'y': 2}",
        "b = {'y': 10, 'z': 3}",
        "result = a | b",
        "print(result)  # {'x': 1, 'y': 10, 'z': 3}",
        "# аналог без оператора (до 3.9):",
        "result = {**a, **b}"
      ],
      "related": [
        "dict-merge-update",
        "dict.update",
        "объединение-словарей",
        "слияние-и-распаковка-словарей"
      ],
      "related_errors": []
    },
    {
      "id": "dict-merge-update",
      "title": "d1 |= d2",
      "kind": "term",
      "summary": {
        "ru": "Оператор обновления словаря на месте (Python 3.9+). Добавляет/перезаписывает в d1 все пары из d2 без создания нового объекта.",
        "en": "The in-place dictionary update operator (Python 3.9+). It adds or overwrites every pair of d2 in d1 without creating a new object."
      },
      "body": {
        "ru": "Обновление идёт на месте, поэтому изменение видят все, кто ссылается на этот словарь — включая вызывающий код, если словарь пришёл аргументом в функцию. У |= есть послабление по сравнению с |: справа может стоять не только словарь, но и любой итерируемый набор пар ключ-значение, как у update(). До Python 3.9 оператора нет — там пишут update().",
        "en": "The update happens in place, so every name referring to that dict sees the change — including the caller, if the dict was passed into a function. Compared with |, the |= form is more permissive: the right operand may be any iterable of key-value pairs, not just a dict, exactly like update(). Before Python 3.9 the operator does not exist; use update() there."
      },
      "syntax": "d1 |= d2",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict",
      "version": "3.9",
      "section": "Словари (dict)",
      "subcat": "операторы",
      "color_group": "mapset",
      "aliases": [
        "слияние словарей на месте",
        "объединить словари без нового объекта"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "config = {'debug': False, 'host': 'localhost'}",
        "overrides = {'debug': True, 'port': 8080}",
        "config |= overrides",
        "print(config)",
        "# {'debug': True, 'host': 'localhost', 'port': 8080}"
      ],
      "related": [
        "dict-merge",
        "dict.update",
        "объединение-словарей"
      ],
      "related_errors": []
    },
    {
      "id": "dict.clear",
      "title": "dict.clear",
      "kind": "function",
      "summary": {
        "ru": "Удаляет все пары, оставляя пустой словарь (на месте).",
        "en": "Remove all items, leaving an empty dict (in place)."
      },
      "body": {
        "ru": "clear() чистит сам объект, поэтому пустоту увидят все, кто держит ссылку на этот словарь; присваивание d = {} лишь перевешивает имя на новый словарь, а старый остаётся прежним для остальных ссылок. Именно из-за этой разницы clear() выбирают для общих кешей и для словаря, переданного в функцию, когда очистку должен заметить вызывающий код.",
        "en": "clear() empties the object itself, so every reference to that dict sees the change; d = {} merely rebinds the name and leaves the old dict intact for anyone else holding it. That difference is why clear() is the right call for shared caches and for a dict passed into a function whose caller must see the result."
      },
      "syntax": "d.clear()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.clear",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "удаление",
      "color_group": "mapset",
      "aliases": [
        "очистить словарь",
        "удалить все ключи словаря"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1, 'b': 2}",
        "d.clear()",
        "print(d)        # → {}",
        "print(len(d))   # → 0"
      ],
      "related": [
        "del-для-словаря",
        "dict.pop",
        "list.clear"
      ],
      "related_errors": []
    },
    {
      "id": "dict.copy",
      "title": "dict.copy",
      "kind": "function",
      "summary": {
        "ru": "Возвращает поверхностную (shallow) копию словаря: вложенные объекты разделяются с оригиналом.",
        "en": "Return a shallow copy of the dict; nested objects are shared with the original."
      },
      "body": {
        "ru": "Тот же результат дают dict(d) и {**d} — все три копируют ровно один уровень и сохраняют порядок вставки. Тонкость с наследованием: если класс унаследован от dict, его copy() вернёт обычный dict, а не ваш подкласс; чтобы сохранить тип, нужен copy.copy() или собственный __copy__.",
        "en": "dict(d) and {**d} give the same result — all three copy exactly one level and preserve insertion order. One subtlety with inheritance: on a dict subclass, copy() returns a plain dict rather than an instance of your class; use copy.copy() or define __copy__ if the type must survive."
      },
      "syntax": "d.copy()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.copy",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "копирование",
      "color_group": "mapset",
      "aliases": [
        "скопировать словарь",
        "поверхностная копия"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "a = {'x': 1}",
        "b = a.copy()",
        "b['y'] = 2",
        "print(a)   # → {'x': 1}",
        "print(b)   # → {'x': 1, 'y': 2}"
      ],
      "related": [
        "copy-deepcopy-словаря",
        "copy.deepcopy",
        "list.copy"
      ],
      "related_errors": []
    },
    {
      "id": "dict.fromkeys",
      "title": "dict.fromkeys()",
      "kind": "function",
      "summary": {
        "ru": "Создаёт новый словарь из итерируемого набора ключей. Второй аргумент задаёт значение по умолчанию для всех ключей (по умолчанию None).",
        "en": "Creates a new dictionary from an iterable of keys. The second argument sets the same default value for every key (None by default)."
      },
      "body": {
        "ru": "Значение вычисляется один раз и становится общим для всех ключей, а не копируется под каждый: дайте вторым аргументом пустой список — и добавление в него по одному ключу изменит его сразу у всех. Для изменяемых значений берите словарное включение или defaultdict. Побочный, но очень ходовой приём: вызов с одним аргументом убирает дубликаты из последовательности, сохраняя порядок первого появления.",
        "en": "The value is evaluated once and shared by every key rather than copied per key: pass an empty list as the second argument and appending through one key changes it for all of them. For mutable values use a dict comprehension or defaultdict instead. A side use that comes up constantly: calling it with a single argument drops duplicates from a sequence while preserving first-occurrence order."
      },
      "syntax": "dict.fromkeys(iterable, value=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.fromkeys",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "перебор",
      "color_group": "mapset",
      "aliases": [
        "создать словарь из списка ключей",
        "заполнить словарь одинаковыми значениями",
        "словарь с ключами по умолчанию"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "print(dict.fromkeys([\"a\",\"b\",\"c\"]))      # → {'a': None, 'b': None, 'c': None}",
        "print(dict.fromkeys(\"xyz\", 0))           # → {'x': 0, 'y': 0, 'z': 0}",
        "keys = [\"name\",\"age\",\"city\"]",
        "d = dict.fromkeys(keys, \"N/A\")",
        "print(d)   # → {'name': 'N/A', 'age': 'N/A', 'city': 'N/A'}",
        "print(dict.fromkeys([1,2,1,3]))          # → {1: None, 2: None, 3: None}"
      ],
      "related": [
        "создание-словаря",
        "словарные-выражения-dict-comprehension",
        "defaultdict"
      ],
      "related_errors": []
    },
    {
      "id": "dict.get",
      "title": "dict.get",
      "kind": "function",
      "summary": {
        "ru": "Возвращает значение по ключу или default (по умолчанию None), не вызывая KeyError.",
        "en": "Return the value for a key, or default (None by default), without raising KeyError."
      },
      "body": {
        "ru": "get не отличает «ключа нет» от «ключ есть, но значение None» — обе ситуации дадут None; когда разница важна, проверяйте key in d. Второй аргумент вычисляется всегда, ещё до вызова метода: d.get(k, expensive()) запустит expensive() даже при найденном ключе. И в отличие от setdefault, get никогда не меняет словарь.",
        "en": "get cannot tell \"key missing\" from \"key present with value None\" — both give None, so use key in d when the difference matters. The default argument is evaluated eagerly, before the call happens: d.get(k, expensive()) runs expensive() even when the key is there. Unlike setdefault, get never modifies the dictionary."
      },
      "syntax": "d.get(key, default=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.get",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "доступ",
      "color_group": "mapset",
      "aliases": [
        "значение по умолчанию для ключа",
        "получить значение без ошибки",
        "если ключа нет в словаре"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1}",
        "print(d.get('a'))      # → 1",
        "print(d.get('z'))      # → None",
        "print(d.get('z', 0))   # → 0"
      ],
      "related": [
        "доступ-d-key",
        "dict.setdefault",
        "in-not-in-для-словаря",
        "keyerror"
      ],
      "related_errors": []
    },
    {
      "id": "dict.items",
      "title": "dict.items",
      "kind": "function",
      "summary": {
        "ru": "Возвращает динамическое представление (view) пар (ключ, значение); отражает изменения словаря.",
        "en": "Return a dynamic view of (key, value) pairs; reflects later changes to the dict."
      },
      "body": {
        "ru": "Это окно в словарь, а не список: пока идёт цикл по нему, добавлять и удалять ключи нельзя — интерпретатор бросит RuntimeError, так что для изменений сначала скопируйте пары в список. Само представление ведёт себя как множество: пересечение и разность двух таких представлений дают общие и различающиеся пары, если значения хешируемые.",
        "en": "It is a live window into the dict, not a list: adding or removing keys while looping over it raises RuntimeError, so snapshot the pairs into a list first if you intend to modify. The view is also set-like — intersecting or subtracting two item views gives the shared and differing pairs, as long as the values are hashable."
      },
      "syntax": "d.items()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.items",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "перебор",
      "color_group": "mapset",
      "aliases": [
        "перебрать словарь по ключам и значениям",
        "пары ключ-значение",
        "цикл по словарю"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1, 'b': 2}",
        "print(list(d.items()))   # → [('a', 1), ('b', 2)]",
        "print(dict(d.items()))   # → {'a': 1, 'b': 2}"
      ],
      "related": [
        "dict.keys",
        "dict.values",
        "распаковка-в-for",
        "сортировка-словаря"
      ],
      "related_errors": []
    },
    {
      "id": "dict.keys",
      "title": "dict.keys",
      "kind": "function",
      "summary": {
        "ru": "Возвращает динамическое представление (view) ключей словаря.",
        "en": "Return a dynamic view of the dict's keys."
      },
      "body": {
        "ru": "Представление живое, а не снимок: добавили или удалили ключ после view = d.keys() — view и его len() сразу это отражают. Отсюда типичная ошибка: менять словарь прямо внутри for k in d.keys() — Python бросит RuntimeError, сначала сделайте копию list(d.keys()). И само .keys() в цикле избыточно: for k in d перебирает ключи и так.",
        "en": "The view is live, not a snapshot: add or drop a key after view = d.keys() and both the view and its len() follow the dict. So mutating the dict inside for k in d.keys() raises RuntimeError — iterate over a list(d.keys()) copy instead. Also, plain for k in d already walks the keys, so spelling out .keys() buys nothing."
      },
      "syntax": "d.keys()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.keys",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "перебор",
      "color_group": "mapset",
      "aliases": [
        "получить все ключи словаря",
        "список ключей словаря",
        "перебрать ключи"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1, 'b': 2}",
        "print(list(d.keys()))   # → ['a', 'b']",
        "view = d.keys()",
        "d['c'] = 3",
        "print(list(view))       # → ['a', 'b', 'c'] (view динамичен)"
      ],
      "related": [
        "dict.values",
        "dict.items",
        "in-not-in-для-словаря"
      ],
      "related_errors": []
    },
    {
      "id": "dict.pop",
      "title": "dict.pop",
      "kind": "function",
      "summary": {
        "ru": "Удаляет ключ и возвращает его значение; default или KeyError, если ключа нет.",
        "en": "Remove a key and return its value; default or KeyError if the key is absent."
      },
      "body": {
        "ru": "Не путайте с list.pop(): у списка аргумент — индекс и второго параметра-заглушки нет, здесь же первый аргумент — ключ. d.pop(key, None) — стандартный способ снести ключ, которого может не быть: del d[key] в такой ситуации упадёт с KeyError. Возврат значения делает pop удобным, когда нужно «забрать и удалить» за одно действие, например вынуть служебный ключ из словаря параметров.",
        "en": "Do not confuse it with list.pop(), whose argument is an index and which has no default parameter; here the first argument is a key. d.pop(key, None) is the usual way to drop a key that may not be there — del d[key] would raise KeyError instead. Because it returns the value, pop is handy when you need to take and remove in one step, such as pulling a service key out of a dict of options."
      },
      "syntax": "d.pop(key, default)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.pop",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "удаление",
      "color_group": "mapset",
      "aliases": [
        "удалить ключ из словаря",
        "извлечь значение и удалить ключ"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1, 'b': 2}",
        "print(d.pop('a'))       # → 1",
        "print(d.pop('z', -1))   # → -1",
        "print(d)                # → {'b': 2}"
      ],
      "related": [
        "dict.popitem",
        "del-для-словаря",
        "dict.get",
        "list.pop"
      ],
      "related_errors": [
        "KeyError"
      ]
    },
    {
      "id": "dict.popitem",
      "title": "dict.popitem",
      "kind": "function",
      "summary": {
        "ru": "Удаляет и возвращает последнюю добавленную пару (LIFO, с Python 3.7); KeyError на пустом словаре.",
        "en": "Remove and return the last inserted (key, value) pair (LIFO since 3.7); KeyError if empty."
      },
      "body": {
        "ru": "LIFO здесь означает именно стек: popitem() отдаёт последнюю вставленную пару, а не самую старую; за FIFO идите в collections.OrderedDict с popitem(last=False) или в deque. Зато это безопасный способ вычерпать словарь в цикле while d — удалять ключи прямо по ходу итерации по самому словарю нельзя, будет RuntimeError о смене размера.",
        "en": "LIFO here means a stack: popitem() hands back the most recently inserted pair, not the oldest one; for FIFO reach for collections.OrderedDict with popitem(last=False), or a deque. It is also the safe way to drain a dict in a while d loop, since deleting keys while iterating over the dict itself raises RuntimeError about the size changing."
      },
      "syntax": "d.popitem()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.popitem",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "удаление",
      "color_group": "mapset",
      "aliases": [
        "удалить последнюю пару словаря",
        "извлечь последний элемент словаря"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1, 'b': 2}",
        "print(d.popitem())   # → ('b', 2)",
        "print(d)             # → {'a': 1}"
      ],
      "related": [
        "dict.pop",
        "del-для-словаря",
        "ordereddict"
      ],
      "related_errors": [
        "KeyError"
      ]
    },
    {
      "id": "dict.setdefault",
      "title": "dict.setdefault",
      "kind": "function",
      "summary": {
        "ru": "Возвращает значение по ключу; если ключа нет — добавляет его со значением default и возвращает default.",
        "en": "Return the value for a key; if absent, insert it with default and return default."
      },
      "body": {
        "ru": "Главная идиома — группировка: d.setdefault(k, []).append(x), потому что метод возвращает именно тот объект, что лежит (или только что лёг) в словаре. Но default вычисляется при каждом вызове, даже когда ключ уже есть, — новый пустой список создаётся и тут же выбрасывается; если группировок много, чище и быстрее collections.defaultdict(list).",
        "en": "The classic idiom is grouping: d.setdefault(k, []).append(x), since the method returns the very object stored in (or just placed into) the dictionary. The default is built on every call even when the key already exists — a fresh empty list is created and immediately discarded — so for heavy grouping collections.defaultdict(list) is cleaner and faster."
      },
      "syntax": "d.setdefault(key, default=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.setdefault",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "доступ",
      "color_group": "mapset",
      "aliases": [
        "добавить ключ, если его нет",
        "создать ключ со значением по умолчанию"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1}",
        "print(d.setdefault('a', 0))   # → 1",
        "print(d.setdefault('b', 2))   # → 2",
        "print(d)                      # → {'a': 1, 'b': 2}"
      ],
      "related": [
        "dict.get",
        "defaultdict",
        "добавление-изменение-d-key-val"
      ],
      "related_errors": []
    },
    {
      "id": "dict.update",
      "title": "dict.update",
      "kind": "function",
      "summary": {
        "ru": "Добавляет или заменяет пары из другого словаря, итерируемого пар или именованных аргументов (на месте).",
        "en": "Add or replace pairs from another dict, an iterable of pairs, or keyword arguments (in place)."
      },
      "body": {
        "ru": "Метод меняет словарь на месте и возвращает None, так что строка d = d.update(other) молча превратит d в None — классическая студенческая опечатка. Совпадающие ключи перезаписываются без предупреждения, прежние значения теряются; если нужен новый словарь, а не мутация исходного, берите d1 | d2 (Python 3.9+). Аргументом может быть не только словарь, но и любой итерируемый объект пар — например, список кортежей или zip.",
        "en": "update() mutates the dict in place and returns None, so d = d.update(other) silently turns d into None — a classic beginner typo. Colliding keys are overwritten without warning and the old values are gone; if you want a new dict rather than a mutated original, use d1 | d2 (Python 3.9+). The argument need not be a dict either: any iterable of key/value pairs works, such as a list of tuples or a zip object."
      },
      "syntax": "d.update(other, **kwargs)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.update",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "изменение",
      "color_group": "mapset",
      "aliases": [
        "обновить словарь другим словарём",
        "добавить несколько пар сразу"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1}",
        "d.update({'b': 2})",
        "d.update(c=3)",
        "print(d)        # → {'a': 1, 'b': 2, 'c': 3}",
        "d.update(a=10)",
        "print(d['a'])   # → 10 (перезапись)"
      ],
      "related": [
        "dict-merge-update",
        "объединение-словарей",
        "добавление-изменение-d-key-val"
      ],
      "related_errors": []
    },
    {
      "id": "dict.values",
      "title": "dict.values",
      "kind": "function",
      "summary": {
        "ru": "Возвращает динамическое представление (view) значений словаря.",
        "en": "Return a dynamic view of the dict's values."
      },
      "body": {
        "ru": "В отличие от keys(), это представление не ведёт себя как множество: значения могут повторяться и не обязаны быть хешируемыми, поэтому операций & , | и - у values() нет. Проверка x in d.values() линейна — она перебирает все значения, тогда как поиск по ключу стоит O(1); если ищете по значению часто, стройте обратный словарь.",
        "en": "Unlike keys(), this view is not set-like: values may repeat and need not be hashable, so &, | and - are unavailable. Membership x in d.values() is a linear scan, while a key lookup is O(1) — if you search by value often, build a reversed mapping instead."
      },
      "syntax": "d.values()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.values",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "перебор",
      "color_group": "mapset",
      "aliases": [
        "получить все значения словаря",
        "список значений словаря",
        "перебрать значения"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1, 'b': 2}",
        "print(list(d.values()))   # → [1, 2]",
        "print(sum(d.values()))    # → 3"
      ],
      "related": [
        "dict.keys",
        "dict.items",
        "in-not-in-для-словаря"
      ],
      "related_errors": []
    },
    {
      "id": "in-not-in-для-словаря",
      "title": "in / not in для словаря",
      "kind": "term",
      "summary": {
        "ru": "in проверяет наличие КЛЮЧА в словаре за O(1). Для проверки значений используйте in d.values().",
        "en": "in checks for a KEY in the dictionary, in O(1). To test the values, use in d.values()."
      },
      "body": {
        "ru": "Ключ проверяется через хеш, поэтому нехешируемый объект даёт не False, а TypeError: unhashable type — например, [] in d. Пары in так не проверить: нужно ('a', 1) in d.items(). А связка if k in d: d[k] делает два поиска подряд — обычно чище d.get(k, default) или try/except KeyError.",
        "en": "Membership hashes the key, so an unhashable object raises TypeError: unhashable type rather than returning False — [] in d is the classic case. It never tests pairs; for that you need ('a', 1) in d.items(). And if k in d: d[k] does two lookups in a row — d.get(k, default) or try/except KeyError is usually cleaner."
      },
      "syntax": "key in d  |  key not in d",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#mapping-types-dict",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "поиск",
      "color_group": "mapset",
      "aliases": [
        "проверить есть ли ключ в словаре",
        "есть ли такой ключ",
        "проверка наличия ключа"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "d = {'a': 1, 'b': 2}",
        "print('a' in d)",
        "# → True",
        "print('c' in d)",
        "# → False",
        "print('c' not in d)",
        "# → True",
        "print(1 in d)",
        "# → False (1 — не ключ!)",
        "print(1 in d.values())",
        "# → True (проверка значений)"
      ],
      "related": [
        "dict.get",
        "доступ-d-key",
        "dict.values",
        "in-not-in-для-списков"
      ],
      "related_errors": []
    },
    {
      "id": "ordereddict",
      "title": "OrderedDict",
      "kind": "term",
      "summary": {
        "ru": "OrderedDict из collections гарантирует порядок вставки (в Python 3.7+ обычный dict тоже сохраняет порядок). Имеет move_to_end() и popitem(last=).",
        "en": "OrderedDict from collections guarantees insertion order (since Python 3.7 a plain dict keeps it too). It also has move_to_end() and popitem(last=)."
      },
      "body": {
        "ru": "Заметное отличие от обычного словаря — сравнение: два OrderedDict равны только при совпадающем порядке ключей, а вот OrderedDict и обычный dict сравниваются как обычные словари, без учёта порядка. Брать OrderedDict сегодня стоит только ради move_to_end() и popitem(last=False) — например, для LRU-кеша; для простого «сохранить порядок вставки» хватает dict, он легче и быстрее.",
        "en": "The visible difference from a plain dict is equality: two OrderedDicts compare equal only if the key order matches, while an OrderedDict compared against a plain dict is order-insensitive. These days reach for it only when you need move_to_end() or popitem(last=False) — an LRU cache, say; if you just want insertion order preserved, a plain dict is lighter and faster."
      },
      "syntax": "from collections import OrderedDict\nod = OrderedDict()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/collections.html#collections.OrderedDict",
      "version": "3.1",
      "section": "Словари (dict)",
      "subcat": "collections",
      "color_group": "mapset",
      "aliases": [
        "упорядоченный словарь",
        "порядок ключей в словаре",
        "переместить ключ в конец"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "from collections import OrderedDict",
        "od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])",
        "print(od)",
        "# → OrderedDict([('a', 1), ('b', 2), ('c', 3)])",
        "od.move_to_end('a')",
        "print(od)",
        "# → OrderedDict([('b', 2), ('c', 3), ('a', 1)])",
        "od.move_to_end('a', last=False)",
        "print(list(od.keys())[0])",
        "# → 'a' (переместить в начало)",
        "print(od.popitem(last=True))",
        "# → ('a', 1) (последний)",
        "print(od.popitem(last=False))",
        "# → ('b', 2) (первый)"
      ],
      "related": [
        "создание-словаря",
        "dict.popitem",
        "сортировка-словаря"
      ],
      "related_errors": []
    },
    {
      "id": "вложенные-словари",
      "title": "Вложенные словари",
      "kind": "term",
      "summary": {
        "ru": "Значения словаря могут быть любого типа, включая другие словари. Доступ через цепочку ключей.",
        "en": "Dictionary values may be of any type, including other dictionaries. They are reached through a chain of keys."
      },
      "body": {
        "ru": "Присваивание не создаёт промежуточные уровни: d['a']['b'] = 1 упадёт с KeyError, если ключа 'a' ещё нет, — сначала d.setdefault('a', {}) или collections.defaultdict. Цепочка d.get(k1, {}).get(k2) безопасна для чтения, но каждый вызов создаёт пустой словарь-заглушку. И помните, что d.copy() копирует только верхний уровень: вложенные словари останутся общими с оригиналом, для независимой копии нужен copy.deepcopy().",
        "en": "Assignment does not create intermediate levels: d['a']['b'] = 1 raises KeyError when 'a' is missing — use d.setdefault('a', {}) or collections.defaultdict first. The d.get(k1, {}).get(k2) chain is safe for reading, though each call builds a throwaway empty dict. Also note that d.copy() is shallow: nested dictionaries stay shared with the original, so use copy.deepcopy() for a truly independent copy."
      },
      "syntax": "d[key1][key2]  |  d.get(k1, {}).get(k2)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#mapping-types-dict",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "вложенность",
      "color_group": "mapset",
      "aliases": [
        "словарь внутри словаря",
        "многоуровневый словарь",
        "доступ по цепочке ключей"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "user = {'name': 'Alice', 'address': {'city': 'Moscow', 'zip': '101000'}}",
        "print(user['address']['city'])",
        "# → 'Moscow'",
        "user['address']['country'] = 'Russia'",
        "print(user['address'])",
        "# → {'city': 'Moscow', 'zip': '101000', 'country': 'Russia'}",
        "db = {'alice': {'age': 30}, 'bob': {'age': 25}}",
        "for name, info in db.items():",
        "    print(name, info['age'])",
        "    # → alice 30 / bob 25",
        "    print(user.get('phone', {}).get('mobile', 'N/A'))",
        "    # → 'N/A' (безопасный доступ)",
        "    catalog = {}",
        "    catalog.setdefault('fruits', {})['apple'] = 1.5",
        "    catalog.setdefault('fruits', {})['banana'] = 0.8",
        "    print(catalog)",
        "    # → {'fruits': {'apple': 1.5, 'banana': 0.8}}",
        "    nested = {'a': {'b': {'c': 42}}}",
        "    print(nested['a']['b']['c'])",
        "    # → 42",
        "    config = {'db': {'host': 'localhost', 'port': 5432}, 'debug': True}",
        "    config['db']['host'] = '192.168.1.1'",
        "    print(config['db'])",
        "    # → {'host': '192.168.1.1', 'port': 5432}"
      ],
      "related": [
        "dict.get",
        "dict.setdefault",
        "copy-deepcopy-словаря",
        "json"
      ],
      "related_errors": []
    },
    {
      "id": "добавление-изменение-d-key-val",
      "title": "d[key] = value",
      "kind": "construct",
      "summary": {
        "ru": "Присваивание d[key] = value добавляет новый ключ или заменяет значение существующего. Для нескольких пар сразу — dict.update().",
        "en": "Assigning d[key] = value adds a new key or replaces an existing value. For several pairs at once, use dict.update()."
      },
      "body": {
        "ru": "Ключ обязан быть хешируемым: строка, число, кортеж из неизменяемых элементов подойдут, а список или множество дадут TypeError: unhashable type. Начиная с Python 3.7 порядок вставки гарантирован, и перезапись существующего ключа не двигает его в конец — позиция сохраняется; чтобы ключ переехал в хвост, его нужно сначала удалить и вставить заново.",
        "en": "The key must be hashable: strings, numbers and tuples of immutable items work, while a list or a set raises TypeError: unhashable type. Since Python 3.7 insertion order is guaranteed, and overwriting an existing key keeps its original position rather than moving it to the end — to push a key to the back you have to delete it and insert it again."
      },
      "syntax": "d[key] = value",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#mapping-types-dict",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "доступ",
      "color_group": "mapset",
      "aliases": [
        "добавить элемент в словарь",
        "изменить значение по ключу",
        "записать значение в словарь"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1}",
        "d['b'] = 2",
        "print(d)   # → {'a': 1, 'b': 2}",
        "d['a'] = 99",
        "print(d)   # → {'a': 99, 'b': 2}"
      ],
      "related": [
        "доступ-d-key",
        "dict.update",
        "dict.setdefault",
        "del-для-словаря"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "доступ-d-key",
      "title": "Доступ d[key]",
      "kind": "construct",
      "summary": {
        "ru": "d[key] возвращает значение по ключу или вызывает KeyError, если ключа нет. Чтобы избежать ошибки при отсутствии ключа — dict.get() с default.",
        "en": "d[key] returns the value for a key or raises KeyError if absent. To avoid the error on a missing key, use dict.get() with a default."
      },
      "body": {
        "ru": "Чтение и запись через квадратные скобки несимметричны: d[key] = 1 спокойно создаст отсутствующий ключ, а чтение того же ключа упадёт с KeyError. Поиск по ключу в среднем O(1), поэтому проверка in перед доступом почти ничего не стоит; но если ключ обычно на месте, дешевле сразу читать и перехватывать KeyError. И учтите: d.get(key) вернёт None и для пропущенного ключа, и для ключа со значением None — различить эти случаи можно только через in или собственный объект-заглушку в качестве default.",
        "en": "Reading and writing through square brackets are not symmetric: d[key] = 1 happily creates a missing key, while reading that same key raises KeyError. Key lookup is O(1) on average, so an in check before access costs almost nothing; but when the key is normally present, it is cheaper to read and catch KeyError instead. Note too that d.get(key) returns None both for a missing key and for a key whose value is None — only in, or your own sentinel as the default, tells those apart."
      },
      "syntax": "d[key]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#mapping-types-dict",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "доступ",
      "color_group": "mapset",
      "aliases": [
        "получить значение по ключу",
        "обращение к словарю по ключу",
        "чтение элемента словаря"
      ],
      "keywords": [],
      "tags": [
        "dict"
      ],
      "examples": [
        "d = {'a': 1, 'b': 2}",
        "print(d['a'])         # → 1",
        "print('z' in d)       # → False (проверка перед d[key])"
      ],
      "related": [
        "dict.get",
        "keyerror",
        "in-not-in-для-словаря",
        "добавление-изменение-d-key-val"
      ],
      "related_errors": [
        "KeyError",
        "TypeError"
      ]
    },
    {
      "id": "объединение-словарей",
      "title": "Объединение словарей",
      "kind": "term",
      "summary": {
        "ru": "update() изменяет словарь на месте. ** распаковка создаёт новый словарь. | (Python 3.9+) — оператор слияния.",
        "en": "update() modifies the dictionary in place. ** unpacking builds a new one. | (Python 3.9+) is the merge operator."
      },
      "body": {
        "ru": "update() ничего не возвращает, и попытка написать d = d1.update(d2) кладёт в d значение None — самая частая ошибка на этом месте. Выбор способа сводится к одному вопросу: нужен ли исходный словарь дальше в неизменном виде. Во всех трёх вариантах значения переносятся по ссылке: вложенные списки и словари остаются общими с исходниками.",
        "en": "update() returns None, so writing d = d1.update(d2) silently leaves you with None — the classic mistake here. Picking a method comes down to one question: do you still need the original dict untouched. All three variants copy values by reference, so nested lists and dicts stay shared with the originals."
      },
      "syntax": "d1.update(d2)  |  {**d1, **d2}  |  d1 | d2",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict.update",
      "version": "3.9",
      "section": "Словари (dict)",
      "subcat": "операторы",
      "color_group": "mapset",
      "aliases": [
        "слить два словаря",
        "добавить один словарь в другой"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "d1 = {'a': 1, 'b': 2}",
        "d2 = {'b': 20, 'c': 3}",
        "d1.update(d2)",
        "print(d1)",
        "# → {'a': 1, 'b': 20, 'c': 3}",
        "d3 = {'a': 1, 'b': 2}",
        "merged = {**d3, **d2}",
        "print(merged)",
        "# → {'a': 1, 'b': 20, 'c': 3}",
        "d4 = d3 | d2",
        "print(d4)",
        "# → {'a': 1, 'b': 20, 'c': 3} (Python 3.9+)",
        "extra = {'x': 9}",
        "combined = {**d3, **d2, **extra}",
        "print(combined)",
        "# → {'a': 1, 'b': 20, 'c': 3, 'x': 9}",
        "print(d3)",
        "# → {'a': 1, 'b': 2} (** не изменяет оригинал)",
        "d5 = {'a': 1}",
        "d5 |= {'b': 2, 'a': 99}",
        "print(d5)",
        "# → {'a': 99, 'b': 2}"
      ],
      "related": [
        "dict.update",
        "dict-merge",
        "слияние-и-распаковка-словарей",
        "оператор-слияния-словарей"
      ],
      "related_errors": []
    },
    {
      "id": "оператор-слияния-словарей",
      "title": "| оператор слияния словарей",
      "kind": "term",
      "summary": {
        "ru": "Оператор | (Python 3.9+) объединяет два словаря в новый. При совпадении ключей значение берётся из правого словаря. |= обновляет словарь на месте.",
        "en": "The | operator (Python 3.9+) merges two dictionaries into a new one. For a key present in both, the value comes from the right one. |= updates the dictionary in place."
      },
      "body": {
        "ru": "Копия получается поверхностной: вложенные списки и словари в результате — те же самые объекты, и правка через один словарь видна через другой. Порядок ключей задаёт левый операнд: общий ключ остаётся на своём прежнем месте, но уже с новым значением, а ключи, которые есть только справа, дописываются в конец.",
        "en": "The merge is shallow: nested lists and dicts in the result are the very same objects, so a change made through one dict shows up in the other. Key order follows the left operand — a shared key keeps its original position but takes the new value, while keys unique to the right operand are appended at the end."
      },
      "syntax": "d1 | d2  d1 |= d2",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict",
      "version": "3.9",
      "section": "Словари (dict)",
      "subcat": "операторы",
      "color_group": "mapset",
      "aliases": [
        "объединить словари одним оператором",
        "новый словарь из двух словарей"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "a = {\"x\": 1, \"y\": 2}; b = {\"y\": 99, \"z\": 3}",
        "print(a | b)       # → {'x': 1, 'y': 99, 'z': 3}",
        "print(b | a)       # → {'y': 2, 'z': 3, 'x': 1}",
        "a |= b; print(a)   # → {'x': 1, 'y': 99, 'z': 3}",
        "print({} | {\"k\": 1})  # → {'k': 1}",
        "c = {\"p\": 5}; c |= {\"q\": 6}; print(c)  # → {'p': 5, 'q': 6}"
      ],
      "related": [
        "dict-merge",
        "dict-merge-update",
        "объединение-словарей"
      ],
      "related_errors": []
    },
    {
      "id": "слияние-и-распаковка-словарей",
      "title": "Слияние и распаковка ** словарей",
      "kind": "term",
      "summary": {
        "ru": "** распаковывает словарь в именованные аргументы или создаёт слияние. При дублировании ключей побеждает последний.",
        "en": "** unpacks a dictionary into keyword arguments or builds a merged one. On duplicate keys the last one wins."
      },
      "body": {
        "ru": "И {**d1, **d2}, и d1 | d2 делают поверхностную копию: вложенные списки и словари остаются общими с исходниками, и правка через одну ссылку видна в другой. Оператор | (3.9+) требует, чтобы обе стороны были именно dict, тогда как {**a, **b} примет любое отображение; при вызове f(**d) все ключи обязаны быть строками, иначе TypeError. Для изменения на месте есть d1 |= d2 — аналог update().",
        "en": "Both {**d1, **d2} and d1 | d2 copy shallowly: nested lists and dicts stay shared with the originals, so a change through one reference shows up in the other. The | operator (3.9+) demands real dicts on both sides, while {**a, **b} accepts any mapping; and in a call f(**d) every key must be a string or you get TypeError. For an in-place merge use d1 |= d2, the equivalent of update()."
      },
      "syntax": "func(**d)  |  {**d1, **d2}  |  d1 | d2 (Python 3.9+)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#dictionary-displays",
      "version": "3.5",
      "section": "Словари (dict)",
      "subcat": "распаковка",
      "color_group": "mapset",
      "aliases": [
        "распаковка словаря звёздочками",
        "передать словарь как именованные аргументы"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "def greet(name, age):",
        "    return f'{name} is {age}'",
        "info = {'name': 'Alice', 'age': 30}",
        "print(greet(**info))",
        "# → 'Alice is 30'",
        "d1 = {'a': 1, 'b': 2}",
        "d2 = {'c': 3, 'd': 4}",
        "merged = {**d1, **d2}",
        "print(merged)",
        "# → {'a': 1, 'b': 2, 'c': 3, 'd': 4}",
        "d3 = {'a': 1}",
        "d4 = {'a': 99, 'b': 2}",
        "print({**d3, **d4})",
        "# → {'a': 99, 'b': 2} (d4 перекрывает d3)",
        "defaults = {'color': 'black', 'size': 'M'}",
        "user_prefs = {'size': 'L'}",
        "settings = {**defaults, **user_prefs}",
        "print(settings)",
        "# → {'color': 'black', 'size': 'L'}",
        "print({**{'x': 1}, 'y': 2, **{'z': 3}})",
        "# → {'x': 1, 'y': 2, 'z': 3}"
      ],
      "related": [
        "kwargs",
        "объединение-словарей",
        "dict-merge",
        "распаковка-списка"
      ],
      "related_errors": []
    },
    {
      "id": "словарные-выражения-dict-comprehension",
      "title": "Словарные выражения (dict comprehension)",
      "kind": "function",
      "summary": {
        "ru": "Dict comprehension создаёт словарь из итерируемого объекта. Поддерживает фильтрацию.",
        "en": "A dict comprehension builds a dictionary out of an iterable. Filtering is supported."
      },
      "body": {
        "ru": "Если выражение для ключа даёт повторы, исключения не будет — победит последнее вычисленное значение, и часть элементов молча исчезнет, поэтому длина результата может оказаться меньше длины исходной последовательности. Двоеточие — единственное, что отличает dict comprehension от set comprehension: {f(x) for x in xs} собирает множество, а не словарь. Переменная цикла живёт только внутри выражения и наружу не утекает.",
        "en": "If the key expression produces duplicates nothing is raised — the last computed value wins and the earlier entries vanish silently, so the result can be shorter than the source sequence. The colon is the only thing separating a dict comprehension from a set comprehension: {f(x) for x in xs} builds a set, not a mapping. The loop variable is scoped to the comprehension and does not leak into the surrounding code."
      },
      "syntax": "{k: v for ... in ...}  |  {k: v for ... in ... if cond}",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#dictionary-displays",
      "version": "3.5",
      "section": "Словари (dict)",
      "subcat": "comprehension",
      "color_group": "mapset",
      "aliases": [
        "генератор словаря",
        "словарь одной строкой",
        "поменять местами ключи и значения"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "squares = {x: x**2 for x in range(5)}",
        "print(squares)",
        "# → {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}",
        "d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}",
        "even_vals = {k: v for k, v in d.items() if v % 2 == 0}",
        "print(even_vals)",
        "# → {'b': 2, 'd': 4}",
        "inverted = {v: k for k, v in d.items()}",
        "print(inverted)",
        "# → {1: 'a', 2: 'b', 3: 'c', 4: 'd'}",
        "words = ['hello', 'world', 'python']",
        "lengths = {w: len(w) for w in words}",
        "print(lengths)",
        "# → {'hello': 5, 'world': 5, 'python': 6}",
        "keys = ['a', 'b', 'c']",
        "vals = [1, 2, 3]",
        "combined = {k: v for k, v in zip(keys, vals)}",
        "print(combined)",
        "# → {'a': 1, 'b': 2, 'c': 3}",
        "upper_d = {k.upper(): v*2 for k, v in d.items()}",
        "print(upper_d)",
        "# → {'A': 2, 'B': 4, 'C': 6, 'D': 8}",
        "matrix = {(i, j): i*j for i in range(3) for j in range(3)}",
        "print(matrix[(2, 2)])",
        "# → 4",
        "data = [('a', 1), ('b', 2), ('a', 3)]",
        "last = {k: v for k, v in data}",
        "print(last)",
        "# → {'a': 3, 'b': 2} (последнее значение для дублей)"
      ],
      "related": [
        "списочные-выражения-list-comprehension",
        "генераторы-множеств-set-comprehension",
        "создание-словаря",
        "dict.fromkeys"
      ],
      "related_errors": []
    },
    {
      "id": "создание-словаря",
      "title": "Создание словаря",
      "kind": "term",
      "summary": {
        "ru": "Словарь создаётся литералом {k:v}, конструктором dict(), dict.fromkeys() или словарным выражением.",
        "en": "A dictionary is written as a {k: v} literal, or built with dict(), dict.fromkeys() or a dict comprehension."
      },
      "body": {
        "ru": "{} — это пустой словарь, а не множество: пустое множество создаётся только через set(). У dict.fromkeys(keys, []) значение вычисляется один раз, и все ключи получают один и тот же список — добавили элемент по одному ключу, он «появился» у всех; для независимых изменяемых значений берите словарное выражение. Форма dict(a=1) работает лишь с ключами-идентификаторами, произвольные строки так не задать.",
        "en": "{} is an empty dict, never an empty set — that one is only set(). In dict.fromkeys(keys, []) the value is evaluated once, so every key ends up bound to the very same list and an append through one key appears under all of them; use a dict comprehension when each value must be its own mutable object. The dict(a=1) form only accepts keys that are valid identifiers, so arbitrary strings need the literal."
      },
      "syntax": "{}  |  dict()  |  {k: v}  |  dict.fromkeys(keys[, val])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#dict",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "создание",
      "color_group": "mapset",
      "aliases": [
        "пустой словарь",
        "объявить словарь",
        "инициализация словаря"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "d1 = {}",
        "# → {}",
        "d2 = {'a': 1, 'b': 2}",
        "# → {'a': 1, 'b': 2}",
        "d3 = dict(x=1, y=2)",
        "# → {'x': 1, 'y': 2}",
        "d4 = dict([('a', 1), ('b', 2)])",
        "# → {'a': 1, 'b': 2}",
        "d5 = dict.fromkeys(['a', 'b', 'c'], 0)",
        "print(d5)",
        "# → {'a': 0, 'b': 0, 'c': 0}",
        "d6 = dict.fromkeys('abc')",
        "print(d6)",
        "# → {'a': None, 'b': None, 'c': None}",
        "d7 = {i: i**2 for i in range(4)}",
        "print(d7)",
        "# → {0: 0, 1: 1, 2: 4, 3: 9}"
      ],
      "related": [
        "dict.fromkeys",
        "словарные-выражения-dict-comprehension",
        "dict",
        "создание-множества"
      ],
      "related_errors": []
    },
    {
      "id": "сортировка-словаря",
      "title": "Сортировка словаря",
      "kind": "term",
      "summary": {
        "ru": "sorted(d.items()) сортирует пары. key= задаёт критерий сортировки. Результат — список кортежей или новый словарь.",
        "en": "sorted(d.items()) sorts the pairs. key= sets the sorting criterion. The result is a list of tuples, or a new dictionary."
      },
      "body": {
        "ru": "sorted() возвращает список кортежей, а не словарь: чтобы снова получить dict, оберните результат в dict() — порядок сохранится, словарь помнит порядок вставки с версии 3.7. Сортировки «на месте» у dict нет вообще, есть только построение нового объекта. По умолчанию сравнение начинается с ключей, поэтому смешанные типы ключей (int рядом с str) дадут TypeError, а чтобы отсортировать по убыванию значения и разрешить ничьи по алфавиту, берут составной ключ вида (-значение, ключ).",
        "en": "sorted() hands back a list of tuples, not a dict; wrap it in dict() to get a dictionary again — insertion order is preserved since 3.7. There is no in-place sort for a dict at all, only building a new object. The default comparison starts with the keys, so mixed key types (int next to str) raise TypeError; to sort by descending value and break ties alphabetically, use a compound key like (-value, key)."
      },
      "syntax": "sorted(d.items())  |  sorted(d.items(), key=lambda x: x[1])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#sorted",
      "version": "",
      "section": "Словари (dict)",
      "subcat": "сортировка",
      "color_group": "mapset",
      "aliases": [
        "отсортировать словарь по значению",
        "отсортировать словарь по ключу"
      ],
      "keywords": [],
      "tags": [
        "mapset"
      ],
      "examples": [
        "d = {'banana': 3, 'apple': 1, 'cherry': 2}",
        "print(sorted(d.items()))",
        "# → [('apple', 1), ('banana', 3), ('cherry', 2)]",
        "print(sorted(d.items(), key=lambda x: x[1]))",
        "# → [('apple', 1), ('cherry', 2), ('banana', 3)]",
        "print(sorted(d.keys()))",
        "# → ['apple', 'banana', 'cherry']",
        "print(dict(sorted(d.items())))",
        "# → {'apple': 1, 'banana': 3, 'cherry': 2} (по ключу)",
        "print(dict(sorted(d.items(), key=lambda x: x[1], reverse=True)))",
        "# → {'banana': 3, 'cherry': 2, 'apple': 1} (по значению убыв.)",
        "print(sorted(d, key=d.get))",
        "# → ['apple', 'cherry', 'banana']"
      ],
      "related": [
        "dict.items",
        "sorted",
        "sorted-с-key",
        "operator.itemgetter"
      ],
      "related_errors": []
    },
    {
      "id": "del-для-списка",
      "title": "del для списка",
      "kind": "term",
      "summary": {
        "ru": "Оператор del удаляет элемент по индексу, срез или весь список из памяти. Изменяет объект на месте.",
        "en": "The del statement removes an item by index, a slice, or the whole list from memory. It modifies the object in place."
      },
      "body": {
        "ru": "del lst[i] и lst.pop(i) удаляют одно и то же, но del ничего не возвращает — берите pop, когда значение ещё нужно. del lst[a:b] режет список на месте, поэтому все другие имена, ссылающиеся на этот же список, увидят изменение, а lst = lst[:a] + lst[b:] лишь создаёт новый объект и перепривязывает имя. Отдельно del lst убирает только имя: сам объект живёт, пока на него есть другие ссылки.",
        "en": "del lst[i] and lst.pop(i) remove the same item, but del returns nothing — use pop when you still need the value. del lst[a:b] cuts the list in place, so every other name bound to that same list sees the change, whereas lst = lst[:a] + lst[b:] just builds a new object and rebinds the name. A bare del lst only removes the name: the object survives as long as other references point to it."
      },
      "syntax": "del lst[i]  |  del lst[a:b]  |  del lst",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/simple_stmts.html#the-del-statement",
      "version": "",
      "section": "Списки (list)",
      "subcat": "удаление",
      "color_group": "seq",
      "aliases": [
        "удалить элемент по индексу",
        "удалить срез списка",
        "удалить переменную из памяти"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "lst = [0, 1, 2, 3, 4]",
        "del lst[2]",
        "print(lst)",
        "# → [0, 1, 3, 4]",
        "del lst[1:3]",
        "print(lst)",
        "# → [0, 4]",
        "lst2 = list(range(6))",
        "del lst2[::2]",
        "print(lst2)",
        "# → [1, 3, 5]",
        "lst3 = [1, 2, 3]",
        "del lst3[:]",
        "print(lst3)",
        "# → [] (очистка через срез)",
        "lst4 = [1, 2, 3]",
        "del lst4",
        "# NameError при обращении к lst4 после del"
      ],
      "related": [
        "list.pop",
        "list.remove",
        "list.clear",
        "del-для-словаря"
      ],
      "related_errors": []
    },
    {
      "id": "enumerate-со-списком",
      "title": "enumerate() со списком",
      "kind": "term",
      "summary": {
        "ru": "enumerate() возвращает пары (индекс, элемент). start= задаёт начальный индекс.",
        "en": "enumerate() yields (index, item) pairs. start= sets the initial index."
      },
      "body": {
        "ru": "enumerate() ленив: это итератор, а не список, поэтому печать самого объекта покажет <enumerate object ...>, а второй проход по нему уже ничего не даст — оборачивайте в list(), если пары нужны целиком. Аргумент start= сдвигает только счётчик, элементы остаются на местах: после enumerate(lst, 1) число i больше не годится как индекс для lst[i].",
        "en": "enumerate() is lazy: it returns an iterator, not a list, so printing the object shows <enumerate object ...> and a second pass over it yields nothing — wrap it in list() if you need all the pairs at once. The start= argument shifts the counter only, not the items: after enumerate(lst, 1) the value of i is no longer a valid index into lst."
      },
      "syntax": "enumerate(lst[, start=0])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#enumerate",
      "version": "",
      "section": "Списки (list)",
      "subcat": "итерация",
      "color_group": "seq",
      "aliases": [
        "индекс и элемент в цикле",
        "номер элемента при переборе",
        "пронумеровать список"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "lst = ['a', 'b', 'c']",
        "for i, v in enumerate(lst):",
        "    print(i, v)",
        "# → 0 a / 1 b / 2 c",
        "for i, v in enumerate(lst, start=1):",
        "    print(i, v)",
        "    # → 1 a / 2 b / 3 c",
        "    indexed = list(enumerate(['x', 'y', 'z']))",
        "    print(indexed)",
        "    # → [(0, 'x'), (1, 'y'), (2, 'z')]",
        "    words = ['zero', 'one', 'two']",
        "    d = {i: w for i, w in enumerate(words)}",
        "    print(d)",
        "    # → {0: 'zero', 1: 'one', 2: 'two'}",
        "    lst2 = [10, 20, 30]",
        "    max_i = max(enumerate(lst2), key=lambda x: x[1])",
        "    print(max_i)",
        "    # → (2, 30)"
      ],
      "related": [
        "enumerate",
        "zip-со-списками",
        "range"
      ],
      "related_errors": []
    },
    {
      "id": "in-not-in-для-списков",
      "title": "in / not in для списков",
      "kind": "term",
      "summary": {
        "ru": "Проверяет наличие элемента в списке за O(n). Возвращает bool.",
        "en": "Checks whether an item is present in the list, in O(n). Returns a bool."
      },
      "body": {
        "ru": "Проверка линейная, так что многократный поиск внутри цикла легко даёт O(n²) — если один и тот же набор проверяется часто, переложите его в set, где проверка почти мгновенная (ценой хешируемости элементов и потери порядка). Сравнение идёт по ==, а не по типу, поэтому True и 1 считаются одним и тем же элементом. И в отличие от строк, где in ищет подстроку, у списка in ищет элемент целиком, а не подпоследовательность.",
        "en": "The scan is linear, so repeating it inside a loop quietly turns into O(n squared) — if you probe the same collection often, put it in a set, where the test is near-instant (at the price of hashable elements and lost order). Membership compares with ==, not by type, so True and 1 count as the same element. And unlike strings, where in means substring, a list checks for one whole element, never a sub-sequence."
      },
      "syntax": "item in lst  |  item not in lst",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#membership-test-operations",
      "version": "3.8",
      "section": "Списки (list)",
      "subcat": "поиск",
      "color_group": "seq",
      "aliases": [
        "проверить есть ли элемент в списке",
        "содержится ли значение в списке",
        "принадлежность элемента списку"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "lst = [1, 2, 3, 4, 5]",
        "print(3 in lst)",
        "# → True",
        "print(6 in lst)",
        "# → False",
        "print(6 not in lst)",
        "# → True",
        "print([1, 2] in [[1, 2], [3, 4]])",
        "# → True (сравнение подсписков)",
        "print(None in [1, None, 3])",
        "# → True"
      ],
      "related": [
        "in-not-in-для-множеств-o-1",
        "list.index",
        "list.count",
        "in"
      ],
      "related_errors": []
    },
    {
      "id": "list.append",
      "title": "list.append",
      "kind": "function",
      "summary": {
        "ru": "Добавляет один элемент в конец списка (на месте); возвращает None.",
        "en": "Append a single item to the end of the list in place; returns None."
      },
      "body": {
        "ru": "Метод меняет список на месте и возвращает None, поэтому lst = lst.append(x) — классический способ потерять список. Аргумент кладётся целиком одним элементом, даже если это список или строка; чтобы разложить содержимое по элементам, нужен extend. Добавление в конец стоит амортизированное O(1).",
        "en": "It mutates the list in place and returns None, so lst = lst.append(x) is the classic way to lose your list. The argument is stored as one single element even when it is itself a list or a string — use extend to spread the contents out. Appending costs amortised O(1)."
      },
      "syntax": "lst.append(item)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list.append",
      "version": "",
      "section": "Списки (list)",
      "subcat": "добавление",
      "color_group": "seq",
      "aliases": [
        "добавить элемент в список",
        "добавить в конец списка"
      ],
      "keywords": [],
      "tags": [
        "list"
      ],
      "examples": [
        "lst = [1, 2]",
        "lst.append(3)",
        "print(lst)          # → [1, 2, 3]",
        "lst.append([4, 5])",
        "print(lst)          # → [1, 2, 3, [4, 5]]"
      ],
      "related": [
        "list.extend",
        "list.insert",
        "list.pop"
      ],
      "related_errors": []
    },
    {
      "id": "list.clear",
      "title": "list.clear",
      "kind": "function",
      "summary": {
        "ru": "Удаляет все элементы, оставляя пустой список (на месте).",
        "en": "Remove all items, leaving an empty list (in place)."
      },
      "body": {
        "ru": "Разница с lst = [] принципиальна: clear() опустошает сам объект, и все другие имена, указывающие на этот список, тоже увидят пустоту, а присваивание просто перепривязывает одно имя к новому списку, оставляя старый нетронутым. Это же делает и del lst[:] — clear() лишь читаемее. Метод появился в Python 3.3.",
        "en": "The difference from lst = [] matters: clear() empties the object itself, so every other name bound to that list sees it become empty, while assignment merely rebinds one name to a fresh list and leaves the old one untouched. del lst[:] does exactly the same thing — clear() is just more readable. The method exists since Python 3.3."
      },
      "syntax": "lst.clear()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list.clear",
      "version": "",
      "section": "Списки (list)",
      "subcat": "удаление",
      "color_group": "seq",
      "aliases": [
        "очистить список",
        "удалить все элементы списка"
      ],
      "keywords": [],
      "tags": [
        "list"
      ],
      "examples": [
        "lst = [1, 2, 3]",
        "lst.clear()",
        "print(lst)        # → []",
        "print(len(lst))   # → 0"
      ],
      "related": [
        "del-для-списка",
        "dict.clear",
        "set.clear"
      ],
      "related_errors": []
    },
    {
      "id": "list.copy",
      "title": "list.copy",
      "kind": "function",
      "summary": {
        "ru": "Возвращает поверхностную (shallow) копию списка: новый список с теми же ссылками на элементы; вложенные объекты не копируются.",
        "en": "Return a shallow copy: a new list with the same element references; nested objects are not copied."
      },
      "body": {
        "ru": "Копия поверхностная: сам список новый, а элементы в нём — те же самые объекты. Если внутри лежат вложенные списки или словари, изменение через копию будет видно и в оригинале; для независимой копии нужен copy.deepcopy(). Ровно то же самое делают срез lst[:] и list(lst), а вот b = a копией не является — это второе имя одного и того же списка.",
        "en": "The copy is shallow: the outer list is new, but the elements inside are the very same objects. Mutate a nested list or dict through the copy and the original changes too — reach for copy.deepcopy() when you need real independence. lst[:] and list(lst) do exactly the same job, while b = a copies nothing at all: it is just a second name for one list."
      },
      "syntax": "lst.copy()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list.copy",
      "version": "",
      "section": "Списки (list)",
      "subcat": "копирование",
      "color_group": "seq",
      "aliases": [
        "скопировать список",
        "копия списка без ссылки на оригинал",
        "поверхностная копия"
      ],
      "keywords": [],
      "tags": [
        "list"
      ],
      "examples": [
        "a = [1, 2, 3]",
        "b = a.copy()",
        "b.append(4)",
        "print(a)   # → [1, 2, 3]",
        "print(b)   # → [1, 2, 3, 4]"
      ],
      "related": [
        "copy.deepcopy",
        "copy.copy",
        "срезы-списка",
        "ловушки-копирования"
      ],
      "related_errors": []
    },
    {
      "id": "list.count",
      "title": "list.count",
      "kind": "function",
      "summary": {
        "ru": "Возвращает число вхождений значения в список (сравнение через ==).",
        "en": "Return the number of occurrences of a value in the list (compared with ==)."
      },
      "body": {
        "ru": "Каждый вызов проходит весь список заново, поэтому подсчёт частот циклом по элементам выходит квадратичным — для этого есть collections.Counter, который считает всё за один проход. Сравнение по ==, а не по типу: True неотличим от 1, а False от 0, так что в смешанном списке результат может удивить. Позицию count() не даёт — за ней к index().",
        "en": "Every call rescans the whole list, so counting frequencies by looping over the elements and calling count() each time is quadratic — collections.Counter does the same work in a single pass. Matching uses ==, not the type, so True is indistinguishable from 1 and False from 0, which can surprise you in a mixed list. It tells you how many, never where — that is index()'s job."
      },
      "syntax": "lst.count(value)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list.count",
      "version": "",
      "section": "Списки (list)",
      "subcat": "поиск",
      "color_group": "seq",
      "aliases": [
        "сколько раз элемент встречается в списке",
        "подсчёт вхождений в списке",
        "посчитать повторы в списке"
      ],
      "keywords": [],
      "tags": [
        "list"
      ],
      "examples": [
        "nums = [1, 2, 2, 3, 2]",
        "print(nums.count(2))   # → 3",
        "print(nums.count(9))   # → 0"
      ],
      "related": [
        "list.index",
        "in-not-in-для-списков",
        "counter",
        "str.count"
      ],
      "related_errors": []
    },
    {
      "id": "list.extend",
      "title": "list.extend",
      "kind": "function",
      "summary": {
        "ru": "Добавляет все элементы итерируемого объекта в конец списка (на месте).",
        "en": "Append all items from an iterable to the end of the list (in place)."
      },
      "body": {
        "ru": "extend перебирает аргумент, поэтому строка разложится на отдельные символы — чтобы положить её целиком, нужен append. Работает на месте и возвращает None; lst += iterable делает ровно то же самое, а вот lst = lst + other требует именно список и создаёт новый объект.",
        "en": "extend iterates over its argument, so a string is split into individual characters — use append if you want the whole string as one element. It works in place and returns None; lst += iterable does exactly the same, whereas lst = lst + other demands a list and builds a new object."
      },
      "syntax": "lst.extend(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list.extend",
      "version": "",
      "section": "Списки (list)",
      "subcat": "добавление",
      "color_group": "seq",
      "aliases": [
        "добавить список к списку",
        "добавить несколько элементов в список"
      ],
      "keywords": [],
      "tags": [
        "list"
      ],
      "examples": [
        "lst = [1, 2]",
        "lst.extend([3, 4])",
        "print(lst)          # → [1, 2, 3, 4]",
        "lst.extend('ab')",
        "print(lst)          # → [1, 2, 3, 4, 'a', 'b']"
      ],
      "related": [
        "list.append",
        "объединение-повторение-списков",
        "list.insert"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "list.index",
      "title": "list.index",
      "kind": "function",
      "summary": {
        "ru": "Возвращает индекс первого вхождения значения; опциональные start/end сужают диапазон; ValueError, если не найдено.",
        "en": "Return the index of the first matching value; optional start/end narrow the search; raises ValueError if absent."
      },
      "body": {
        "ru": "В отличие от str.find, «промаха» тут нет: если значения в списке нет, метод бросает ValueError, поэтому либо оборачивайте вызов в try, либо сначала проверяйте наличие через in. Поиск линейный, слева направо, и при заданных start/end возвращается всё равно индекс в исходном списке, а не смещение от start.",
        "en": "Unlike str.find there is no \"not found\" return value: a missing item raises ValueError, so either wrap the call in try or check with in first. The scan is linear and left to right, and with start/end the number you get back is still an index into the whole list, not an offset from start."
      },
      "syntax": "lst.index(value, start=0, stop=len)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list.index",
      "version": "",
      "section": "Списки (list)",
      "subcat": "поиск",
      "color_group": "seq",
      "aliases": [
        "найти позицию элемента в списке",
        "узнать индекс элемента",
        "номер элемента в списке"
      ],
      "keywords": [],
      "tags": [
        "list"
      ],
      "examples": [
        "letters = ['a', 'b', 'c', 'b']",
        "print(letters.index('b'))      # → 1",
        "print(letters.index('b', 2))   # → 3"
      ],
      "related": [
        "list.count",
        "valueerror",
        "in-not-in-для-списков",
        "str.index"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "list.insert",
      "title": "list.insert",
      "kind": "function",
      "summary": {
        "ru": "Вставляет элемент перед указанным индексом; индекс ≥ len — в конец, отрицательный — от конца.",
        "en": "Insert an item before the given index; index ≥ len appends, negative counts from the end."
      },
      "body": {
        "ru": "Вставка сдвигает все элементы правее, то есть стоит O(n): сборка списка через insert(0, x) в цикле вырождается в O(n²) — для дешёвого добавления с обоих концов есть collections.deque. Индекс за границами не вызывает IndexError: слишком большой просто добавит в конец, слишком отрицательный — в начало.",
        "en": "Inserting shifts every element to the right, so it costs O(n): building a list with insert(0, x) in a loop degrades to O(n²) — use collections.deque when you need cheap inserts at the front. Out-of-range indices never raise IndexError: too large appends at the end, too negative inserts at position 0."
      },
      "syntax": "lst.insert(index, item)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list.insert",
      "version": "",
      "section": "Списки (list)",
      "subcat": "добавление",
      "color_group": "seq",
      "aliases": [
        "вставить элемент в список по индексу",
        "вставить элемент в начало списка"
      ],
      "keywords": [],
      "tags": [
        "list"
      ],
      "examples": [
        "lst = [1, 2, 3]",
        "lst.insert(1, 99)",
        "print(lst)   # → [1, 99, 2, 3]",
        "lst.insert(0, 'start')",
        "print(lst)   # → ['start', 1, 99, 2, 3]"
      ],
      "related": [
        "list.append",
        "list.pop",
        "list.extend"
      ],
      "related_errors": []
    },
    {
      "id": "list.pop",
      "title": "list.pop",
      "kind": "function",
      "summary": {
        "ru": "Удаляет и возвращает элемент по индексу (по умолчанию последний); IndexError на пустом/неверном индексе.",
        "en": "Remove and return the item at the index (last by default); raises IndexError if empty or out of range."
      },
      "body": {
        "ru": "pop() с конца работает за O(1), а pop(0) и любой другой ранний индекс — за O(n): все хвостовые элементы сдвигаются влево. Если нужна очередь FIFO, берите collections.deque с popleft() вместо списка, иначе на больших данных получите квадратичное время. Тем pop и отличается от del и remove, что возвращает удалённое значение — на этом удобно строить обработку «взял и обработал».",
        "en": "Popping from the end is O(1), but pop(0) — or any early index — is O(n) because every later element shifts left. For a FIFO queue use collections.deque with popleft() instead of a list, otherwise large inputs turn quadratic. Unlike del and remove, pop hands the removed value back, which is what makes the take-and-process pattern convenient."
      },
      "syntax": "lst.pop(index=-1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list.pop",
      "version": "",
      "section": "Списки (list)",
      "subcat": "удаление",
      "color_group": "seq",
      "aliases": [
        "извлечь последний элемент списка",
        "удалить и вернуть элемент",
        "достать элемент по индексу"
      ],
      "keywords": [],
      "tags": [
        "list"
      ],
      "examples": [
        "lst = [1, 2, 3]",
        "print(lst.pop())    # → 3",
        "print(lst.pop(0))   # → 1",
        "print(lst)          # → [2]"
      ],
      "related": [
        "list.remove",
        "del-для-списка",
        "indexerror",
        "list.append"
      ],
      "related_errors": [
        "IndexError"
      ]
    },
    {
      "id": "list.remove",
      "title": "list.remove",
      "kind": "function",
      "summary": {
        "ru": "Удаляет первое вхождение значения; ValueError, если значение не найдено.",
        "en": "Remove the first matching value; raises ValueError if not found."
      },
      "body": {
        "ru": "Совпадение ищется через ==, а не по тождеству, поэтому lst.remove(1) спокойно выбросит True, а lst.remove(1.0) — целую единицу. Удаляется только первое вхождение и поиск линейный, так что вычищать все копии циклом while в remove — и медленно, и легко ошибиться; надёжнее собрать новый список включением с условием. Удалять элементы из списка прямо во время for-цикла по нему не стоит: индексы сдвигаются и часть элементов проскакивает непроверенной.",
        "en": "Matching uses ==, not identity, so lst.remove(1) will happily drop a True, and lst.remove(1.0) will drop an integer 1. Only the first match goes, and the scan is linear, so stripping every copy with a while loop around remove is both slow and error-prone — build a filtered list with a comprehension instead. Never remove items while iterating the same list with for: indices shift and some elements get skipped entirely."
      },
      "syntax": "lst.remove(value)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list.remove",
      "version": "",
      "section": "Списки (list)",
      "subcat": "удаление",
      "color_group": "seq",
      "aliases": [
        "удалить элемент по значению",
        "убрать значение из списка",
        "удалить первое вхождение"
      ],
      "keywords": [],
      "tags": [
        "list"
      ],
      "examples": [
        "lst = [1, 2, 3, 2]",
        "lst.remove(2)",
        "print(lst)   # → [1, 3, 2]",
        "lst.remove(2)",
        "print(lst)   # → [1, 3]"
      ],
      "related": [
        "list.pop",
        "del-для-списка",
        "list.index",
        "valueerror"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "list.reverse",
      "title": "list.reverse",
      "kind": "function",
      "summary": {
        "ru": "Переворачивает список на месте. Для нового обратного итератора без изменения списка — встроенная reversed().",
        "en": "Reverse the list in place. For a new reversed iterator without mutating, use the built-in reversed()."
      },
      "body": {
        "ru": "Как и все методы, меняющие список на месте, reverse() возвращает None — присваивание вида lst = lst.reverse() затрёт список этим None. reversed() ничего не копирует: это ленивый проход с конца, который видит изменения, внесённые в список во время обхода, зато применим к любой последовательности, а reverse() есть только у списка.",
        "en": "Like every method that mutates a list in place, reverse() returns None, so lst = lst.reverse() throws the list away and leaves you with None. reversed() copies nothing: it walks the list lazily from the end and therefore reflects changes made while you iterate, but it works on any sequence, whereas reverse() exists only on lists."
      },
      "syntax": "lst.reverse()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list.reverse",
      "version": "",
      "section": "Списки (list)",
      "subcat": "сортировка",
      "color_group": "seq",
      "aliases": [
        "перевернуть список",
        "развернуть список",
        "обратный порядок элементов"
      ],
      "keywords": [],
      "tags": [
        "list"
      ],
      "examples": [
        "lst = [1, 2, 3]",
        "lst.reverse()",
        "print(lst)     # → [3, 2, 1]",
        "chars = ['a', 'b', 'c']",
        "chars.reverse()",
        "print(chars)   # → ['c', 'b', 'a']"
      ],
      "related": [
        "reversed",
        "срезы-с-шагом-2-1",
        "list.sort",
        "for-...-in-reversed"
      ],
      "related_errors": []
    },
    {
      "id": "list.sort",
      "title": "list.sort",
      "kind": "function",
      "summary": {
        "ru": "Сортирует список на месте; key= задаёт функцию ключа, reverse=True — обратный порядок. Для нового списка без изменения исходного — встроенная sorted().",
        "en": "Sort the list in place; key= sets a key function, reverse=True descending. For a new sorted list without mutating, use the built-in sorted()."
      },
      "body": {
        "ru": "Метод меняет список на месте и возвращает None, поэтому lst = lst.sort() — типичная ошибка, дающая None вместо списка. Сортировка стабильна: элементы с равными ключами сохраняют исходный взаимный порядок, а функция key вызывается ровно один раз на элемент, и сравниваются уже её результаты — смесь несравнимых типов (числа и строки) даст TypeError, приводите их к общему виду прямо в key.",
        "en": "The method sorts in place and returns None, so lst = lst.sort() is the classic mistake that leaves you holding None instead of a list. The sort is stable — items with equal keys keep their original relative order — and the key function is called exactly once per item, with only its results compared, so mixing incomparable types such as numbers and strings raises TypeError; normalise them inside key if you need that."
      },
      "syntax": "lst.sort(*, key=None, reverse=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list.sort",
      "version": "",
      "section": "Списки (list)",
      "subcat": "сортировка",
      "color_group": "seq",
      "aliases": [
        "отсортировать список",
        "сортировка списка по возрастанию",
        "упорядочить список"
      ],
      "keywords": [],
      "tags": [
        "list"
      ],
      "examples": [
        "lst = [3, 1, 2]",
        "lst.sort()",
        "print(lst)              # → [1, 2, 3]",
        "lst.sort(reverse=True)",
        "print(lst)              # → [3, 2, 1]",
        "words = ['bb', 'a', 'ccc']",
        "words.sort(key=len)",
        "print(words)            # → ['a', 'bb', 'ccc']"
      ],
      "related": [
        "sorted",
        "сортировка-ключом-key-lambda",
        "list.reverse"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "zip-со-списками",
      "title": "zip() со списками",
      "kind": "term",
      "summary": {
        "ru": "zip() объединяет элементы нескольких итерируемых объектов в кортежи. Останавливается на самом коротком.",
        "en": "zip() joins the items of several iterables into tuples. It stops at the shortest one."
      },
      "body": {
        "ru": "Обрезка по самой короткой последовательности происходит молча — если длины обязаны совпадать, с Python 3.10 передавайте strict=True, тогда расхождение даст ValueError вместо тихо потерянных элементов. Результат одноразовый и ленивый: после одного list(zip(...)) или одного цикла итератор пуст, повторно пройти по нему не выйдет. Если нужно дополнить до самой длинной, а не обрезать — itertools.zip_longest().",
        "en": "Truncation to the shortest input happens silently — when the lengths must match, pass strict=True (Python 3.10+) so a mismatch raises ValueError instead of quietly dropping items. The result is a lazy, single-use iterator: after one list(zip(...)) or one loop it is exhausted and cannot be traversed again. If you need padding to the longest input rather than truncation, reach for itertools.zip_longest()."
      },
      "syntax": "zip(lst1, lst2, ...)  |  zip(*matrix)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#zip",
      "version": "",
      "section": "Списки (list)",
      "subcat": "итерация",
      "color_group": "seq",
      "aliases": [
        "пройти по двум спискам одновременно",
        "объединить списки попарно",
        "транспонировать матрицу"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "a = [1, 2, 3]",
        "b = ['a', 'b', 'c']",
        "print(list(zip(a, b)))",
        "# → [(1, 'a'), (2, 'b'), (3, 'c')]",
        "for x, y in zip(a, b):",
        "    print(x, y)",
        "    # → 1 a / 2 b / 3 c",
        "    print(list(zip([1, 2, 3], [4, 5])))",
        "    # → [(1, 4), (2, 5)] (по короткому)",
        "    keys = ['a', 'b', 'c']",
        "    vals = [1, 2, 3]",
        "    d = dict(zip(keys, vals))",
        "    print(d)",
        "    # → {'a': 1, 'b': 2, 'c': 3}",
        "    matrix = [[1, 2, 3], [4, 5, 6]]",
        "    transposed = list(zip(*matrix))",
        "    print(transposed)",
        "    # → [(1, 4), (2, 5), (3, 6)]"
      ],
      "related": [
        "zip",
        "enumerate-со-списком",
        "zip-strict-true",
        "itertools.zip_longest"
      ],
      "related_errors": []
    },
    {
      "id": "вложенные-списки-матрицы",
      "title": "Вложенные списки / матрицы",
      "kind": "term",
      "summary": {
        "ru": "Список списков используется как двумерная матрица. Доступ через двойной индекс matrix[row][col].",
        "en": "A list of lists is used as a two-dimensional matrix. Items are reached through a double index, matrix[row][col]."
      },
      "body": {
        "ru": "Главная ловушка — [[0] * cols] * rows: внешнее умножение копирует ссылку, все строки оказываются одним и тем же списком, и запись в одну ячейку меняет весь столбец. Надёжный способ — генератор списков, где новая строка создаётся на каждой итерации. Индексы идут в порядке matrix[строка][столбец], а прямоугольность никто не проверяет: строки могут быть разной длины, и заметите вы это только по IndexError.",
        "en": "The classic trap is [[0] * cols] * rows: the outer multiplication copies a reference, so every row is the same list and writing one cell changes a whole column. Build rows with a list comprehension instead, so each iteration creates a fresh row. Indexing goes matrix[row][col], and nothing enforces a rectangle — rows may differ in length, and you usually find out via IndexError."
      },
      "syntax": "matrix[i][j]  |  [[val]*cols for _ in range(rows)]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/faq/programming.html#how-do-i-create-a-multidimensional-list",
      "version": "",
      "section": "Списки (list)",
      "subcat": "матрицы",
      "color_group": "seq",
      "aliases": [
        "двумерный массив",
        "список списков",
        "обход двумерного списка"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]",
        "print(matrix[1][2])",
        "# → 6",
        "for row in matrix:",
        "    print(row)",
        "    # → [1, 2, 3] / [4, 5, 6] / [7, 8, 9]",
        "    zeros = [[0]*3 for _ in range(3)]",
        "    zeros[0][0] = 1",
        "    print(zeros)",
        "    # → [[1, 0, 0], [0, 0, 0], [0, 0, 0]]",
        "    transposed = [[row[i] for row in matrix] for i in range(3)]",
        "    print(transposed)",
        "    # → [[1, 4, 7], [2, 5, 8], [3, 6, 9]]",
        "    m = [[i+j for j in range(3)] for i in range(3)]",
        "    print(m)",
        "    # → [[0, 1, 2], [1, 2, 3], [2, 3, 4]]",
        "    flat = [val for row in matrix for val in row]",
        "    print(flat)",
        "    # → [1, 2, 3, 4, 5, 6, 7, 8, 9]",
        "    diag = [matrix[i][i] for i in range(3)]",
        "    print(diag)",
        "    # → [1, 5, 9]"
      ],
      "related": [
        "вложенный-list-comprehension",
        "объединение-повторение-списков",
        "zip-со-списками",
        "вложенные-циклы"
      ],
      "related_errors": []
    },
    {
      "id": "вложенный-list-comprehension",
      "title": "Вложенный list comprehension",
      "kind": "term",
      "summary": {
        "ru": "List comprehension может содержать несколько вложенных for, что позволяет строить вложенные списки или «разворачивать» матрицы в плоский список.",
        "en": "A list comprehension may contain several nested for clauses, which lets you build nested lists or flatten a matrix into a single list."
      },
      "body": {
        "ru": "Порядок for-ов читается слева направо ровно как вложенные циклы сверху вниз: сначала внешний for row in matrix, потом внутренний for x in row. Написать [x for x in row for row in matrix] — самая частая ошибка: имя row там ещё не определено, и вы получите NameError. Для транспонирования проще zip(*matrix), а глубже двух уровней вложенности обычный цикл читается лучше выражения.",
        "en": "Read the for clauses left to right exactly as nested loops top to bottom: the outer for row in matrix comes first, the inner for x in row second. Writing [x for x in row for row in matrix] is the classic mistake — row is not defined at that point, so you get a NameError. For transposing, zip(*matrix) is simpler, and past two levels of nesting a plain loop reads better than the comprehension."
      },
      "syntax": "[[выражение for j in inner] for i in outer]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/datastructures.html#nested-list-comprehensions",
      "version": "",
      "section": "Списки (list)",
      "subcat": "comprehension",
      "color_group": "seq",
      "aliases": [
        "развернуть вложенный список в плоский",
        "двойной цикл в списочном выражении",
        "матрица одной строкой"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "matrix = [[1,2,3],[4,5,6],[7,8,9]]",
        "flat = [x for row in matrix for x in row]",
        "print(flat)   # → [1, 2, 3, 4, 5, 6, 7, 8, 9]",
        "trans = [[row[i] for row in matrix] for i in range(3)]",
        "print(trans)  # → [[1, 4, 7], [2, 5, 8], [3, 6, 9]]",
        "pairs = [(i,j) for i in range(3) for j in range(3) if i != j]",
        "print(len(pairs))  # → 6",
        "sq = [[i*j for j in range(1,4)] for i in range(1,4)]",
        "print(sq[0])  # → [1, 2, 3]"
      ],
      "related": [
        "списочные-выражения-list-comprehension",
        "вложенные-списки-матрицы",
        "вложенные-циклы",
        "условный-list-comprehension"
      ],
      "related_errors": []
    },
    {
      "id": "индексирование-списка",
      "title": "Индексирование списка",
      "kind": "term",
      "summary": {
        "ru": "Доступ к элементу по индексу. Отрицательные индексы — от конца. Поддерживает присваивание.",
        "en": "Access to an item by index. Negative indices count from the end. Assignment is supported."
      },
      "body": {
        "ru": "Выход за границы — сразу IndexError, аналога dict.get() с запасным значением у списка нет: либо проверяйте len() заранее, либо ловите исключение. Присваивание lst[i] = val только заменяет уже существующий элемент, расширить список так нельзя — для этого append() или insert(). Само обращение по индексу — O(1), не важно, первый элемент или миллионный.",
        "en": "An out-of-range index raises IndexError right away — a list has no dict.get()-style fallback, so check len() first or catch the exception. lst[i] = val replaces an existing item only; it cannot grow the list, that is what append() and insert() are for. Indexing itself is O(1), whether you ask for the first item or the millionth."
      },
      "syntax": "lst[i]  |  lst[i] = val",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types",
      "version": "",
      "section": "Списки (list)",
      "subcat": "индексы/срезы",
      "color_group": "seq",
      "aliases": [
        "получить элемент списка по номеру",
        "последний элемент списка",
        "отрицательные индексы"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "lst = [10, 20, 30, 40, 50]",
        "print(lst[0])",
        "# → 10",
        "print(lst[-1])",
        "# → 50",
        "print(lst[2])",
        "# → 30",
        "print(lst[-2])",
        "# → 40",
        "lst[1] = 99",
        "print(lst)",
        "# → [10, 99, 30, 40, 50]",
        "lst[-1] = 0",
        "print(lst)",
        "# → [10, 99, 30, 40, 0]"
      ],
      "related": [
        "срезы-списка",
        "indexerror",
        "list.index",
        "индексирование-строк"
      ],
      "related_errors": []
    },
    {
      "id": "объединение-повторение-списков",
      "title": "+ объединение / * повторение списков",
      "kind": "term",
      "summary": {
        "ru": "Оператор + возвращает новый список-объединение. * повторяет список n раз. Для mutable объектов * создаёт shallow копии.",
        "en": "The + operator returns a new list — the concatenation of the two. * repeats a list n times. For mutable objects * produces shallow copies."
      },
      "body": {
        "ru": "Оба оператора строят новый список целиком, поэтому накопление в цикле вида res = res + [x] квадратично по времени — используйте append() или extend(). Различайте lst += other и lst = lst + other: += меняет список на месте, как extend, и это видят все, кто держит ссылку на тот же объект, к тому же справа допустим любой итерируемый объект; обычный + требует, чтобы справа тоже был список, иначе TypeError.",
        "en": "Both operators build a whole new list, so accumulating with res = res + [x] inside a loop is quadratic — use append() or extend(). Also keep lst += other apart from lst = lst + other: += mutates in place like extend, so everyone holding a reference to that list sees the change, and it accepts any iterable on the right; plain + insists on another list and raises TypeError otherwise."
      },
      "syntax": "lst1 + lst2  |  lst * n",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
      "version": "",
      "section": "Списки (list)",
      "subcat": "операторы",
      "color_group": "seq",
      "aliases": [
        "склеить два списка",
        "объединить два списка в один",
        "список из n нулей"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "print([1, 2] + [3, 4])",
        "# → [1, 2, 3, 4]",
        "print([0] * 5)",
        "# → [0, 0, 0, 0, 0]",
        "print(['a', 'b'] * 3)",
        "# → ['a', 'b', 'a', 'b', 'a', 'b']",
        "print([1, 2] + [])",
        "# → [1, 2]",
        "trap = [[]] * 3",
        "trap[0].append(1)",
        "print(trap)",
        "# → [[1], [1], [1]] (одна ссылка!)"
      ],
      "related": [
        "list.extend",
        "вложенные-списки-матрицы",
        "конкатенация-строк"
      ],
      "related_errors": []
    },
    {
      "id": "распаковка-списка",
      "title": "Распаковка списка — *",
      "kind": "term",
      "summary": {
        "ru": "Распаковка позволяет присвоить элементы переменным. * захватывает остаток как список.",
        "en": "Unpacking assigns the items to variables. * captures the rest as a list."
      },
      "body": {
        "ru": "Без звёздочки число переменных слева должно совпадать с числом элементов точь-в-точь, иначе ValueError с текстом вроде not enough values to unpack; звёздочка снимает это ограничение, но в одной цели она может быть только одна. Переменная со звёздочкой всегда получает список — даже если справа кортеж или строка и даже если захватывать нечего (тогда пустой список).",
        "en": "Without a star the number of names on the left must match the number of items exactly, otherwise you get a ValueError such as \"not enough values to unpack\"; the star lifts that restriction, but only one star is allowed per target. The starred name always ends up holding a list, even when the right side is a tuple or a string, and even when there is nothing left to capture (then it is empty)."
      },
      "syntax": "a, b, *rest = lst  |  *head, last = lst  |  a, *_, b = lst",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/simple_stmts.html#assignment-statements",
      "version": "",
      "section": "Списки (list)",
      "subcat": "распаковка",
      "color_group": "seq",
      "aliases": [
        "присвоить несколько переменных сразу",
        "звёздочка перед переменной",
        "остаток списка в переменную"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "a, b, c = [1, 2, 3]",
        "print(a, b, c)",
        "# → 1 2 3",
        "first, *rest = [1, 2, 3, 4, 5]",
        "print(first, rest)",
        "# → 1 [2, 3, 4, 5]",
        "*head, last = [1, 2, 3, 4, 5]",
        "print(head, last)",
        "# → [1, 2, 3, 4] 5",
        "a, *_, b = [1, 2, 3, 4, 5]",
        "print(a, b)",
        "# → 1 5",
        "lst1, lst2 = [1, 2, 3], [4, 5]",
        "combined = [*lst1, *lst2]",
        "print(combined)",
        "# → [1, 2, 3, 4, 5]",
        "def func(*args): return sum(args)",
        "nums = [1, 2, 3]",
        "print(func(*nums))",
        "# → 6"
      ],
      "related": [
        "распаковка-кортежа",
        "распаковка-в-for",
        "args"
      ],
      "related_errors": []
    },
    {
      "id": "создание-списка",
      "title": "Создание списка",
      "kind": "term",
      "summary": {
        "ru": "Список создаётся литералом [], конструктором list(), или списочным выражением. Может содержать элементы любых типов.",
        "en": "A list is written as the [] literal, with the list() constructor, or as a list comprehension. It may hold items of any type."
      },
      "body": {
        "ru": "list() от строки разбирает её посимвольно: list('abc') даёт ['a', 'b', 'c'], а не список из одной строки — для этого нужен литерал. И помните, что умножение копирует ссылки, а не объекты: в [[]] * 3 лежит один и тот же вложенный список, добавление в «первую» строку будет видно во всех трёх; для матриц используйте списочное выражение.",
        "en": "list() applied to a string splits it into characters: list('abc') gives ['a', 'b', 'c'], not a one-item list — use the literal for that. Also note that multiplication copies references, not objects: [[]] * 3 holds the same inner list three times, so appending to the \"first\" row shows up in all of them; build matrices with a comprehension instead."
      },
      "syntax": "[]  |  list()  |  list(iterable)  |  [expr for x in iter]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#list",
      "version": "",
      "section": "Списки (list)",
      "subcat": "создание",
      "color_group": "seq",
      "aliases": [
        "пустой список",
        "объявить массив",
        "как задать список элементов"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "a = []",
        "# → []",
        "b = [1, 2, 3]",
        "# → [1, 2, 3]",
        "c = list('abc')",
        "# → ['a', 'b', 'c']",
        "d = list(range(5))",
        "# → [0, 1, 2, 3, 4]",
        "e = [x**2 for x in range(4)]",
        "# → [0, 1, 4, 9]",
        "f = [1, 'two', 3.0, None]",
        "# → [1, 'two', 3.0, None]",
        "g = [0] * 5",
        "# → [0, 0, 0, 0, 0]"
      ],
      "related": [
        "list",
        "списочные-выражения-list-comprehension",
        "создание-кортежа",
        "создание-множества"
      ],
      "related_errors": []
    },
    {
      "id": "сортировка-ключом-key-lambda",
      "title": "Сортировка ключом key= / lambda",
      "kind": "term",
      "summary": {
        "ru": "key= принимает функцию, возвращающую значение для сравнения. lambda удобна для однострочных ключей.",
        "en": "key= takes a function that returns the value to compare by. A lambda is handy for one-line keys."
      },
      "body": {
        "ru": "Функция из key= вызывается ровно один раз для каждого элемента, а не на каждое сравнение, поэтому даже не самый дешёвый ключ обходится терпимо. Сортировка стабильна: элементы с одинаковым ключом сохраняют исходный порядок — на этом строится сортировка по нескольким полям несколькими проходами, от менее значимого поля к более значимому (reverse=True стабильность не ломает). Классическая ошибка — написать key=len() вместо key=len: передавать надо саму функцию, а не результат её вызова.",
        "en": "The key function is called exactly once per element, not on every comparison, so a moderately expensive key is cheaper than it looks. Sorting is stable: items with equal keys keep their original order, which is what makes multi-field sorting work as several passes from the least significant field to the most significant one (reverse=True preserves stability too). The classic slip is writing key=len() instead of key=len — you pass the function itself, not the result of calling it."
      },
      "syntax": "sorted(lst, key=func)  |  lst.sort(key=lambda x: ...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/howto/sorting.html#key-functions",
      "version": "",
      "section": "Списки (list)",
      "subcat": "сортировка",
      "color_group": "seq",
      "aliases": [
        "сортировка по полю объекта",
        "сортировка по второму элементу",
        "сортировка по длине строки"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "words = ['banana', 'apple', 'fig', 'cherry']",
        "print(sorted(words, key=len))",
        "# → ['fig', 'apple', 'banana', 'cherry']",
        "pairs = [(2, 'b'), (1, 'c'), (3, 'a')]",
        "print(sorted(pairs, key=lambda x: x[1]))",
        "# → [(3, 'a'), (2, 'b'), (1, 'c')]",
        "print(sorted(pairs, key=lambda x: x[0], reverse=True))",
        "# → [(3, 'a'), (2, 'b'), (1, 'c')]",
        "people = [('Alice', 30), ('Bob', 25), ('Carol', 30)]",
        "print(sorted(people, key=lambda p: (p[1], p[0])))",
        "# → [('Bob', 25), ('Alice', 30), ('Carol', 30)]",
        "nums = ['-3', '1', '-10', '2']",
        "print(sorted(nums, key=lambda x: abs(int(x))))",
        "# → ['1', '2', '-3', '-10']",
        "strs = ['Banana', 'apple', 'Cherry']",
        "print(sorted(strs, key=str.lower))",
        "# → ['apple', 'Banana', 'Cherry']",
        "from operator import itemgetter",
        "data = [{'name': 'b', 'val': 2}, {'name': 'a', 'val': 3}]",
        "print(sorted(data, key=itemgetter('name')))",
        "# → [{'name': 'a', 'val': 3}, {'name': 'b', 'val': 2}]"
      ],
      "related": [
        "sorted-с-key",
        "list.sort",
        "lambda",
        "operator.itemgetter"
      ],
      "related_errors": []
    },
    {
      "id": "списочные-выражения-list-comprehension",
      "title": "Списочные выражения (list comprehension)",
      "kind": "function",
      "summary": {
        "ru": "Компактный синтаксис создания списка с необязательной фильтрацией. Быстрее эквивалентного цикла for+append.",
        "en": "Compact syntax for building a list, with optional filtering. Faster than the equivalent for+append loop."
      },
      "body": {
        "ru": "Переменная цикла живёт только внутри выражения и наружу не утекает — одноимённая переменная снаружи останется нетронутой. При вложенности for-части читаются слева направо, как во вложенном цикле, а список строится в памяти целиком: для больших или бесконечных потоков берите генераторное выражение в круглых скобках.",
        "en": "The loop variable is local to the comprehension and never leaks out, so a same-named name outside keeps its value. Nested for clauses read left to right exactly like nested loops, and the whole list is materialised at once — for large or endless streams use a generator expression in parentheses instead."
      },
      "syntax": "[expr for var in iterable [if cond]]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions",
      "version": "",
      "section": "Списки (list)",
      "subcat": "comprehension",
      "color_group": "seq",
      "aliases": [
        "генератор списка",
        "создать список одной строкой",
        "цикл в квадратных скобках"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "squares = [x**2 for x in range(6)]",
        "print(squares)",
        "# → [0, 1, 4, 9, 16, 25]",
        "evens = [x for x in range(10) if x % 2 == 0]",
        "print(evens)",
        "# → [0, 2, 4, 6, 8]",
        "pairs = [(x, y) for x in [1, 2] for y in [3, 4]]",
        "print(pairs)",
        "# → [(1, 3), (1, 4), (2, 3), (2, 4)]",
        "words = ['hello', 'world', 'python']",
        "upper = [w.upper() for w in words]",
        "print(upper)",
        "# → ['HELLO', 'WORLD', 'PYTHON']",
        "flat = [x for row in [[1,2],[3,4],[5,6]] for x in row]",
        "print(flat)",
        "# → [1, 2, 3, 4, 5, 6]",
        "mapped = [x if x > 0 else 0 for x in [-1, 2, -3, 4]]",
        "print(mapped)",
        "# → [0, 2, 0, 4]",
        "digits = [int(c) for c in '12345']",
        "print(digits)",
        "# → [1, 2, 3, 4, 5]",
        "nested = [[i*j for j in range(1, 4)] for i in range(1, 4)]",
        "print(nested)",
        "# → [[1, 2, 3], [2, 4, 6], [3, 6, 9]]"
      ],
      "related": [
        "условный-list-comprehension",
        "вложенный-list-comprehension",
        "генераторное-выражение",
        "словарные-выражения-dict-comprehension"
      ],
      "related_errors": []
    },
    {
      "id": "срезы-с-шагом-2-1",
      "title": "Срезы с шагом [::2] / [::-1]",
      "kind": "term",
      "summary": {
        "ru": "Третий параметр среза — шаг: [start:stop:step]. Шаг 2 — каждый второй элемент. Шаг -1 — обратный порядок. Можно комбинировать start/stop со шагом.",
        "en": "The third slice parameter is the step: [start:stop:step]. Step 2 takes every second item. Step -1 gives the reverse order. start/stop can be combined with a step."
      },
      "body": {
        "ru": "При отрицательном шаге умолчания переворачиваются — срез идёт от конца к началу, но stop по-прежнему не включается, поэтому lst[3:0:-1] вернёт элементы с индексами 3, 2, 1 и потеряет нулевой. Нулевой шаг запрещён: lst[::0] даёт ValueError. И не путайте lst[::-1] с list.reverse(): первый строит новую копию, второй переворачивает список на месте и возвращает None.",
        "en": "With a negative step the defaults flip — the slice runs from the end backwards, yet stop is still excluded, so lst[3:0:-1] gives items 3, 2, 1 and drops index 0. A zero step is forbidden: lst[::0] raises ValueError. Do not confuse lst[::-1] with list.reverse(): the slice builds a new copy, while reverse() flips the list in place and returns None."
      },
      "syntax": "lst[start:stop:step]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
      "version": "",
      "section": "Списки (list)",
      "subcat": "индексы/срезы",
      "color_group": "seq",
      "aliases": [
        "каждый второй элемент",
        "развернуть список в обратном порядке",
        "шаг в срезе"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "lst = [0, 1, 2, 3, 4, 5, 6]",
        "print(lst[::2])     # → [0, 2, 4, 6]",
        "print(lst[::-1])    # → [6, 5, 4, 3, 2, 1, 0]",
        "print(lst[1::2])    # → [1, 3, 5]",
        "print(lst[6:0:-2])  # → [6, 4, 2]",
        "print(lst[:4:1])    # → [0, 1, 2, 3]"
      ],
      "related": [
        "срезы-списка",
        "reversed",
        "list.reverse",
        "срезы-строк"
      ],
      "related_errors": []
    },
    {
      "id": "срезы-списка",
      "title": "Срезы списка",
      "kind": "term",
      "summary": {
        "ru": "Срезы возвращают новый список. s[a:b:c] — с шагом c. [::-1] — реверс. Срез без аргументов — поверхностная копия.",
        "en": "Slices return a new list. s[a:b:c] takes step c. [::-1] reverses it. A slice with no arguments is a shallow copy."
      },
      "body": {
        "ru": "Срез никогда не падает по границам: у короткого списка lst[5:100] просто вернёт пустой список — этим он и отличается от lst[5], который бросит IndexError. Копия поверхностная: вложенные списки в копии — те же самые объекты, и правка через один список видна во втором; для независимой копии нужен copy.deepcopy(). Присваивание в срез (lst[1:3] = [...]) меняет исходный список и может изменить его длину.",
        "en": "Slicing never fails on bounds: for a short list lst[5:100] simply returns an empty list — unlike lst[5], which raises IndexError. The copy is shallow: nested lists are shared objects, so mutating one is visible through the other; use copy.deepcopy() for an independent copy. Assigning to a slice (lst[1:3] = [...]) mutates the original list and can change its length."
      },
      "syntax": "lst[a:b]  |  lst[a:b:c]  |  lst[::-1]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
      "version": "",
      "section": "Списки (list)",
      "subcat": "индексы/срезы",
      "color_group": "seq",
      "aliases": [
        "взять часть списка",
        "вырезать подсписок",
        "копия списка срезом"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "lst = [0, 1, 2, 3, 4, 5]",
        "print(lst[1:4])",
        "# → [1, 2, 3]",
        "print(lst[:3])",
        "# → [0, 1, 2]",
        "print(lst[3:])",
        "# → [3, 4, 5]",
        "print(lst[::-1])",
        "# → [5, 4, 3, 2, 1, 0]",
        "print(lst[::2])",
        "# → [0, 2, 4]",
        "copy = lst[:]",
        "print(copy is lst)",
        "# → False (новый объект)",
        "lst[1:4] = [10, 20, 30]",
        "print(lst)",
        "# → [0, 10, 20, 30, 4, 5]"
      ],
      "related": [
        "срезы-с-шагом-2-1",
        "индексирование-списка",
        "list.copy",
        "срезы-строк"
      ],
      "related_errors": []
    },
    {
      "id": "условный-list-comprehension",
      "title": "Условный list comprehension",
      "kind": "term",
      "summary": {
        "ru": "Фильтрующий if в конце list comprehension отбирает элементы по условию. Не путать с тернарным оператором (if ... else ...) в начале выражения — тот преобразует, а не фильтрует.",
        "en": "A filtering if at the end of a list comprehension selects items by a condition. Not to be confused with the ternary operator (if ... else ...) at the start of the expression — that one transforms rather than filters."
      },
      "body": {
        "ru": "Фильтрующий if ставится после for и идёт без else: [x if x > 0 for x in lst] — SyntaxError, а [x if x > 0 else 0 for x in lst] уже не отбор, а замена значений. Обе конструкции можно совместить в одном выражении — тернарник впереди преобразует, if в конце отбирает.",
        "en": "The filtering if goes after the for and takes no else: [x if x > 0 for x in lst] is a SyntaxError, while [x if x > 0 else 0 for x in lst] replaces values instead of dropping them. Both forms can coexist in one comprehension — the leading ternary transforms, the trailing if selects."
      },
      "syntax": "[выражение for x in iterable if условие]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#comprehensions",
      "version": "3.6",
      "section": "Списки (list)",
      "subcat": "comprehension",
      "color_group": "seq",
      "aliases": [
        "фильтрация списка условием",
        "отобрать элементы списка по условию"
      ],
      "keywords": [],
      "tags": [
        "seq"
      ],
      "examples": [
        "lst = [1, -2, 3, -4, 5]",
        "print([x for x in lst if x > 0])           # → [1, 3, 5]",
        "print([x**2 for x in lst if x % 2 != 0])   # → [1, 9, 25]",
        "words = [\"apple\", \"hi\", \"banana\", \"ok\"]",
        "print([w for w in words if len(w) > 3])     # → ['apple', 'banana']",
        "print([x if x > 0 else 0 for x in lst])    # → [1, 0, 3, 0, 5]  (тернарный)"
      ],
      "related": [
        "списочные-выражения-list-comprehension",
        "тернарный-оператор",
        "filter"
      ],
      "related_errors": []
    },
    {
      "id": "b-...-байт-строки",
      "title": "b\"...\" байт-строки",
      "kind": "term",
      "summary": {
        "ru": "Байтовые строки (b\"...\") хранят последовательность байт, а не Unicode-символов. Каждый элемент — целое число 0–255. Используются при работе с бинарными данными.",
        "en": "Byte strings (b\"...\") hold a sequence of bytes rather than Unicode characters. Each item is an integer 0–255. Used when working with binary data."
      },
      "body": {
        "ru": "Индексация и срез ведут себя по-разному: b[0] даёт целое число 104, а b[0:1] — снова объект bytes b'h'. Смешивать с обычными строками нельзя, b'a' + 'b' — это TypeError; переход туда-обратно только через .encode() и .decode() с явно понятной кодировкой. Сами bytes неизменяемы, как str: если нужно править байты на месте, берут bytearray.",
        "en": "Indexing and slicing disagree on purpose: b[0] gives the integer 104, while b[0:1] gives back a bytes object, b'h'. Bytes never mix with str — b'a' + 'b' raises TypeError, and crossing between them goes through .encode() and .decode() with an encoding you have consciously chosen. Like str, bytes is immutable; when you need to patch bytes in place, reach for bytearray."
      },
      "syntax": "b\"строка\"  b'строка'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#bytes-objects",
      "version": "",
      "section": "Строки (str)",
      "subcat": "байты",
      "color_group": "str",
      "aliases": [
        "байтовая строка",
        "бинарные данные",
        "префикс b перед строкой"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "b = b\"hello\"; print(type(b))    # → <class 'bytes'>",
        "print(b[0])                       # → 104",
        "print(b\"hello\" + b\" world\")     # → b'hello world'",
        "print(b\"hi\" * 3)                # → b'hihihi'",
        "print(b\"hello\".decode(\"utf-8\"))  # → hello",
        "print(\"hello\".encode(\"utf-8\"))   # → b'hello'"
      ],
      "related": [
        "bytes",
        "str.encode",
        "bytes.decode",
        "bytearray"
      ],
      "related_errors": []
    },
    {
      "id": "f-строки",
      "title": "f-строки",
      "kind": "term",
      "summary": {
        "ru": "f-строки (f'...') — форматированные строковые литералы. Выражения в {} вычисляются во время выполнения.",
        "en": "f-strings (f'...') are formatted string literals. The expressions inside {} are evaluated at run time."
      },
      "body": {
        "ru": "Литеральная фигурная скобка пишется удвоением: f'{{{x}}}' выведет значение x в скобках. До Python 3.12 внутри выражения нельзя было использовать те же кавычки, что у самой строки, и обратный слэш — теперь (PEP 701) можно, так что чужой код с промежуточными переменными часто написан именно из-за старого ограничения. Помните, что f-строка подставляет значения сразу при вычислении литерала: заготовить её как шаблон на будущее нельзя, для этого есть str.format().",
        "en": "A literal brace is written by doubling it: f'{{{x}}}' prints the value of x wrapped in braces. Before Python 3.12 an expression could not reuse the outer quote character or contain a backslash; PEP 701 lifted that, so older code full of helper variables was often shaped by the old restriction. Remember that an f-string interpolates the moment the literal is evaluated — it cannot be stored as a reusable template, that is what str.format() is for."
      },
      "syntax": "f'{expr}'  |  f'{val:.2f}'  |  f'{val!r}'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/lexical_analysis.html#f-strings",
      "version": "3.6",
      "section": "Строки (str)",
      "subcat": "форматирование",
      "color_group": "str",
      "aliases": [
        "подставить переменную в строку",
        "интерполяция строк",
        "фигурные скобки в строке"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "name = 'Alice'",
        "print(f'Hello, {name}!')",
        "# → 'Hello, Alice!'",
        "x = 3.14159",
        "print(f'{x:.2f}')",
        "# → '3.14'",
        "print(f'{2 ** 10}')",
        "# → '1024'",
        "n = 42",
        "print(f'{n:>10d}')",
        "# → '        42'",
        "print(f'{n:010d}')",
        "# → '0000000042'",
        "s = 'hello'",
        "print(f'{s!r}')",
        "# → \"'hello'\"",
        "width = 10",
        "print(f'{\"center\":^{width}}')",
        "# → center (выравнивание по центру, ширина 10)",
        "val = 1234567.89",
        "print(f'{val:,.2f}')",
        "# → '1,234,567.89'"
      ],
      "related": [
        "format-метод-форматирования",
        "форматирование-старый-стиль",
        "format"
      ],
      "related_errors": []
    },
    {
      "id": "format-метод-форматирования",
      "title": "format() — метод форматирования",
      "kind": "term",
      "summary": {
        "ru": "Метод str.format() подставляет аргументы по позиции или имени. Поддерживает спецификаторы формата.",
        "en": "The str.format() method substitutes arguments by position or by name. It supports format specifiers."
      },
      "body": {
        "ru": "Литеральные фигурные скобки в шаблоне экранируются удвоением: {{ и }}. Смешивать в одной строке автоматическую нумерацию {} и ручную {0} нельзя — будет ValueError. В новом коде обычно берут f-строки; format() остаётся полезен там, где шаблон приходит извне (конфиг, файл переводов) — но именно поэтому не форматируйте пользовательские шаблоны: через {0.__class__} из них можно вытащить внутренности объекта.",
        "en": "Literal braces are escaped by doubling them: {{ and }}. You cannot mix automatic numbering {} with explicit indexes {0} in one template — that raises ValueError. F-strings are the default for new code; format() earns its place when the template comes from outside the source (config, translation file) — and for that same reason never format a template supplied by a user: constructs like {0.__class__} can walk into the object's internals."
      },
      "syntax": "'{}'.format(val)  |  '{name}'.format(name=val)  |  '{:.2f}'.format(num)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.format",
      "version": "",
      "section": "Строки (str)",
      "subcat": "форматирование",
      "color_group": "str",
      "aliases": [
        "форматирование строки шаблоном",
        "два знака после запятой",
        "подстановка по имени"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('{} {}'.format('Hello', 'World'))",
        "# → 'Hello World'",
        "print('{0} {1} {0}'.format('ha', 'ho'))",
        "# → 'ha ho ha'",
        "print('{name} is {age}'.format(name='Alice', age=30))",
        "# → 'Alice is 30'",
        "print('{:.2f}'.format(3.14159))",
        "# → '3.14'",
        "print('{:>10}'.format('hi'))",
        "# → '        hi'",
        "print('{:0>5}'.format(42))",
        "# → '00042'",
        "print('{:,}'.format(1000000))",
        "# → '1,000,000'"
      ],
      "related": [
        "f-строки",
        "форматирование-старый-стиль",
        "str.format_map",
        "format"
      ],
      "related_errors": []
    },
    {
      "id": "in-not-in-для-строк",
      "title": "in / not in для строк",
      "kind": "term",
      "summary": {
        "ru": "Проверяет, является ли одна строка подстрокой другой. Возвращает bool.",
        "en": "Checks whether one string is a substring of another. Returns a bool."
      },
      "body": {
        "ru": "Для строк проверка идёт по подстроке, а не по отдельному элементу: 'py' in 'python' — True, а вот в списке ['python'] тот же 'py' даст False, потому что там сравниваются целые элементы. Пустая строка входит в любую строку, включая пустую, а поиск регистрозависимый — для сравнения без учёта регистра приводите обе стороны к .lower().",
        "en": "On strings the test is substring-based, not element-based: 'py' in 'python' is True, while 'py' in ['python'] is False because a list compares whole items. The empty string is contained in every string, even an empty one, and the check is case-sensitive — lowercase both sides when case should not matter."
      },
      "syntax": "'sub' in s  |  'sub' not in s",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#membership-test-operations",
      "version": "3.8",
      "section": "Строки (str)",
      "subcat": "поиск",
      "color_group": "str",
      "aliases": [
        "проверить наличие подстроки",
        "содержит ли строка слово",
        "есть ли символ в строке"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('py' in 'python')",
        "# → True",
        "print('xyz' in 'python')",
        "# → False",
        "print('xyz' not in 'python')",
        "# → True",
        "print('' in 'any')",
        "# → True (пустая всегда входит)",
        "print('P' in 'python')",
        "# → False (регистрозависимо)"
      ],
      "related": [
        "str.find",
        "str.count",
        "in-not-in-для-списков"
      ],
      "related_errors": []
    },
    {
      "id": "len-для-строк",
      "title": "len() для строк",
      "kind": "term",
      "summary": {
        "ru": "Возвращает количество символов в строке. Считает Unicode-символы как один символ.",
        "en": "Returns the number of characters in a string. Every Unicode character counts as one."
      },
      "body": {
        "ru": "len считает кодовые точки, а не то, что глаз воспринимает как один знак: эмодзи с модификатором или буква с отдельным комбинирующим ударением дадут 2 и больше. У той же строки, закодированной в UTF-8, длина уже другая — кириллица занимает там по два байта на букву, так что длину строки и длину её байтов путать нельзя. Сама операция O(1): длина хранится в объекте.",
        "en": "len counts code points, not what the eye reads as one character: an emoji with a skin-tone modifier or a letter written with a separate combining accent comes out as 2 or more. Encode the same text to UTF-8 and the length changes — Cyrillic takes two bytes per letter there — so never mix up the length of a string with the length of its bytes. The call itself is O(1); the length is stored on the object."
      },
      "syntax": "len(s)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#len",
      "version": "",
      "section": "Строки (str)",
      "subcat": "длина",
      "color_group": "str",
      "aliases": [
        "длина строки",
        "сколько символов в строке",
        "количество букв в слове"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print(len(''))",
        "# → 0",
        "print(len('hello'))",
        "# → 5",
        "print(len('   '))",
        "# → 3",
        "print(len('Привет'))",
        "# → 6",
        "s = 'test'",
        "if len(s) > 3:",
        "    print('длинная')",
        "    # → 'длинная'"
      ],
      "related": [
        "len",
        "индексирование-строк",
        "срезы-строк"
      ],
      "related_errors": []
    },
    {
      "id": "raw-строки-r-...",
      "title": "Raw-строки r\"...\"",
      "kind": "term",
      "summary": {
        "ru": "Raw-строки (r\"...\") не обрабатывают escape-последовательности: обратный слэш трактуется как обычный символ. Незаменимы для регулярных выражений и путей Windows.",
        "en": "Raw strings (r\"...\") do not process escape sequences: a backslash is an ordinary character. Indispensable for regular expressions and Windows paths."
      },
      "body": {
        "ru": "Префикс r влияет только на разбор литерала, а не на то, что потом делают с текстом: r\"\\d\" — это те же два символа, слэш и d, и модуль re по-прежнему видит в них шаблон «цифра», просто вам не нужно удваивать слэши. Главная ловушка — raw-строка не может заканчиваться нечётным числом обратных слэшей: r\"C:\\\" даёт SyntaxError, потому что слэш всё равно экранирует закрывающую кавычку (хотя в строку и попадает). Для путей в Windows надёжнее pathlib, чем ручная сборка строк.",
        "en": "The r prefix changes only how the literal is parsed, not what later code does with the text: r\"\\d\" is just backslash plus d, and the re module still reads it as the digit pattern — you simply avoid doubling backslashes. The classic trap is that a raw string cannot end in an odd number of backslashes: r\"C:\\\" is a SyntaxError, since the backslash still escapes the closing quote even though it stays in the string. For Windows paths, pathlib beats hand-built string literals."
      },
      "syntax": "r\"строка\"  r'строка'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/lexical_analysis.html#raw-string-literals",
      "version": "3.6",
      "section": "Строки (str)",
      "subcat": "создание",
      "color_group": "str",
      "aliases": [
        "сырая строка",
        "строка без экранирования обратного слэша",
        "строка для регулярного выражения"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print(r\"\\n\\t\")           # → \\n\\t  (без переноса и табуляции)",
        "print(len(r\"\\n\"))        # → 2",
        "print(r\"C:\\Users\\name\")  # → C:\\Users\\name",
        "import re",
        "m = re.search(r\"\\d+\", \"abc123\")",
        "print(m.group())        # → 123",
        "print(r\"abc\" == \"abc\")  # → True"
      ],
      "related": [
        "создание-строк",
        "спецсимволы-паттернов",
        "тройные-кавычки"
      ],
      "related_errors": []
    },
    {
      "id": "str.capitalize",
      "title": "str.capitalize",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию строки: первый символ — заглавный, остальные строчные.",
        "en": "Return a copy with the first character capitalized and the rest lowercased."
      },
      "body": {
        "ru": "Остаток строки принудительно опускается в нижний регистр, поэтому аббревиатуры и внутренние заглавные портятся: 'HTML файл' превратится в 'Html файл'. Если нужно поднять только первую букву, не трогая остальное, берут срез первого символа с upper() и приклеивают хвост как есть. С Python 3.8 первый символ приводится к титульному регистру, а не к верхнему — для обычных букв это неразличимо, для редких лигатур результат отличается.",
        "en": "Everything after the first character is force-lowercased, so acronyms and internal capitals get mangled: 'HTML file' becomes 'Html file'. To raise just the first letter and leave the rest untouched, uppercase a one-character slice and concatenate the remainder unchanged. Since Python 3.8 the first character is converted to title case rather than upper case — indistinguishable for ordinary letters, different for rare ligatures."
      },
      "syntax": "s.capitalize()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.capitalize",
      "version": "",
      "section": "Строки (str)",
      "subcat": "регистр",
      "color_group": "str",
      "aliases": [
        "сделать первую букву заглавной",
        "первая буква большая"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('привет МИР'.capitalize())   # → Привет мир",
        "print('123abc'.capitalize())       # → 123abc",
        "print('ALREADY Done'.capitalize())  # → Already done"
      ],
      "related": [
        "str.title",
        "str.upper",
        "str.lower"
      ],
      "related_errors": []
    },
    {
      "id": "str.casefold",
      "title": "str.casefold",
      "kind": "function",
      "summary": {
        "ru": "Как lower(), но агрессивнее — для регистронезависимого сравнения (напр. ß → ss).",
        "en": "Aggressive lowercasing for caseless matching (e.g. ß → ss)."
      },
      "body": {
        "ru": "На ASCII и кириллице casefold() и lower() дают одинаковый результат — разница вылезает там, где одна и та же буква пишется по-разному: немецкое ß, греческая конечная сигма ς и подобное. Отсюда правило: сравнивать строки без учёта регистра — через casefold(), показывать пользователю — через lower(), потому что casefold может поменять и буквы, и длину строки, и обратно это не разворачивается.",
        "en": "For ASCII and Cyrillic text casefold() and lower() return the same thing; the difference shows up only where one letter has several written forms, like German ß or Greek final sigma ς. Hence the rule: compare caselessly with casefold(), display with lower() — casefold may change the letters and the length of the string, and that transformation cannot be undone."
      },
      "syntax": "s.casefold()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.casefold",
      "version": "3.3",
      "section": "Строки (str)",
      "subcat": "регистр",
      "color_group": "str",
      "aliases": [
        "сравнение строк без учёта регистра",
        "агрессивное приведение к нижнему регистру"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('Straße'.casefold())  # → strasse",
        "print('ß'.casefold() == 'ss')  # → True",
        "print('HeLLo'.casefold())   # → hello"
      ],
      "related": [
        "str.lower",
        "сравнение-строк",
        "str.upper"
      ],
      "related_errors": []
    },
    {
      "id": "str.center",
      "title": "str.center",
      "kind": "function",
      "summary": {
        "ru": "Центрирует строку в поле ширины width, дополняя символом fillchar (по умолч. пробел).",
        "en": "Center the string in a field of the given width, padded with fillchar (space by default)."
      },
      "body": {
        "ru": "Width — это дополнение, а не обрезка: если width не больше длины строки, вернётся исходная строка целиком, так что ровные колонки один center не гарантирует. При нечётном остатке дополнения center отдаёт лишний символ влево (когда width нечётный), а спецификатор формата f'{s:^6}' в такой же ситуации всегда отдаёт его вправо — из-за этой мелочи два способа центрирования дают разный результат.",
        "en": "Width only pads, it never trims: if width is not greater than the string length you simply get the string back, so center alone does not guarantee aligned columns. When the padding does not split evenly, center puts the extra character on the left (for an odd width), whereas the format spec f'{s:^6}' always puts it on the right — so the two ways of centering can produce different strings."
      },
      "syntax": "s.center(width[, fillchar])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.center",
      "version": "",
      "section": "Строки (str)",
      "subcat": "выравнивание",
      "color_group": "str",
      "aliases": [
        "центрировать строку",
        "выровнять текст по центру",
        "дополнить пробелами с обеих сторон"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('hi'.center(6, '*'))  # → **hi**",
        "print('hi'.center(5))       # →   hi ",
        "print('hi'.center(1))       # → hi"
      ],
      "related": [
        "str.ljust",
        "str.rjust",
        "str.zfill"
      ],
      "related_errors": []
    },
    {
      "id": "str.count",
      "title": "str.count",
      "kind": "function",
      "summary": {
        "ru": "Число непересекающихся вхождений подстроки. Опц. диапазон start/end.",
        "en": "Return the number of non-overlapping occurrences of the substring."
      },
      "body": {
        "ru": "Вхождения считаются непересекающимися и слева направо: в строке из четырёх букв 'a' подстрока 'aa' найдётся дважды, а не трижды. Пустая подстрока даёт len(s)+1 — по позиции между каждой парой символов плюс два края. Каждый вызов проходит строку целиком, поэтому считать частоты десятка разных символов десятью вызовами count дорого: collections.Counter соберёт всё за один проход.",
        "en": "Matches are counted left to right and never overlap, so 'aa' occurs twice in a run of four 'a' characters, not three times. An empty substring returns len(s)+1, one position between every pair of characters plus both ends. Each call rescans the whole string, so tallying many different characters with many count calls is wasteful — collections.Counter does it in a single pass."
      },
      "syntax": "s.count(sub[, start[, end]])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.count",
      "version": "",
      "section": "Строки (str)",
      "subcat": "поиск",
      "color_group": "str",
      "aliases": [
        "сколько раз символ встречается в строке",
        "посчитать вхождения подстроки",
        "число повторов подстроки"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('banana'.count('a'))   # → 3",
        "print('banana'.count('na'))  # → 2",
        "print('aaa'.count('aa'))     # → 1"
      ],
      "related": [
        "str.find",
        "in-not-in-для-строк",
        "collections.counter",
        "list.count"
      ],
      "related_errors": []
    },
    {
      "id": "str.encode",
      "title": "str.encode",
      "kind": "function",
      "summary": {
        "ru": "Кодирует строку в байты по указанной кодировке (по умолчанию UTF-8).",
        "en": "Encode the string to bytes using the given codec (UTF-8 by default)."
      },
      "body": {
        "ru": "encode() отдаёт bytes — это другой тип: сложить его со строкой нельзя, а len считает байты, а не символы (в UTF-8 'café' — пять байт). Всё, что не помещается в выбранную кодировку, при errors='strict' даёт UnicodeEncodeError; 'ignore' и 'replace' глушат ошибку ценой безвозвратно потерянных символов. Обратно разворачивается через bytes.decode() с той же самой кодировкой — угадать её по содержимому нельзя.",
        "en": "encode() hands back bytes, a different type: it won't concatenate with str, and its len counts bytes rather than characters ('café' is five bytes in UTF-8). Anything outside the chosen codec raises UnicodeEncodeError under errors='strict'; 'ignore' and 'replace' silence it by throwing characters away for good. Going back needs bytes.decode() with the very same codec — the codec cannot be inferred from the data."
      },
      "syntax": "s.encode(encoding='utf-8', errors='strict')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.encode",
      "version": "",
      "section": "Строки (str)",
      "subcat": "кодировки",
      "color_group": "str",
      "aliases": [
        "строка в байты",
        "закодировать строку",
        "кодировка текста"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('abc'.encode())            # → b'abc'",
        "print('café'.encode('utf-8'))    # → b'caf\\xc3\\xa9'",
        "print('hi'.encode('ascii'))      # → b'hi'"
      ],
      "related": [
        "bytes.decode",
        "b-...-байт-строки",
        "unicodeencodeerror"
      ],
      "related_errors": [
        "UnicodeEncodeError",
        "LookupError"
      ]
    },
    {
      "id": "str.endswith",
      "title": "str.endswith",
      "kind": "function",
      "summary": {
        "ru": "True, если строка заканчивается суффиксом (или одним из кортежа суффиксов).",
        "en": "True if the string ends with the suffix (or one of a tuple of suffixes)."
      },
      "body": {
        "ru": "Второй аргумент — строка или именно кортеж строк: список вызовет TypeError, что регулярно ловят те, кто собрал расширения динамически (оберните в tuple()). Сравнение чувствительно к регистру, поэтому файл с расширением в верхнем регистре проверку на '.py' не пройдёт — приводите имя к нижнему регистру заранее.",
        "en": "The argument must be a string or a tuple of strings — a list raises TypeError, which bites people who build their extension list dynamically (wrap it in tuple()). Matching is case-sensitive, so an uppercase extension will not match '.py'; lowercase the name first, or compare Path(...).suffix instead."
      },
      "syntax": "s.endswith(suffix[, start[, end]])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.endswith",
      "version": "",
      "section": "Строки (str)",
      "subcat": "поиск",
      "color_group": "str",
      "aliases": [
        "проверить окончание строки",
        "заканчивается ли строка на подстроку",
        "проверка суффикса"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('file.py'.endswith('.py'))            # → True",
        "print('file.py'.endswith('.txt'))           # → False",
        "print('file.py'.endswith(('.py', '.txt')))  # → True"
      ],
      "related": [
        "str.startswith",
        "str.removesuffix",
        "in-not-in-для-строк"
      ],
      "related_errors": []
    },
    {
      "id": "str.expandtabs",
      "title": "str.expandtabs",
      "kind": "function",
      "summary": {
        "ru": "Заменяет табы пробелами до колонок кратных tabsize (по умолчанию 8).",
        "en": "Replace tabs with spaces, aligning to columns that are multiples of tabsize (default 8)."
      },
      "body": {
        "ru": "Ширина таба зависит от текущей колонки, а не фиксирована: таб дотягивает текст до ближайшей позиции, кратной tabsize, поэтому один и тот же символ раскрывается то в один пробел, то в семь — наивная замена таба на tabsize пробелов даёт другой результат. Счётчик колонок сбрасывается на каждом переводе строки и возврате каретки, так что многострочный текст считается построчно.",
        "en": "A tab's width depends on the current column rather than being fixed: it stretches the text to the next multiple of tabsize, so the same character may expand to one space or to seven — replacing each tab with tabsize spaces gives a different result. The column counter resets at every newline and carriage return, so multi-line text is measured line by line."
      },
      "syntax": "s.expandtabs(tabsize=8)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.expandtabs",
      "version": "",
      "section": "Строки (str)",
      "subcat": "замена",
      "color_group": "str",
      "aliases": [
        "заменить табы пробелами",
        "табуляция в пробелы"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('a\\tb'.expandtabs(4))   # → a   b",
        "print('ab\\tc'.expandtabs(4))  # → ab  c",
        "print('a\\tb'.expandtabs(1))   # → a b"
      ],
      "related": [
        "str.replace",
        "textwrap.dedent",
        "bytes.expandtabs"
      ],
      "related_errors": []
    },
    {
      "id": "str.find",
      "title": "str.find",
      "kind": "function",
      "summary": {
        "ru": "Индекс первого вхождения подстроки или -1, если не найдена. Опц. start/end.",
        "en": "Return the lowest index of the substring, or -1 if not found. Optional start/end."
      },
      "body": {
        "ru": "Признак «не найдено» — это -1, а не None, поэтому проверка вида «если s.find(sub)» ломается сразу с двух сторон: совпадение в самом начале даёт 0 и считается ложью, а отсутствие даёт -1 и считается истиной. Сравнивайте результат с -1 явно. Когда нужен только факт наличия, читабельнее оператор in; find берут, когда нужна сама позиция.",
        "en": "Failure is signalled by -1, not None, so a truthiness test on the result is wrong twice over: a match at position 0 reads as false, while a miss at -1 reads as true. Always compare against -1 explicitly. If you only need to know whether the substring is there, the in operator is clearer; reach for find when you actually need the position."
      },
      "syntax": "s.find(sub[, start[, end]])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.find",
      "version": "",
      "section": "Строки (str)",
      "subcat": "поиск",
      "color_group": "str",
      "aliases": [
        "найти позицию подстроки",
        "индекс подстроки в строке",
        "поиск подстроки без ошибки"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('hello'.find('l'))     # → 2",
        "print('hello'.find('z'))     # → -1",
        "print('hello'.find('l', 3))  # → 3"
      ],
      "related": [
        "str.index",
        "str.rfind",
        "in-not-in-для-строк",
        "str.count"
      ],
      "related_errors": []
    },
    {
      "id": "str.format_map",
      "title": "str.format_map",
      "kind": "function",
      "summary": {
        "ru": "Как format, но берёт подстановки прямо из переданного словаря (без копирования).",
        "en": "Like format, but take substitutions directly from a mapping (no copy)."
      },
      "body": {
        "ru": "Отличие от format(**d) не только в отсутствии копирования словаря: format_map принимает любой mapping, поэтому можно подсунуть collections.defaultdict или свой класс с __missing__ — и отсутствующий ключ не уронит форматирование, а подставит заглушку. С обычным dict пропущенный ключ по-прежнему даёт KeyError.",
        "en": "The point is not just that the mapping is not copied: format_map accepts any mapping, so you can pass a collections.defaultdict or your own class with __missing__ and have absent keys fall back to a placeholder instead of blowing up. With a plain dict a missing key still raises KeyError."
      },
      "syntax": "s.format_map(mapping)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.format_map",
      "version": "3.2",
      "section": "Строки (str)",
      "subcat": "форматирование",
      "color_group": "str",
      "aliases": [
        "подстановка из словаря в строку",
        "форматирование строки словарём"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('{x}!'.format_map({'x': 'ok'}))          # → ok!",
        "print('{a}-{b}'.format_map({'a': 1, 'b': 2}))  # → 1-2",
        "print('{name}'.format_map({'name': 'Аня'}))    # → Аня"
      ],
      "related": [
        "format-метод-форматирования",
        "f-строки",
        "string.template"
      ],
      "related_errors": [
        "KeyError"
      ]
    },
    {
      "id": "str.index",
      "title": "str.index",
      "kind": "function",
      "summary": {
        "ru": "Как find, но бросает ValueError, если подстрока не найдена.",
        "en": "Like find, but raise ValueError when the substring is not found."
      },
      "body": {
        "ru": "Берите index, когда отсутствие подстроки означает баг: он падает громко и сразу, тогда как -1 от find спокойно уйдёт дальше в срез или арифметику и молча даст отсчёт от конца строки вместо ошибки. Если отсутствие — штатная ситуация, тогда либо find с явной проверкой, либо try/except ValueError — именно ValueError, а не общий Exception.",
        "en": "Use index when a missing substring means the program is wrong: it fails loudly at the point of the mistake, whereas find's -1 quietly flows into a slice or into arithmetic and silently counts from the end of the string instead. When absence is a normal case, either check find's result explicitly or catch ValueError specifically, not a bare Exception."
      },
      "syntax": "s.index(sub[, start[, end]])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.index",
      "version": "",
      "section": "Строки (str)",
      "subcat": "поиск",
      "color_group": "str",
      "aliases": [
        "позиция подстроки или ошибка",
        "поиск подстроки с исключением"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('hello'.index('e'))    # → 1",
        "print('abcabc'.index('c'))   # → 2",
        "print('hello'.index('l', 3))  # → 3"
      ],
      "related": [
        "str.find",
        "str.rindex",
        "valueerror",
        "list.index"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "str.isalnum",
      "title": "str.isalnum",
      "kind": "function",
      "summary": {
        "ru": "True, если строка непуста и все символы буквенно-цифровые.",
        "en": "True if the string is non-empty and all characters are alphanumeric."
      },
      "body": {
        "ru": "Проверка идёт по Unicode, а не по набору [A-Za-z0-9]: кириллица проходит, и вместе с ней экзотика вроде римской цифры Ⅻ или дроби ½, хотя int() их не примет. Если нужны именно латинские буквы и цифры, добавьте isascii() или регулярку. Подчёркивание алфавитно-цифровым не считается, поэтому для проверки имени переменной берут isidentifier().",
        "en": "The test is Unicode-wide, not [A-Za-z0-9]: Cyrillic passes, and so do oddities like the Roman numeral Ⅻ or the fraction ½, even though int() would reject them. Pair it with isascii(), or use a regex, when you really mean plain letters and digits. Underscore is not alphanumeric, so for a variable-name check use isidentifier() instead."
      },
      "syntax": "s.isalnum()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.isalnum",
      "version": "",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "только буквы и цифры",
        "строка без спецсимволов"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('abc123'.isalnum())  # → True",
        "print('a b'.isalnum())     # → False",
        "print(''.isalnum())        # → False"
      ],
      "related": [
        "str.isalpha",
        "str.isdigit",
        "str.isascii"
      ],
      "related_errors": []
    },
    {
      "id": "str.isalpha",
      "title": "str.isalpha",
      "kind": "function",
      "summary": {
        "ru": "True, если строка непуста и все символы — буквы.",
        "en": "True if the string is non-empty and all characters are letters."
      },
      "body": {
        "ru": "Буква здесь — любая буква Unicode, поэтому кириллица, умлауты и иероглифы проходят, а пробел, дефис и апостроф — нет: 'Анна Мария' и 'Jean-Luc' дадут False, хотя это нормальные имена. Пустая строка тоже False, так что отдельная проверка на непустоту не нужна. Надстрочные цифры вроде '²' не alpha, но numeric.",
        "en": "Letter means any Unicode letter, so Cyrillic, umlauts and CJK all pass, while a space, hyphen or apostrophe does not — 'Anna Maria' and 'Jean-Luc' come back False even though they are perfectly good names. The empty string is False as well, so a separate emptiness check is redundant. Superscripts like '²' count as numeric rather than alpha."
      },
      "syntax": "s.isalpha()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.isalpha",
      "version": "",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "состоит ли строка из букв",
        "только буквы в строке"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('abc'.isalpha())    # → True",
        "print('Привет'.isalpha())  # → True",
        "print('abc1'.isalpha())   # → False"
      ],
      "related": [
        "str.isalnum",
        "str.isdigit",
        "string.ascii_letters"
      ],
      "related_errors": []
    },
    {
      "id": "str.isascii",
      "title": "str.isascii",
      "kind": "function",
      "summary": {
        "ru": "True, если строка пуста или все символы — из диапазона ASCII.",
        "en": "True if the string is empty or all characters are ASCII."
      },
      "body": {
        "ru": "Метод появился в Python 3.7 и, в отличие от остальных is-проверок, на пустой строке даёт True. ASCII тут — про кодовые точки меньше 128, а не про печатаемость: перевод строки, табуляция и нулевой байт проверку проходят. Удобно как дешёвый тест перед encode('ascii') или чтобы поймать кириллическую букву-двойника, затесавшуюся в идентификатор.",
        "en": "Added in Python 3.7, and unlike the other is-methods it returns True for the empty string. ASCII here means code points below 128, not printable: newline, tab and the null byte all pass. Handy as a cheap pre-check before encode('ascii'), or to catch a Cyrillic look-alike letter smuggled into an identifier."
      },
      "syntax": "s.isascii()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.isascii",
      "version": "3.7",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "проверка на латиницу",
        "строка без кириллицы"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('abc'.isascii())     # → True",
        "print('café'.isascii())    # → False",
        "print(''.isascii())        # → True"
      ],
      "related": [
        "str.isprintable",
        "str.encode",
        "str.isalnum"
      ],
      "related_errors": []
    },
    {
      "id": "str.isdecimal",
      "title": "str.isdecimal",
      "kind": "function",
      "summary": {
        "ru": "True, если все символы — десятичные цифры (0–9); строже isdigit.",
        "en": "True if all characters are decimal digits (0–9); stricter than isdigit."
      },
      "body": {
        "ru": "Из тройки isdecimal → isdigit → isnumeric эта проверка самая узкая, и только на неё можно опираться перед int(): True означает, что все символы — десятичные цифры (категория Unicode Nd), которые int() разберёт. Знак минуса, точка и пробелы в набор не входят, поэтому '-5' и ' 12' дадут False — для знака и дробей остаётся try/except ValueError.",
        "en": "Of the trio isdecimal → isdigit → isnumeric this is the narrowest check, and the only one worth trusting before int(): True means every character is a decimal digit (Unicode category Nd) that int() can parse. Signs, dots and spaces are not in that set, so '-5' and ' 12' come back False — for signed or fractional input fall back to try/except ValueError."
      },
      "syntax": "s.isdecimal()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.isdecimal",
      "version": "",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "только десятичные цифры",
        "строгая проверка на цифры"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('123'.isdecimal())  # → True",
        "print('²'.isdecimal())    # → False",
        "print(''.isdecimal())     # → False"
      ],
      "related": [
        "str.isdigit",
        "str.isnumeric",
        "int"
      ],
      "related_errors": []
    },
    {
      "id": "str.isdigit",
      "title": "str.isdigit",
      "kind": "function",
      "summary": {
        "ru": "True, если все символы — цифры (включая надстрочные, напр. ²).",
        "en": "True if all characters are digits (including superscripts like ²)."
      },
      "body": {
        "ru": "Набор символов здесь шире, чем у isdecimal: проходят надстрочные и обведённые цифры, на которых int() падает с ValueError. Поэтому isdigit — плохой сторож перед преобразованием в число; если вопрос именно «получится ли int(s)», берите isdecimal или сразу try/except.",
        "en": "The accepted set is wider than isdecimal's: superscripts and circled digits pass, yet int() raises ValueError on them. That makes isdigit a poor guard before converting to a number — when the real question is whether int(s) will work, use isdecimal or just try/except."
      },
      "syntax": "s.isdigit()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.isdigit",
      "version": "",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "проверить что строка это число",
        "состоит ли строка из цифр",
        "проверка ввода на цифры"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('123'.isdigit())   # → True",
        "print('²'.isdigit())     # → True",
        "print('12.3'.isdigit())  # → False"
      ],
      "related": [
        "str.isdecimal",
        "str.isnumeric",
        "int"
      ],
      "related_errors": []
    },
    {
      "id": "str.isidentifier",
      "title": "str.isidentifier",
      "kind": "function",
      "summary": {
        "ru": "True, если строка — допустимый идентификатор Python (в т.ч. ключевые слова).",
        "en": "True if the string is a valid Python identifier (keywords included)."
      },
      "body": {
        "ru": "Метод судит только о грамматике имени, но не о том, что имя свободно: ключевые слова вроде for и class проверку проходят, хотя присвоить им ничего нельзя. Если проверяете имя, сгенерированное программой или введённое пользователем, добавляйте вторую проверку — keyword.iskeyword().",
        "en": "The method judges the shape of the name, not whether the name is usable: keywords like for and class pass, even though you can never assign to them. When validating a generated or user-supplied name, pair it with a second check, keyword.iskeyword()."
      },
      "syntax": "s.isidentifier()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.isidentifier",
      "version": "",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "допустимое имя переменной",
        "можно ли так назвать переменную"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('x1'.isidentifier())    # → True",
        "print('1x'.isidentifier())    # → False",
        "print('for'.isidentifier())   # → True"
      ],
      "related": [
        "переменные",
        "str.isalnum"
      ],
      "related_errors": []
    },
    {
      "id": "str.islower",
      "title": "str.islower",
      "kind": "function",
      "summary": {
        "ru": "True, если в строке есть буквы и все они строчные.",
        "en": "True if all cased characters are lowercase and there is at least one."
      },
      "body": {
        "ru": "Символы без регистра — цифры, пробелы, знаки препинания — просто игнорируются, поэтому 'abc123' даёт True, а '123' и пустая строка — False: нужна хотя бы одна буква. Отсюда частая ошибка: not s.islower() не значит «в строке есть заглавные», на строке из одних цифр это выражение тоже истинно.",
        "en": "Caseless characters — digits, spaces, punctuation — are simply ignored, so 'abc123' is True while '123' and '' are False: at least one cased letter is required. Hence a common bug: not s.islower() does not mean \"contains uppercase\", it is also true for a string of digits alone."
      },
      "syntax": "s.islower()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.islower",
      "version": "",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "проверка на строчные буквы",
        "все буквы маленькие"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('abc'.islower())    # → True",
        "print('Abc'.islower())    # → False",
        "print('abc123'.islower())  # → True"
      ],
      "related": [
        "str.isupper",
        "str.lower",
        "str.istitle"
      ],
      "related_errors": []
    },
    {
      "id": "str.isnumeric",
      "title": "str.isnumeric",
      "kind": "function",
      "summary": {
        "ru": "True, если все символы числовые — включая дроби и римские (½, Ⅴ).",
        "en": "True if all characters are numeric, including fractions and numerals (½, Ⅴ)."
      },
      "body": {
        "ru": "Самая широкая из трёх проверок: True возвращается и для дробей, и для римских цифр, которые ни int(), ни float() разобрать не смогут. Числовое значение такого символа достаёт unicodedata.numeric(), а для валидации ввода перед int() нужен isdecimal.",
        "en": "The widest of the three checks: fractions and Roman numerals return True even though neither int() nor float() can parse them. To get the value of such a character use unicodedata.numeric(); to validate input before int() use isdecimal instead."
      },
      "syntax": "s.isnumeric()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.isnumeric",
      "version": "",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "проверить что строка числовая",
        "числовые символы юникода"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('123'.isnumeric())  # → True",
        "print('½'.isnumeric())    # → True",
        "print('1.2'.isnumeric())  # → False"
      ],
      "related": [
        "str.isdigit",
        "str.isdecimal",
        "int"
      ],
      "related_errors": []
    },
    {
      "id": "str.isprintable",
      "title": "str.isprintable",
      "kind": "function",
      "summary": {
        "ru": "True, если строка пуста или все символы печатаемые (без \\t, \\n и т.п.).",
        "en": "True if the string is empty or all characters are printable (no \\t, \\n, …)."
      },
      "body": {
        "ru": "Печатаемость тут определяет Unicode: непечатаемым считается всё из категорий Other и Separator, кроме обычного ASCII-пробела. Поэтому 'a b' даёт True, а строка с неразрывным пробелом \\xa0 — False. И ещё особенность: на пустой строке метод возвращает True, тогда как почти все остальные проверки is* дают False.",
        "en": "Printability here follows Unicode: everything in the Other and Separator categories is non-printable, with the plain ASCII space as the single exception. So 'a b' is True, while a string holding a non-breaking space \\xa0 is False. Note also that the empty string returns True, unlike nearly every other is* check."
      },
      "syntax": "s.isprintable()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.isprintable",
      "version": "",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "печатаемые символы",
        "проверка на непечатаемые символы"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('abc 123'.isprintable())  # → True",
        "print('a\\tb'.isprintable())     # → False",
        "print(''.isprintable())         # → True"
      ],
      "related": [
        "str.isascii",
        "str.isspace",
        "string.printable"
      ],
      "related_errors": []
    },
    {
      "id": "str.isspace",
      "title": "str.isspace",
      "kind": "function",
      "summary": {
        "ru": "True, если строка непуста и все символы — пробельные.",
        "en": "True if the string is non-empty and all characters are whitespace."
      },
      "body": {
        "ru": "Пробельными считаются не только пробел, \\t и \\n, но и юникодные вроде неразрывного \\xa0 — данные, склеенные из веба, часто «не пустые» именно из-за них. Если нужно просто понять, что во вводе нет ничего значащего, короче и надёжнее проверка not s.strip(): она заодно покрывает и пустую строку, на которой isspace() даёт False.",
        "en": "Whitespace here is not just space, \\t and \\n but Unicode ones too, such as the non-breaking \\xa0 — text scraped from the web often looks blank because of those. When you only need to know that the input carries nothing meaningful, not s.strip() is shorter and safer: it also covers the empty string, where isspace() returns False."
      },
      "syntax": "s.isspace()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.isspace",
      "version": "",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "строка состоит из пробелов",
        "проверка на пробельные символы"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('   '.isspace())    # → True",
        "print(' \\t\\n'.isspace())  # → True",
        "print(''.isspace())       # → False"
      ],
      "related": [
        "str.strip",
        "string.whitespace",
        "str.isprintable"
      ],
      "related_errors": []
    },
    {
      "id": "str.istitle",
      "title": "str.istitle",
      "kind": "function",
      "summary": {
        "ru": "True, если каждое слово начинается с заглавной буквы (title-регистр).",
        "en": "True if the string is titlecased: each word starts with an uppercase letter."
      },
      "body": {
        "ru": "Границей слова считается любой не-буквенный символ, поэтому апостроф ломает интуицию: \"They're\" даёт False, а титульным по мнению Python будет \"They'Re\". Та же логика и у str.title(), из-за чего он портит имена вроде O'Brien. Для человеческих заголовков метод почти бесполезен — он про механическое правило регистра, а не про язык.",
        "en": "Any non-letter counts as a word boundary, so apostrophes defeat intuition: \"They're\" is False, while Python considers \"They'Re\" properly titlecased. str.title() follows the same rule, which is why it mangles names like O'Brien. For real-world headings the method is close to useless — it encodes a mechanical casing rule, not language."
      },
      "syntax": "s.istitle()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.istitle",
      "version": "",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "проверка что каждое слово с большой буквы",
        "проверить стиль заголовка"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('Hello World'.istitle())  # → True",
        "print('hello world'.istitle())  # → False",
        "print('HELLO'.istitle())        # → False"
      ],
      "related": [
        "str.title",
        "str.islower",
        "str.isupper"
      ],
      "related_errors": []
    },
    {
      "id": "str.isupper",
      "title": "str.isupper",
      "kind": "function",
      "summary": {
        "ru": "True, если в строке есть буквы и все они заглавные.",
        "en": "True if all cased characters are uppercase and there is at least one."
      },
      "body": {
        "ru": "Метод смотрит только на буквы: цифры и знаки препинания он игнорирует, но если букв нет вовсе, ответ False — поэтому '123' и пустая строка не считаются заглавными. Из-за этого isupper() не эквивалентен сравнению s == s.upper(): для '123' сравнение истинно, а метод даёт False.",
        "en": "Only cased characters are examined: digits and punctuation are ignored, yet a string with no letters at all returns False, so '123' and the empty string are not uppercase. That is why isupper() is not the same as s == s.upper(): for '123' the comparison holds while the method returns False."
      },
      "syntax": "s.isupper()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.isupper",
      "version": "",
      "section": "Строки (str)",
      "subcat": "проверка символов",
      "color_group": "str",
      "aliases": [
        "проверка на заглавные буквы",
        "все буквы большие"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('ABC'.isupper())   # → True",
        "print('Abc'.isupper())   # → False",
        "print('ABC!'.isupper())  # → True"
      ],
      "related": [
        "str.islower",
        "str.upper",
        "str.istitle"
      ],
      "related_errors": []
    },
    {
      "id": "str.join",
      "title": "str.join",
      "kind": "function",
      "summary": {
        "ru": "Собирает строки итерабельного в одну через строку-разделитель (получатель).",
        "en": "Concatenate an iterable of strings using the string as the separator."
      },
      "body": {
        "ru": "Разделитель — это та строка, у которой вызывается метод: правильно ','.join(items), а не items.join(','), и порядок здесь путают почти все. Элементы join к строке не приводит — список чисел сразу даёт TypeError, числа нужно пропустить через map(str, ...). Склейка в цикле через += переписывает результат заново на каждом шаге, а join проходит последовательность и выделяет память один раз.",
        "en": "The separator is the string you call the method on: it is ','.join(items), never items.join(','), and that inversion trips up most beginners. join does no conversion — an iterable of ints raises TypeError, so wrap it in map(str, ...) first. Building a string with += in a loop rebuilds it on every step, while join walks the sequence and allocates the result once."
      },
      "syntax": "sep.join(iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.join",
      "version": "",
      "section": "Строки (str)",
      "subcat": "объединение",
      "color_group": "str",
      "aliases": [
        "объединить список в строку",
        "склеить строки через разделитель",
        "собрать строку из списка"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print(','.join(['a', 'b', 'c']))  # → a,b,c",
        "print(''.join(['1', '2', '3']))   # → 123",
        "print('-'.join('abc'))            # → a-b-c"
      ],
      "related": [
        "str.split",
        "конкатенация-строк",
        "sep"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "str.ljust",
      "title": "str.ljust",
      "kind": "function",
      "summary": {
        "ru": "Выравнивает строку по левому краю в поле ширины width, дополняя справа fillchar.",
        "en": "Left-justify the string in a field of the given width, padded on the right."
      },
      "body": {
        "ru": "ljust только дополняет и никогда не обрезает: строка длиннее width вылезет за колонку целиком, для жёсткой ширины нужен ещё срез s[:width]. Ширина считается в символах, а не в ширине глифов на экране — эмодзи и иероглифы занимают в терминале две клетки, и таблица всё равно поедет. В шаблоне вывода то же самое обычно короче записать как f'{s:<10}'.",
        "en": "ljust pads and never truncates: a string longer than width spills past the column in full, so a hard width needs a slice s[:width] as well. The width is counted in characters, not in the space a glyph occupies — emoji and CJK characters take two terminal cells, and the table drifts anyway. Inside a formatted string the same padding usually reads better as f'{s:<10}'."
      },
      "syntax": "s.ljust(width[, fillchar])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.ljust",
      "version": "",
      "section": "Строки (str)",
      "subcat": "выравнивание",
      "color_group": "str",
      "aliases": [
        "выровнять текст по левому краю",
        "дополнить строку справа пробелами",
        "ровные колонки при выводе"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('hi'.ljust(5, '.'))  # → hi...",
        "print('hi'.ljust(5) + '|')  # → hi   |",
        "print('hello'.ljust(3))    # → hello"
      ],
      "related": [
        "str.rjust",
        "str.center",
        "str.zfill"
      ],
      "related_errors": []
    },
    {
      "id": "str.lower",
      "title": "str.lower",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию строки, где все символы приведены к нижнему регистру.",
        "en": "Return a copy with all cased characters converted to lowercase."
      },
      "body": {
        "ru": "Строки неизменяемы: lower() возвращает новую строку, исходная остаётся прежней — результат обязательно нужно присвоить или сразу использовать, иначе вызов просто пропадёт впустую. Для сравнения без учёта регистра латиницы и кириллицы lower() хватает, но на текстах с ß и подобными буквами надёжнее casefold().",
        "en": "Strings are immutable: lower() returns a new string and leaves the original untouched, so the result has to be assigned or used right away or the call is wasted. For caseless comparison of Latin and Cyrillic text lower() is enough, but with letters like German ß casefold() is the safer choice."
      },
      "syntax": "s.lower()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.lower",
      "version": "",
      "section": "Строки (str)",
      "subcat": "регистр",
      "color_group": "str",
      "aliases": [
        "привести к нижнему регистру",
        "сделать буквы строчными"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('HELLO'.lower())     # → hello",
        "print('ПРИВЕТ'.lower())    # → привет",
        "print('Hello World'.lower())  # → hello world"
      ],
      "related": [
        "str.upper",
        "str.casefold",
        "str.islower"
      ],
      "related_errors": []
    },
    {
      "id": "str.lstrip",
      "title": "str.lstrip",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию без ведущих символов слева (по умолчанию пробелов).",
        "en": "Return a copy with leading characters removed (whitespace by default)."
      },
      "body": {
        "ru": "Аргумент — это набор символов, а не префикс: lstrip срезает слева всё подряд, пока очередной символ входит в набор. Поэтому 'template.py'.lstrip('temp') даёт 'late.py', а вовсе не '.py'. Если нужно убрать префикс именно целиком, есть str.removeprefix() (с Python 3.9).",
        "en": "The argument is a set of characters, not a prefix: lstrip keeps eating characters from the left as long as each one belongs to that set. That is why 'template.py'.lstrip('temp') gives 'late.py' and not '.py'. When you mean a whole prefix, use str.removeprefix() (Python 3.9+)."
      },
      "syntax": "s.lstrip([chars])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.lstrip",
      "version": "",
      "section": "Строки (str)",
      "subcat": "обрезка",
      "color_group": "str",
      "aliases": [
        "убрать пробелы слева",
        "обрезать строку слева",
        "удалить ведущие символы"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('  hi'.lstrip())     # → hi",
        "print('xxhi'.lstrip('x'))  # → hi",
        "print('--a--'.lstrip('-'))  # → a--"
      ],
      "related": [
        "str.strip",
        "str.rstrip",
        "str.removeprefix"
      ],
      "related_errors": []
    },
    {
      "id": "str.maketrans",
      "title": "str.maketrans",
      "kind": "function",
      "summary": {
        "ru": "Статический метод: строит таблицу перевода для translate из двух строк (и опц. удаляемых).",
        "en": "Static method: build a translation table for translate from two strings (plus optional deletes)."
      },
      "body": {
        "ru": "Таблица работает посимвольно: заменить подстроку из нескольких символов через translate не выйдет — для этого str.replace. Две строки-аргумента обязаны быть равной длины, иначе ValueError, а третий аргумент перечисляет символы на удаление. Со словарём гибче: ключом может быть символ или его код, значением — строка любой длины или None, что означает «выбросить».",
        "en": "The table maps character by character: translate cannot swap a multi-character substring — that is str.replace's job. The two-string form requires both strings to be the same length, otherwise ValueError, and the optional third argument lists characters to drop. The dict form is more flexible: keys are single characters or their code points, values may be strings of any length or None, which means delete."
      },
      "syntax": "str.maketrans(x[, y[, z]])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.maketrans",
      "version": "",
      "section": "Строки (str)",
      "subcat": "замена",
      "color_group": "str",
      "aliases": [
        "таблица замены символов",
        "заменить несколько символов сразу",
        "транслитерация строки"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('abc'.translate(str.maketrans('ab', 'AB')))  # → ABc",
        "t = str.maketrans({'x': 'y'}); print('xox'.translate(t))  # → yoy",
        "print('a1b2'.translate(str.maketrans('', '', '12')))  # → ab"
      ],
      "related": [
        "str.translate",
        "str.replace",
        "bytes.maketrans"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "str.partition",
      "title": "str.partition",
      "kind": "function",
      "summary": {
        "ru": "Делит строку по первому разделителю на кортеж (до, разделитель, после).",
        "en": "Split at the first occurrence of the separator into a (head, sep, tail) tuple."
      },
      "body": {
        "ru": "Главное преимущество перед split() — результат всегда ровно из трёх элементов, поэтому распаковка head, sep, tail = s.partition('=') не падает с ValueError ни на какой строке. Признак «разделителя не нашлось» — пустой средний элемент, а не пустой хвост: вся строка при этом уезжает в голову. Нужен последний разделитель, а не первый — берите rpartition().",
        "en": "The win over split() is that the result always has exactly three items, so head, sep, tail = s.partition('=') never raises ValueError, whatever the input. Detect a missing separator by the empty middle item rather than by an empty tail — when nothing matches, the whole string lands in the head. If you need the last separator instead of the first, use rpartition()."
      },
      "syntax": "s.partition(sep)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.partition",
      "version": "",
      "section": "Строки (str)",
      "subcat": "разбивка",
      "color_group": "str",
      "aliases": [
        "разбить строку по первому разделителю",
        "разделить строку на три части"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('a=b=c'.partition('='))  # → ('a', '=', 'b=c')",
        "print('abc'.partition('='))    # → ('abc', '', '')",
        "print('k: v'.partition(': '))  # → ('k', ': ', 'v')"
      ],
      "related": [
        "str.rpartition",
        "str.split",
        "str.find"
      ],
      "related_errors": []
    },
    {
      "id": "str.removeprefix",
      "title": "str.removeprefix",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию без префикса prefix, если он есть; иначе строку без изменений (Python 3.9+).",
        "en": "Return a copy with the given prefix removed if present, otherwise unchanged (Python 3.9+)."
      },
      "body": {
        "ru": "Главная ловушка — спутать с lstrip: lstrip('test_') снимает с начала любые символы из набора t, e, s, _ в любом порядке, а removeprefix ищет ровно эту подстроку. Убирается максимум одна копия префикса, повторов не будет. Метод появился в 3.9, на более старом Python его нет.",
        "en": "The classic mix-up is with lstrip: lstrip('test_') peels off any leading characters from the set t, e, s, _ in any order, while removeprefix matches that exact substring. At most one copy of the prefix is removed, never repeated occurrences. It only exists from Python 3.9 on."
      },
      "syntax": "s.removeprefix(prefix)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.removeprefix",
      "version": "3.9",
      "section": "Строки (str)",
      "subcat": "замена",
      "color_group": "str",
      "aliases": [
        "убрать префикс строки",
        "удалить начало строки"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('test_foo'.removeprefix('test_'))  # → foo",
        "print('foo'.removeprefix('test_'))       # → foo",
        "print('__x'.removeprefix('_'))           # → _x"
      ],
      "related": [
        "str.removesuffix",
        "str.startswith",
        "str.lstrip"
      ],
      "related_errors": []
    },
    {
      "id": "str.removesuffix",
      "title": "str.removesuffix",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию без суффикса suffix, если он есть; иначе строку без изменений (Python 3.9+).",
        "en": "Return a copy with the given suffix removed if present, otherwise unchanged (Python 3.9+)."
      },
      "body": {
        "ru": "Типичная задача — срезать расширение, и именно здесь rstrip('.py') портит данные: он снимает с конца любые точки, p и y, так что 'happy.py' превратится в 'ha'. Ручной срез по длине суффикса тоже небезопасен: если суффикса нет, он всё равно отрежет хвост, а на пустом суффиксе вернёт пустую строку. Для имён файлов чаще удобнее pathlib с его stem и with_suffix.",
        "en": "The usual job is stripping an extension, and that is exactly where rstrip('.py') corrupts data: it removes any trailing dots, p's and y's, turning 'happy.py' into 'ha'. Slicing by the suffix length by hand is no safer — it cuts the tail even when the suffix is absent, and an empty suffix leaves you with an empty string. For filenames, pathlib with stem and with_suffix is usually the better tool."
      },
      "syntax": "s.removesuffix(suffix)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.removesuffix",
      "version": "3.9",
      "section": "Строки (str)",
      "subcat": "замена",
      "color_group": "str",
      "aliases": [
        "убрать суффикс строки",
        "удалить окончание строки",
        "убрать расширение файла"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('file.py'.removesuffix('.py'))   # → file",
        "print('file'.removesuffix('.py'))      # → file",
        "print('x__'.removesuffix('_'))         # → x_"
      ],
      "related": [
        "str.removeprefix",
        "str.endswith",
        "str.rstrip"
      ],
      "related_errors": []
    },
    {
      "id": "str.replace",
      "title": "str.replace",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию, где все вхождения old заменены на new. Опц. лимит count.",
        "en": "Return a copy with all occurrences of old replaced by new (optional count limit)."
      },
      "body": {
        "ru": "Строки неизменяемы: замена возвращает новую строку и ничего не правит на месте — забытое присваивание результата одна из самых частых ошибок новичков. Проход идёт слева направо, вставленный текст повторно не просматривается, но вторая replace в цепочке спокойно наткнётся на то, что подставила первая, поэтому для взаимной замены символов берут translate. Пустой old — особый случай: new вставляется между каждой парой символов и по краям.",
        "en": "Strings are immutable, so replace hands back a new string and changes nothing in place — forgetting to assign the result is the single most common beginner slip. Scanning goes left to right and inserted text is not re-scanned, but a second replace in a chain will happily hit what the first one produced, which is why swapping two characters calls for translate instead. An empty old is a special case: new gets inserted between every pair of characters and at both ends."
      },
      "syntax": "s.replace(old, new[, count])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.replace",
      "version": "",
      "section": "Строки (str)",
      "subcat": "замена",
      "color_group": "str",
      "aliases": [
        "заменить подстроку в строке",
        "замена символов в тексте",
        "поменять часть строки"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('a.b.c'.replace('.', '_'))    # → a_b_c",
        "print('aaa'.replace('a', 'b', 2))   # → bba",
        "print('hello'.replace('l', ''))     # → heo"
      ],
      "related": [
        "str.translate",
        "re.sub",
        "str.removeprefix",
        "str.count"
      ],
      "related_errors": []
    },
    {
      "id": "str.rfind",
      "title": "str.rfind",
      "kind": "function",
      "summary": {
        "ru": "Индекс последнего вхождения подстроки или -1 (поиск с конца).",
        "en": "Return the highest index of the substring, or -1 if not found (search from the right)."
      },
      "body": {
        "ru": "Направление поиска обратное, но индекс возвращается обычный, от начала строки, а не отрицательный; аргументы start и end по-прежнему задают срез в прямом порядке. Для разбора по последнему разделителю обычно удобнее rpartition или rsplit с maxsplit=1 — они сразу отдают части, без ручной арифметики с индексом. Строгий двойник, бросающий ValueError вместо -1, называется rindex.",
        "en": "Only the scan direction is reversed: the returned index is still counted from the start of the string, never negative, and start/end still delimit a slice in the usual order. To split on the last separator, rpartition or rsplit with maxsplit=1 is usually cleaner, since they hand you the pieces without index arithmetic. The strict twin that raises ValueError instead of returning -1 is rindex."
      },
      "syntax": "s.rfind(sub[, start[, end]])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.rfind",
      "version": "",
      "section": "Строки (str)",
      "subcat": "поиск",
      "color_group": "str",
      "aliases": [
        "найти подстроку с конца",
        "позиция последнего вхождения"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('hello'.rfind('l'))  # → 3",
        "print('hello'.rfind('z'))  # → -1",
        "print('abcabc'.rfind('a'))  # → 3"
      ],
      "related": [
        "str.find",
        "str.rindex",
        "str.rpartition"
      ],
      "related_errors": []
    },
    {
      "id": "str.rindex",
      "title": "str.rindex",
      "kind": "function",
      "summary": {
        "ru": "Как rfind, но бросает ValueError, если подстрока не найдена (поиск с конца).",
        "en": "Like rfind, but raise ValueError when the substring is not found."
      },
      "body": {
        "ru": "Отличие от rfind ровно одно — реакция на отсутствие: rfind вернёт -1, rindex бросит ValueError. Берите rindex, когда пропажа подстроки означает битые данные и падать надо сразу; если результат всё равно предстоит проверять, rfind читается лучше, чем try/except. Аргументы start и end сужают зону поиска, но индекс возвращается от начала всей строки, а не от start.",
        "en": "The only difference from rfind is the failure mode: rfind returns -1, rindex raises ValueError. Reach for rindex when a missing substring means broken data and you want the crash; if you are going to inspect the result anyway, rfind reads better than a try/except. The start and end arguments narrow the search window, but the index you get back is still counted from the beginning of the whole string."
      },
      "syntax": "s.rindex(sub[, start[, end]])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.rindex",
      "version": "",
      "section": "Строки (str)",
      "subcat": "поиск",
      "color_group": "str",
      "aliases": [
        "поиск с конца с исключением",
        "последнее вхождение или ошибка"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('abcabc'.rindex('a'))  # → 3",
        "print('abcabc'.rindex('b'))  # → 4",
        "print('hello'.rindex('l'))   # → 3"
      ],
      "related": [
        "str.rfind",
        "str.index",
        "valueerror"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "str.rjust",
      "title": "str.rjust",
      "kind": "function",
      "summary": {
        "ru": "Выравнивает строку по правому краю в поле ширины width, дополняя слева fillchar.",
        "en": "Right-justify the string in a field of the given width, padded on the left."
      },
      "body": {
        "ru": "Если width не больше длины строки, ничего не обрезается — вернётся исходная строка целиком, так что для «уместить в N колонок» rjust не годится. Аргумент fillchar должен быть ровно одним символом, иначе TypeError. Для чисел со знаком берите zfill: rjust с нулём поставит нули перед минусом, а не после него.",
        "en": "When width is not greater than the current length nothing gets truncated — the original string comes back untouched, so rjust cannot squeeze text into N columns. The fillchar argument must be exactly one character, otherwise you get a TypeError. For signed numbers use zfill instead: rjust with '0' puts the zeros in front of the minus, not after it."
      },
      "syntax": "s.rjust(width[, fillchar])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.rjust",
      "version": "",
      "section": "Строки (str)",
      "subcat": "выравнивание",
      "color_group": "str",
      "aliases": [
        "выровнять текст по правому краю",
        "дополнить строку слева пробелами",
        "прижать число вправо"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('hi'.rjust(5, '.'))  # → ...hi",
        "print('42'.rjust(5, '0'))  # → 00042",
        "print('hello'.rjust(3))    # → hello"
      ],
      "related": [
        "str.ljust",
        "str.center",
        "str.zfill"
      ],
      "related_errors": []
    },
    {
      "id": "str.rpartition",
      "title": "str.rpartition",
      "kind": "function",
      "summary": {
        "ru": "Делит строку по последнему разделителю на кортеж (до, разделитель, после).",
        "en": "Split at the last occurrence of the separator into a (head, sep, tail) tuple."
      },
      "body": {
        "ru": "Кортеж всегда ровно из трёх элементов, поэтому распаковка на три имени не упадёт ни на каких данных — в отличие от rsplit, где длина списка зависит от того, нашёлся ли разделитель. Когда разделителя нет, пустыми оказываются первые два элемента, а вся строка попадает в третий (у partition наоборот — строка идёт первой). Пустая строка в роли разделителя не «режет по символам», а вызывает ValueError.",
        "en": "The result is always a three-item tuple, so unpacking into three names never blows up on unexpected input — unlike rsplit, whose list length depends on whether the separator was found. If the separator is missing, the first two items come back empty and the whole string lands in the third (partition puts the string first instead). An empty separator raises ValueError rather than splitting per character."
      },
      "syntax": "s.rpartition(sep)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.rpartition",
      "version": "",
      "section": "Строки (str)",
      "subcat": "разбивка",
      "color_group": "str",
      "aliases": [
        "разбить строку по последнему разделителю",
        "отделить хвост строки после разделителя"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('a=b=c'.rpartition('='))  # → ('a=b', '=', 'c')",
        "print('abc'.rpartition('='))    # → ('', '', 'abc')",
        "print('a/b/c'.rpartition('/'))  # → ('a/b', '/', 'c')"
      ],
      "related": [
        "str.partition",
        "str.rsplit",
        "str.rfind"
      ],
      "related_errors": []
    },
    {
      "id": "str.rsplit",
      "title": "str.rsplit",
      "kind": "function",
      "summary": {
        "ru": "Как split, но при заданном maxsplit разбивает с конца строки.",
        "en": "Like split, but split from the right when maxsplit is given."
      },
      "body": {
        "ru": "Пока maxsplit не задан, rsplit и split возвращают один и тот же список — разница проявляется только когда число разбиений ограничено и нужен именно хвост: последнее поле строки лога, расширение имени файла, домен адреса. Учтите разные режимы разделителя: без sep (или с sep=None) серии пробелов схлопываются и пустых кусков не бывает, а с явным разделителем каждый его повтор даёт пустую строку.",
        "en": "Without maxsplit, rsplit and split produce identical lists — the difference shows up only when you cap the number of splits and specifically want the tail: the last field of a log line, a file extension, the domain of an address. Note the two separator modes: with no sep (or sep=None) runs of whitespace collapse and no empty pieces appear, while an explicit separator yields an empty string for every repeat of it."
      },
      "syntax": "s.rsplit(sep=None, maxsplit=-1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.rsplit",
      "version": "",
      "section": "Строки (str)",
      "subcat": "разбивка",
      "color_group": "str",
      "aliases": [
        "разбить строку с конца",
        "разделить строку справа"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('a,b,c'.rsplit(',', 1))  # → ['a,b', 'c']",
        "print('a,b,c'.rsplit(','))     # → ['a', 'b', 'c']",
        "print('a b c'.rsplit(None, 1))  # → ['a b', 'c']"
      ],
      "related": [
        "str.split",
        "str.rpartition",
        "str.join"
      ],
      "related_errors": []
    },
    {
      "id": "str.rstrip",
      "title": "str.rstrip",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию без хвостовых символов справа (по умолчанию пробелов).",
        "en": "Return a copy with trailing characters removed (whitespace by default)."
      },
      "body": {
        "ru": "Тот же капкан, что у lstrip, только с правого конца: аргумент — набор символов, поэтому 'test.txt'.rstrip('.txt') вернёт 'tes'. Отрезать суффикс как целое умеет str.removesuffix() (с Python 3.9). Голый rstrip() удобен для снятия перевода строки при построчном чтении, но заодно съест и значимые пробелы в конце, если они часть данных.",
        "en": "Same trap as lstrip, mirrored: the argument is a character set, so 'test.txt'.rstrip('.txt') returns 'tes'. Use str.removesuffix() (Python 3.9+) when you mean a suffix as a whole. Bare rstrip() is convenient for dropping the newline off a line read from a file, but it also eats meaningful trailing spaces if your data carries any."
      },
      "syntax": "s.rstrip([chars])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.rstrip",
      "version": "",
      "section": "Строки (str)",
      "subcat": "обрезка",
      "color_group": "str",
      "aliases": [
        "убрать пробелы справа",
        "обрезать строку справа",
        "удалить перенос строки в конце"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('hi  '.rstrip())     # → hi",
        "print('hixx'.rstrip('x'))  # → hi",
        "print('--a--'.rstrip('-'))  # → --a"
      ],
      "related": [
        "str.strip",
        "str.lstrip",
        "str.removesuffix"
      ],
      "related_errors": []
    },
    {
      "id": "str.split",
      "title": "str.split",
      "kind": "function",
      "summary": {
        "ru": "Разбивает строку по разделителю в список. Без аргумента — по любым пробелам.",
        "en": "Split the string by a separator into a list; with no argument, split on any whitespace."
      },
      "body": {
        "ru": "Два режима расходятся именно на краях и повторах: split() без аргумента схлопывает любые серии пробелов и игнорирует их по краям, а split(' ') честно отдаёт пустую строку за каждый лишний пробел. Отсюда классическая ловушка на пустом вводе — ''.split() даёт пустой список, а ''.split(',') даёт список с одной пустой строкой, и проверка на длину ломается. Пустая строка как разделитель запрещена: будет ValueError, а не разбивка посимвольно.",
        "en": "The two modes differ exactly at the edges and on repeats: split() with no argument collapses runs of whitespace and ignores leading/trailing ones, while split(' ') faithfully returns an empty string for every extra space. Hence the classic empty-input trap — ''.split() gives an empty list, but ''.split(',') gives a list holding one empty string, and a length check breaks. An empty separator is rejected with ValueError, not treated as per-character splitting."
      },
      "syntax": "s.split(sep=None, maxsplit=-1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.split",
      "version": "",
      "section": "Строки (str)",
      "subcat": "разбивка",
      "color_group": "str",
      "aliases": [
        "разбить строку на слова",
        "разделить строку по запятой",
        "считать числа через пробел"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('a,b,c'.split(','))   # → ['a', 'b', 'c']",
        "print('a  b   c'.split())   # → ['a', 'b', 'c']",
        "print('a,b,c'.split(',', 1))  # → ['a', 'b,c']"
      ],
      "related": [
        "str.rsplit",
        "str.join",
        "str.splitlines",
        "str.partition"
      ],
      "related_errors": []
    },
    {
      "id": "str.splitlines",
      "title": "str.splitlines",
      "kind": "function",
      "summary": {
        "ru": "Разбивает строку по границам строк (\\n, \\r и др.) в список без переводов строк.",
        "en": "Split the string at line boundaries into a list, without the line breaks."
      },
      "body": {
        "ru": "От split('\\n') отличается двумя важными вещами: завершающий перевод строки не порождает лишний пустой элемент в конце, и границей считается не только \\n, но и \\r, \\r\\n, а также \\v, \\f, \\x1c-\\x1e, \\x85, \\u2028, \\u2029 — текст с такими символами может разрезаться там, где вы не ждали. Пустая строка даёт пустой список, а не список с одной пустой строкой.",
        "en": "It differs from split('\\n') in two ways that matter: a trailing newline does not add a stray empty item at the end, and line boundaries include not just \\n but \\r, \\r\\n, plus \\v, \\f, \\x1c-\\x1e, \\x85, \\u2028 and \\u2029 — text containing those can be cut where you did not expect it. An empty string yields an empty list, not a list with one empty string."
      },
      "syntax": "s.splitlines(keepends=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.splitlines",
      "version": "",
      "section": "Строки (str)",
      "subcat": "разбивка",
      "color_group": "str",
      "aliases": [
        "разбить текст на строки",
        "разделить по переносам строк"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('a\\nb\\nc'.splitlines())  # → ['a', 'b', 'c']",
        "print('a\\nb\\n'.splitlines())   # → ['a', 'b']",
        "print('one line'.splitlines())  # → ['one line']"
      ],
      "related": [
        "str.split",
        "file.readlines",
        "итерация-по-файлу"
      ],
      "related_errors": []
    },
    {
      "id": "str.startswith",
      "title": "str.startswith",
      "kind": "function",
      "summary": {
        "ru": "True, если строка начинается с префикса (или одного из кортежа префиксов).",
        "en": "True if the string starts with the prefix (or one of a tuple of prefixes)."
      },
      "body": {
        "ru": "Префиксом может быть строка или кортеж строк — список или множество дают TypeError, это самая частая осечка. Пустой префикс всегда даёт True. С аргументом start проверка сдвигается на указанную позицию: s.startswith(p, 5) равносильно s[5:].startswith(p), только без копирования строки.",
        "en": "The prefix must be a str or a tuple of str — a list or a set raises TypeError, which is the usual stumble. An empty prefix always matches. Passing start shifts the check to that position: s.startswith(p, 5) means the same as s[5:].startswith(p), but without copying the string."
      },
      "syntax": "s.startswith(prefix[, start[, end]])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.startswith",
      "version": "",
      "section": "Строки (str)",
      "subcat": "поиск",
      "color_group": "str",
      "aliases": [
        "проверить начало строки",
        "начинается ли строка с подстроки",
        "проверка префикса"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('hello'.startswith('he'))          # → True",
        "print('hello'.startswith('lo'))          # → False",
        "print('hello'.startswith(('hi', 'he')))  # → True"
      ],
      "related": [
        "str.endswith",
        "str.removeprefix",
        "str.find"
      ],
      "related_errors": []
    },
    {
      "id": "str.strip",
      "title": "str.strip",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию без ведущих и хвостовых символов (по умолчанию пробелов).",
        "en": "Return a copy with leading and trailing characters removed (whitespace by default)."
      },
      "body": {
        "ru": "strip убирает не подстроку, а любые символы из переданного набора с обоих концов; порядок и повторы внутри набора не важны, а середина строки не трогается вообще. Голый strip() снимает все пробельные символы, включая перевод строки и табуляцию, — поэтому его и вешают на результат input() и на строки из файла. Строки неизменяемы: s.strip() отдельной строкой ничего не делает, результат нужно присвоить.",
        "en": "strip removes any characters from the given set at both ends — not a substring; order and duplicates inside the set are irrelevant, and the middle of the string is never touched. Bare strip() clears all whitespace, newlines and tabs included, which is why it is routinely applied to input() and to lines read from a file. Strings are immutable, so s.strip() on a line of its own does nothing; you have to assign the result."
      },
      "syntax": "s.strip([chars])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.strip",
      "version": "",
      "section": "Строки (str)",
      "subcat": "обрезка",
      "color_group": "str",
      "aliases": [
        "убрать лишние пробелы по краям",
        "обрезать пробелы в начале и конце",
        "очистить ввод от пробелов"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('  hi  '.strip())    # → hi",
        "print('xxhixx'.strip('x'))  # → hi",
        "print('...a.'.strip('.'))   # → a"
      ],
      "related": [
        "str.lstrip",
        "str.rstrip",
        "str.split"
      ],
      "related_errors": []
    },
    {
      "id": "str.swapcase",
      "title": "str.swapcase",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию строки с инверсией регистра: заглавные ↔ строчные.",
        "en": "Return a copy with uppercase characters converted to lowercase and vice versa."
      },
      "body": {
        "ru": "Два swapcase() подряд не обязаны вернуть исходную строку: ß становится SS, а обратно уже ss. Символы без регистра — цифры, пробелы, знаки препинания — остаются как есть; в реальных задачах метод почти не нужен и встречается в основном в учебных упражнениях.",
        "en": "Applying swapcase() twice does not necessarily restore the original: ß turns into SS, and back again it becomes ss. Characters without case — digits, spaces, punctuation — are left alone, and outside of exercises this method is rarely what a real task needs."
      },
      "syntax": "s.swapcase()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.swapcase",
      "version": "",
      "section": "Строки (str)",
      "subcat": "регистр",
      "color_group": "str",
      "aliases": [
        "поменять регистр на противоположный",
        "инвертировать регистр букв"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('Hello World'.swapcase())  # → hELLO wORLD",
        "print('Привет'.swapcase())       # → пРИВЕТ",
        "print('abcABC123'.swapcase())    # → ABCabc123"
      ],
      "related": [
        "str.upper",
        "str.lower",
        "str.title"
      ],
      "related_errors": []
    },
    {
      "id": "str.title",
      "title": "str.title",
      "kind": "function",
      "summary": {
        "ru": "Возвращает строку, где каждое слово начинается с заглавной буквы, остальные строчные.",
        "en": "Return a titlecased version: each word starts with an uppercase letter."
      },
      "body": {
        "ru": "Началом слова title() считает любой символ после не-буквы, поэтому апостроф ломает результат (don't превращается в Don'T), а внутренние заглавные затираются: McDonald становится Mcdonald. Для человеческих заголовков берите string.capwords() или собственную обработку по словам — title() годится лишь для простого текста без апострофов и составных имён.",
        "en": "title() treats any character following a non-letter as a word start, so apostrophes break it (don't becomes Don'T) and inner capitals are lost: McDonald turns into Mcdonald. For real-world headings use string.capwords() or your own word-by-word logic; title() only suits plain text without apostrophes or compound names."
      },
      "syntax": "s.title()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.title",
      "version": "",
      "section": "Строки (str)",
      "subcat": "регистр",
      "color_group": "str",
      "aliases": [
        "каждое слово с заглавной буквы",
        "привести к регистру заголовка"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('hello world'.title())   # → Hello World",
        "print('пример строки'.title())  # → Пример Строки",
        "print(\"don't stop\".title())    # → Don'T Stop"
      ],
      "related": [
        "str.capitalize",
        "str.istitle",
        "string.capwords",
        "str.upper"
      ],
      "related_errors": []
    },
    {
      "id": "str.translate",
      "title": "str.translate",
      "kind": "function",
      "summary": {
        "ru": "Заменяет символы по таблице (обычно из str.maketrans): ord(символа) → замена.",
        "en": "Map characters through a translation table (usually from str.maketrans)."
      },
      "body": {
        "ru": "Ключи таблицы — коды символов из ord, а не сами символы: словарь вида {'a': 'A'} не вызовет ошибку, он просто молча ничего не заменит, поэтому таблицу почти всегда строят через str.maketrans. Значение None (или третий аргумент maketrans) удаляет символ, а замена может быть длиннее одного символа. В отличие от цепочки replace, строка проходится ровно один раз и подставленное дальше не переписывается — так и делают взаимные замены.",
        "en": "Table keys are character codes from ord, not characters: a dict like {'a': 'A'} raises nothing and silently replaces nothing, which is why the table is nearly always built with str.maketrans. A value of None (or maketrans's third argument) deletes the character, and a replacement may be longer than one character. Unlike a chain of replace calls, the string is walked exactly once and inserted text is never rewritten — that is how you swap two characters safely."
      },
      "syntax": "s.translate(table)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.translate",
      "version": "",
      "section": "Строки (str)",
      "subcat": "замена",
      "color_group": "str",
      "aliases": [
        "замена символов по таблице",
        "таблица замены символов",
        "удалить набор символов из строки"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('abc'.translate(str.maketrans('abc', 'xyz')))  # → xyz",
        "print('a-b-c'.translate(str.maketrans('', '', '-')))  # → abc",
        "print('cat'.translate({ord('a'): 'A'}))  # → cAt"
      ],
      "related": [
        "str.maketrans",
        "str.replace",
        "re.sub"
      ],
      "related_errors": []
    },
    {
      "id": "str.upper",
      "title": "str.upper",
      "kind": "function",
      "summary": {
        "ru": "Возвращает копию строки, где все символы приведены к верхнему регистру.",
        "en": "Return a copy with all cased characters converted to uppercase."
      },
      "body": {
        "ru": "Длина результата не обязана совпадать с длиной исходной строки: ß разворачивается в две буквы SS, так что позиции символов после upper() смещаются и по ним нельзя резать исходный текст. upper() и lower() не обратны друг другу — s.upper().lower() не всегда даёт s, поэтому для регистронезависимого сравнения лучше casefold().",
        "en": "The result is not guaranteed to have the same length as the input: ß expands to two characters, SS, so indices computed on the uppercased text no longer line up with the original. upper() and lower() are not inverses either — s.upper().lower() can differ from s — so use casefold() when you need a caseless comparison."
      },
      "syntax": "s.upper()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.upper",
      "version": "",
      "section": "Строки (str)",
      "subcat": "регистр",
      "color_group": "str",
      "aliases": [
        "привести к верхнему регистру",
        "сделать буквы заглавными"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('Hello'.upper())     # → HELLO",
        "print('привет'.upper())    # → ПРИВЕТ",
        "print('straße'.upper())    # → STRASSE"
      ],
      "related": [
        "str.lower",
        "str.isupper",
        "str.swapcase"
      ],
      "related_errors": []
    },
    {
      "id": "str.zfill",
      "title": "str.zfill",
      "kind": "function",
      "summary": {
        "ru": "Дополняет строку нулями слева до ширины width; знак +/- остаётся впереди.",
        "en": "Pad the string on the left with zeros to the given width; a leading sign is kept."
      },
      "body": {
        "ru": "В этом и отличие от rjust с нулём: zfill пропускает вперёд ведущий плюс или минус и дополняет уже после знака, поэтому знак не тонет в нулях. Знак учитывается только в самой первой позиции строки, а строка, уже не короче width, возвращается без изменений — обрезки не будет.",
        "en": "That sign handling is exactly what separates zfill from rjust with '0': zfill steps over a leading + or - and pads after it, so the sign never ends up buried in zeros. Only a sign in the very first position counts, and a string already at least width long is returned unchanged — nothing is ever truncated."
      },
      "syntax": "s.zfill(width)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#str.zfill",
      "version": "",
      "section": "Строки (str)",
      "subcat": "выравнивание",
      "color_group": "str",
      "aliases": [
        "дополнить нулями слева",
        "ведущие нули",
        "вывести число с нулями впереди"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('42'.zfill(5))    # → 00042",
        "print('-42'.zfill(5))   # → -0042",
        "print('3.14'.zfill(6))  # → 003.14"
      ],
      "related": [
        "str.rjust",
        "str.center",
        "f-строки"
      ],
      "related_errors": []
    },
    {
      "id": "индексирование-строк",
      "title": "Индексирование строк",
      "kind": "term",
      "summary": {
        "ru": "Доступ к символу строки по индексу. Положительные индексы от начала (0), отрицательные — от конца (-1).",
        "en": "Access to a character of a string by index. Positive indices count from the start (0), negative ones from the end (-1)."
      },
      "body": {
        "ru": "Выход за границу — это IndexError, тогда как срез молча возвращает то, что попало в диапазон: на пустой строке s[0] падает, а s[0:1] даёт пустую строку. Отдельного типа символа в Python нет — s[i] это строка длиной 1, и присвоить в неё нельзя: строки неизменяемы, попытка даёт TypeError. Индекс считается в символах Unicode, а не в байтах.",
        "en": "An out-of-range index raises IndexError, while a slice quietly returns whatever fits: on an empty string s[0] blows up but s[0:1] just gives an empty string. Python has no separate character type — s[i] is a one-character string, and you cannot assign to it, since strings are immutable and the attempt raises TypeError. Indices count Unicode characters, not bytes."
      },
      "syntax": "s[i]  # i — целое число",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
      "version": "",
      "section": "Строки (str)",
      "subcat": "индексы/срезы",
      "color_group": "str",
      "aliases": [
        "символ по индексу",
        "первый символ строки",
        "последний символ строки",
        "отрицательные индексы"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "s = 'Python'",
        "print(s[0])",
        "# → 'P'",
        "print(s[1])",
        "# → 'y'",
        "print(s[-1])",
        "# → 'n'",
        "print(s[-2])",
        "# → 'o'",
        "print(s[5])",
        "# → 'n'",
        "s2 = 'AB'",
        "print(s2[0], s2[1], s2[-1])",
        "# → 'A' 'B' 'B'"
      ],
      "related": [
        "срезы-строк",
        "len-для-строк",
        "индексирование-списка",
        "indexerror"
      ],
      "related_errors": []
    },
    {
      "id": "конкатенация-строк",
      "title": "+ конкатенация строк",
      "kind": "term",
      "summary": {
        "ru": "Оператор + объединяет две строки в новую. Операнды должны быть строками.",
        "en": "The + operator joins two strings into a new one. Both operands must be strings."
      },
      "body": {
        "ru": "Оба операнда обязаны быть строками: 'год ' + 2026 бросает TypeError, число надо завернуть в str() или собрать всё f-строкой. Каждое + создаёт новый объект, то есть склейка в цикле копирует накопленную строку снова и снова — когда кусков много, собирайте список и отдавайте его в ''.join().",
        "en": "Both operands must be strings: 'year ' + 2026 raises TypeError, so wrap the number in str() or use an f-string instead. Every + builds a brand-new object, so concatenating inside a loop recopies the whole accumulated string each time — collect the pieces in a list and hand them to ''.join() when there are many."
      },
      "syntax": "s1 + s2",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
      "version": "",
      "section": "Строки (str)",
      "subcat": "операторы",
      "color_group": "str",
      "aliases": [
        "склеить две строки",
        "сложение строк",
        "соединить строки плюсом"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('Hello' + ' ' + 'World')",
        "# → 'Hello World'",
        "a = 'foo'",
        "b = 'bar'",
        "print(a + b)",
        "# → 'foobar'",
        "print('py' + 'thon')",
        "# → 'python'",
        "prefix = 'pre_'",
        "print(prefix + 'fix')",
        "# → 'pre_fix'",
        "print('' + 'empty left')",
        "# → 'empty left'"
      ],
      "related": [
        "str.join",
        "f-строки",
        "повторение-строки"
      ],
      "related_errors": []
    },
    {
      "id": "повторение-строки",
      "title": "* повторение строки",
      "kind": "term",
      "summary": {
        "ru": "Оператор * повторяет строку n раз. При n <= 0 возвращает пустую строку.",
        "en": "The * operator repeats a string n times. For n <= 0 it returns an empty string."
      },
      "body": {
        "ru": "Множитель обязан быть целым числом: 'ab' * 2.0 даёт TypeError, дробное значение сначала приведите к int. В отличие от списков, где [[0]] * 3 создаёт три ссылки на один и тот же вложенный список, со строками этой ловушки нет — они неизменяемы, менять «одну из копий» просто невозможно.",
        "en": "The multiplier has to be an int: 'ab' * 2.0 raises TypeError, so convert a float first. Unlike lists, where [[0]] * 3 gives three references to the same inner list, strings carry no such trap — being immutable, there is nothing to mutate through a shared reference."
      },
      "syntax": "s * n  |  n * s",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
      "version": "",
      "section": "Строки (str)",
      "subcat": "операторы",
      "color_group": "str",
      "aliases": [
        "повторить строку несколько раз",
        "умножить строку на число",
        "дублировать текст"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('ab' * 3)",
        "# → 'ababab'",
        "print('-' * 20)",
        "# → '--------------------'",
        "print('ha' * 0)",
        "# → ''",
        "sep = '=-' * 5",
        "print(sep)",
        "# → '=-=-=-=-=-=-=-=-'",
        "print(3 * 'go!')",
        "# → 'go!go!go!'"
      ],
      "related": [
        "конкатенация-строк",
        "объединение-повторение-списков",
        "умножение"
      ],
      "related_errors": []
    },
    {
      "id": "создание-строк",
      "title": "Создание строк",
      "kind": "term",
      "summary": {
        "ru": "Строки создаются одинарными/двойными кавычками, тройными для многострочных, raw-строками (r''), байтами (b''), escape-последовательностями.",
        "en": "Strings are written with single or double quotes, triple quotes for multi-line text, raw strings (r''), bytes (b'') and escape sequences."
      },
      "body": {
        "ru": "Соседние строковые литералы склеиваются ещё на этапе компиляции: 'abc' 'def' — это одна строка 'abcdef'. Из-за этого забытая запятая в списке ['a', 'b' 'c'] не вызывает ошибки, а молча даёт два элемента вместо трёх. И b'...' — это не строка, а объект bytes: сложение или сравнение bytes со str кончается TypeError или False, нужен явный decode()/encode().",
        "en": "Adjacent string literals are joined at compile time: 'abc' 'def' is the single string 'abcdef'. That is why a missing comma in ['a', 'b' 'c'] raises nothing and silently yields two items instead of three. Also, b'...' is not a string but a bytes object: mixing bytes with str gives a TypeError or a silent False, so convert explicitly with decode()/encode()."
      },
      "syntax": "s = 'text' | s = \"text\" | s = '''multi''' | s = r'raw' | s = b'bytes'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/lexical_analysis.html#strings",
      "version": "3.3",
      "section": "Строки (str)",
      "subcat": "создание",
      "color_group": "str",
      "aliases": [
        "как объявить строку",
        "одинарные или двойные кавычки",
        "строковый литерал"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "s1 = 'hello'",
        "# → 'hello'",
        "s2 = \"world\"",
        "# → 'world'",
        "s3 = '''line1",
        "line2",
        "line3'''",
        "# → 'line1\\nline2\\nline3'",
        "s4 = r'C:\\Users\\name'",
        "# → 'C:\\\\Users\\\\name' (без escape)",
        "s5 = b'bytes'",
        "# → b'bytes'",
        "s6 = 'tab:\\there'",
        "# → 'tab:\\there' (\\t — табуляция)",
        "s7 = '\\u041f\\u0440\\u0438\\u0432\\u0435\\u0442'",
        "# → 'Привет'"
      ],
      "related": [
        "str",
        "тройные-кавычки",
        "raw-строки-r-...",
        "f-строки"
      ],
      "related_errors": []
    },
    {
      "id": "сравнение-строк",
      "title": "Сравнение строк",
      "kind": "term",
      "summary": {
        "ru": "Строки сравниваются лексикографически по Unicode code points символов.",
        "en": "Strings are compared lexicographically by the Unicode code points of their characters."
      },
      "body": {
        "ru": "Порядок по code point — не алфавитный: все заглавные латинские буквы идут раньше строчных, поэтому 'Zoo' < 'apple' даёт True, а кириллица и символы с диакритикой стоят вообще отдельным блоком. Для человеческой сортировки задавайте ключ, например key=str.lower или key=str.casefold. Второй típичный промах — сравнивать числа как строки: '10' < '9' истинно, потому что сопоставляется первый символ.",
        "en": "Code point order is not alphabetical order: every uppercase ASCII letter sorts before every lowercase one, so 'Zoo' < 'apple' is True, and Cyrillic or accented letters form their own separate block. For human-friendly sorting pass a key such as key=str.lower or key=str.casefold. The other common slip is comparing numbers as text: '10' < '9' is True because only the first characters decide."
      },
      "syntax": "s1 == s2  |  s1 < s2  |  s1 > s2",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#value-comparisons",
      "version": "3.8",
      "section": "Строки (str)",
      "subcat": "сравнение",
      "color_group": "str",
      "aliases": [
        "какая строка больше",
        "лексикографический порядок строк"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('abc' == 'abc')",
        "# → True",
        "print('abc' < 'abd')",
        "# → True ('c' < 'd')",
        "print('b' > 'a')",
        "# → True",
        "print('Z' < 'a')",
        "# → True (ASCII: Z=90, a=97)",
        "print('abc' == 'ABC')",
        "# → False (регистрозависимо)"
      ],
      "related": [
        "операторы-сравнения",
        "str.casefold",
        "is-is-not",
        "строки-в-памяти-интернирование-неизменяе"
      ],
      "related_errors": []
    },
    {
      "id": "срезы-строк",
      "title": "Срезы строк",
      "kind": "term",
      "summary": {
        "ru": "Получение подстроки по диапазону индексов. s[a:b] — от a до b (не включая b), s[a:b:c] — с шагом c. s[::-1] — реверс.",
        "en": "Taking a substring by a range of indices. s[a:b] runs from a to b (b excluded), s[a:b:c] takes step c. s[::-1] reverses the string."
      },
      "body": {
        "ru": "Срез не умеет вылетать за границы: s[10:20] на короткой строке вернёт пустую строку, тогда как s[10] бросит IndexError — этим удобно пользоваться, но так же легко проглядеть опечатку. Каждый срез — новая строка длиной b-a, поэтому резать строку в цикле по кусочку дороже, чем просто идти по ней. При отрицательном шаге границы читаются справа налево: нужно s[5:1:-1], а не s[1:5:-1].",
        "en": "A slice never goes out of range: s[10:20] on a short string just yields an empty string, while s[10] raises IndexError — handy, but it also hides typos. Every slice allocates a fresh string of length b-a, so chopping a string piece by piece in a loop costs more than iterating over it directly. With a negative step the bounds read right to left: you want s[5:1:-1], not s[1:5:-1]."
      },
      "syntax": "s[a:b]  |  s[a:b:c]  |  s[::-1]",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
      "version": "",
      "section": "Строки (str)",
      "subcat": "индексы/срезы",
      "color_group": "str",
      "aliases": [
        "подстрока по диапазону",
        "вырезать часть строки",
        "перевернуть строку"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "s = 'abcdefgh'",
        "print(s[2:5])",
        "# → 'cde'",
        "print(s[:3])",
        "# → 'abc'",
        "print(s[5:])",
        "# → 'fgh'",
        "print(s[::-1])",
        "# → 'hgfedcba'",
        "print(s[::2])",
        "# → 'aceg'",
        "print(s[-3:])",
        "# → 'fgh'",
        "print(s[1:7:2])",
        "# → 'bdf'"
      ],
      "related": [
        "индексирование-строк",
        "срезы-с-шагом-2-1",
        "срезы-списка",
        "slice"
      ],
      "related_errors": []
    },
    {
      "id": "строки-в-памяти-интернирование-неизменяе",
      "title": "Строки в памяти — интернирование, неизменяемость",
      "kind": "term",
      "summary": {
        "ru": "Строки в Python неизменяемы (immutable). Интернирование — кэширование коротких строк-идентификаторов. is проверяет идентичность объектов, == — равенство.",
        "en": "Strings in Python are immutable. Interning caches short identifier-like strings. is checks object identity, == checks equality."
      },
      "body": {
        "ru": "Из неизменяемости следует практическое: upper(), replace(), strip() и срезы ничего не правят на месте, а возвращают новую строку — забыть присвоить результат обратно самая частая ошибка новичка. Сравнивать строки нужно только через ==: is иногда «срабатывает» на литералах благодаря интернированию, но на строке, собранной в рантайме (из ввода или конкатенации), внезапно даёт False; с Python 3.8 сравнение через is с литералом ещё и вызывает SyntaxWarning.",
        "en": "Immutability has a very practical consequence: upper(), replace(), strip() and slices never edit in place, they hand back a new string, and forgetting to assign that result is the classic beginner bug. Compare strings with == only — is may appear to work on literals thanks to interning, yet fails on a string built at runtime from input or concatenation; since Python 3.8 using is against a literal also emits a SyntaxWarning."
      },
      "syntax": "s[i] = x  # TypeError  |  id(s)  |  sys.intern(s)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.intern",
      "version": "",
      "section": "Строки (str)",
      "subcat": "память",
      "color_group": "str",
      "aliases": [
        "неизменяемость строк",
        "почему нельзя изменить символ строки",
        "кэширование строк в памяти"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "s = 'hello'",
        "try:",
        "s[0] = 'H'",
        "except TypeError as e:",
        "print(e)",
        "# → 'str' object does not support item assignment",
        "a = 'hello'",
        "b = 'hello'",
        "print(a == b)",
        "# → True",
        "print(a is b)",
        "# → True (интернирование коротких строк)",
        "import sys",
        "x = sys.intern('some_long_string')",
        "y = sys.intern('some_long_string')",
        "print(x is y)",
        "# → True (принудительное интернирование)",
        "s2 = s.replace('h', 'H')",
        "print(s, s2)",
        "# → 'hello' 'Hello' (оригинал не изменился)"
      ],
      "related": [
        "is-is-not",
        "sys.intern",
        "id",
        "неизменяемость-кортежа"
      ],
      "related_errors": []
    },
    {
      "id": "тройные-кавычки",
      "title": "Тройные кавычки",
      "kind": "term",
      "summary": {
        "ru": "Строки в тройных кавычках (\"\"\"...\"\"\" или '''...''') могут содержать переносы строк и кавычки без экранирования. Используются для многострочных строк и docstring-документации.",
        "en": "Triple-quoted strings (\"\"\"...\"\"\" or '''...''') may contain line breaks and quotes with no escaping. Used for multi-line text and for docstrings."
      },
      "body": {
        "ru": "В строку попадает всё буквально, включая перевод строки сразу после открывающих кавычек и отступы внутри блока кода — поэтому многострочный текст в теле функции часто оказывается с лишними пробелами в начале каждой строки. Лечится обратным слэшем в конце первой строки (он съедает перенос) и textwrap.dedent() для отступов. Докстрингом строка становится только тогда, когда она первый оператор в теле модуля, класса или функции; в любом другом месте это просто литерал, который вычислили и выбросили.",
        "en": "Everything inside is taken literally — including the newline right after the opening quotes and the indentation of the surrounding code — so multi-line text written inside a function usually carries leading spaces on every line. A trailing backslash on the first line swallows the initial newline, and textwrap.dedent() strips the indentation. A triple-quoted string becomes a docstring only when it is the first statement of a module, class or function; anywhere else it is just a value that is computed and thrown away."
      },
      "syntax": "\"\"\"текст\"\"\"\n'''текст'''",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals",
      "version": "3.3",
      "section": "Строки (str)",
      "subcat": "создание",
      "color_group": "str",
      "aliases": [
        "многострочная строка",
        "текст на несколько строк",
        "докстринг"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "s = \"\"\"строка 1\\nстрока 2\"\"\"",
        "print(len(s))        # → 15",
        "def foo():",
        "\"\"\"Докстринг функции.\"\"\"",
        "pass",
        "print(foo.__doc__)   # → Докстринг функции.",
        "s2 = \"\"\"можно \\\"кавычки\\\" внутри\"\"\"",
        "print(\"кавычки\" in s2)  # → True",
        "print(type(s2))      # → <class 'str'>"
      ],
      "related": [
        "создание-строк",
        "raw-строки-r-...",
        "textwrap.dedent"
      ],
      "related_errors": []
    },
    {
      "id": "форматирование-старый-стиль",
      "title": "% форматирование (старый стиль)",
      "kind": "function",
      "summary": {
        "ru": "Старый стиль форматирования через оператор %. %s — строка, %d — целое, %f — вещественное, %r — repr.",
        "en": "The old formatting style, through the % operator. %s — string, %d — integer, %f — float, %r — repr."
      },
      "body": {
        "ru": "Правый операнд % — это кортеж аргументов, поэтому если подставить надо сам кортеж, его заворачивают ещё в один: '%s' % (t,), иначе Python разложит его по спецификаторам и упадёт с TypeError. %d к вещественному числу молча отбрасывает дробную часть. В новом коде пишут f-строки, но %-стиль жив в логировании: logging.info('x=%s', x) откладывает подстановку до момента, когда запись действительно пишется.",
        "en": "The right-hand operand of % is a tuple of arguments, so a tuple you want to insert as a value must be wrapped in another one — '%s' % (t,) — otherwise Python spreads it across the specifiers and fails with TypeError. %d applied to a float silently drops the fractional part. New code uses f-strings, yet %-style survives in logging: logging.info('x=%s', x) defers the substitution until the record is actually emitted."
      },
      "syntax": "'format %s' % value  |  'format %(key)s' % dict",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting",
      "version": "",
      "section": "Строки (str)",
      "subcat": "форматирование",
      "color_group": "str",
      "aliases": [
        "старый способ форматирования строк",
        "форматирование через знак процента"
      ],
      "keywords": [],
      "tags": [
        "str"
      ],
      "examples": [
        "print('Hello, %s!' % 'World')",
        "# → 'Hello, World!'",
        "print('x=%d, y=%d' % (3, 5))",
        "# → 'x=3, y=5'",
        "print('pi=%.3f' % 3.14159)",
        "# → 'pi=3.142'",
        "print('%(name)s is %(age)d' % {'name': 'Bob', 'age': 25})",
        "# → 'Bob is 25'",
        "print('%05d' % 42)",
        "# → '00042'"
      ],
      "related": [
        "f-строки",
        "format-метод-форматирования",
        "string.template"
      ],
      "related_errors": [
        "TypeError",
        "KeyError"
      ]
    },
    {
      "id": "bool",
      "title": "bool",
      "kind": "term",
      "summary": {
        "ru": "Логический тип данных. Подкласс int: True == 1, False == 0. bool(x) возвращает результат проверки истинности x.",
        "en": "The boolean type. A subclass of int: True == 1, False == 0. bool(x) returns the truth value of x."
      },
      "body": {
        "ru": "Раз bool — подкласс int, True равно 1 и сталкивается с ним как ключ словаря ({True: 'a', 1: 'b'} оставит одну запись), а в арифметике True ведёт себя как 1. Не сравнивайте с True/False явно (if x == True) — пишите просто if x:, иначе истинное значение вроде 2 (оно != True) даст неожиданный результат.",
        "en": "Because bool subclasses int, True equals 1 and collides with it as a dict key ({True: 'a', 1: 'b'} keeps one entry), and True acts as 1 in arithmetic. Don't compare against True/False explicitly (if x == True) — just write if x:, or a truthy value like 2 (which is != True) will surprise you."
      },
      "syntax": "bool([x])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#bool",
      "version": "",
      "section": "Типы данных",
      "subcat": "логика",
      "color_group": "op",
      "aliases": [
        "логический тип",
        "истина и ложь",
        "булев тип"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "print(True, False)  # → True False",
        "print(5 > 3)  # → True",
        "print(True and False)  # → False",
        "print(bool(0), bool(1), bool(\"\"))  # → False True False",
        "print(bool([]), bool([1]))  # → False True (falsy / truthy)",
        "print(True + True)  # → 2 (bool является подтипом int)",
        "print(not True)  # → False"
      ],
      "related": [
        "преобразование-типов",
        "and-or-not",
        "int"
      ],
      "related_errors": []
    },
    {
      "id": "bytearray",
      "title": "bytearray",
      "kind": "term",
      "summary": {
        "ru": "Изменяемая последовательность байт. В отличие от bytes, поддерживает присвоение по индексу и методы append(), extend().",
        "en": "A mutable sequence of bytes. Unlike bytes, it supports item assignment and the append() and extend() methods."
      },
      "body": {
        "ru": "Берите bytearray, когда двоичные данные собираются или правятся по кусочкам: append(), extend() и присваивание по индексу/срезу меняют объект на месте, без копии на каждом шаге. Плата за изменяемость — bytearray нехешируем: ключом словаря или элементом множества он быть не может, там нужен неизменяемый bytes.",
        "en": "Reach for bytearray when binary data is built up or patched piece by piece: append(), extend() and index/slice assignment mutate it in place instead of copying at every step. The price of mutability is that bytearray is unhashable — it can't be a dict key or set member, where you need the immutable bytes instead."
      },
      "syntax": "bytearray(n)  bytearray(iterable)  bytearray(b\"...\")  bytearray(source, encoding)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#func-bytearray",
      "version": "",
      "section": "Типы данных",
      "subcat": "байты",
      "color_group": "op",
      "aliases": [
        "изменяемый массив байт",
        "изменяемые байты"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "ba = bytearray(5); print(ba)      # → bytearray(b'\\x00\\x00\\x00\\x00\\x00')",
        "ba = bytearray(b\"hello\")",
        "ba[0] = 72; print(ba)             # → bytearray(b'Hello')",
        "ba.append(33); print(bytes(ba))   # → b'Hello!'",
        "print(bytearray.fromhex(\"4142\"))  # → bytearray(b'AB')",
        "ba2 = bytearray(b\"abc\")",
        "ba2.extend(b\"def\"); print(ba2)   # → bytearray(b'abcdef')"
      ],
      "related": [
        "bytes",
        "memoryview",
        "b-...-байт-строки"
      ],
      "related_errors": []
    },
    {
      "id": "bytes",
      "title": "bytes",
      "kind": "term",
      "summary": {
        "ru": "Неизменяемая последовательность байт (целых чисел 0–255). Используется для бинарных данных и текста в определённой кодировке.",
        "en": "An immutable sequence of bytes (integers 0–255). Used for binary data and for text in a particular encoding."
      },
      "body": {
        "ru": "bytes(5) даёт не цифры числа 5, а пять нулевых байт b'\\x00\\x00\\x00\\x00\\x00' — частая ловушка. Индексация возвращает int (b[0] → 65), а срез — снова bytes; менять содержимое нельзя, для этого есть изменяемый близнец bytearray.",
        "en": "bytes(5) is not the digits of 5 but five zero bytes b'\\x00\\x00\\x00\\x00\\x00' — a common trap. Indexing yields an int (b[0] → 65) while slicing yields bytes again, and the contents are immutable — reach for its mutable twin bytearray when you need to change them."
      },
      "syntax": "bytes(n)  bytes(iterable)  b\"...\"  bytes.fromhex(hex_str)  bytes(source, encoding)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#func-bytes",
      "version": "",
      "section": "Типы данных",
      "subcat": "байты",
      "color_group": "op",
      "aliases": [
        "байтовая строка",
        "бинарные данные",
        "двоичные данные"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "print(bytes(5))              # → b'\\x00\\x00\\x00\\x00\\x00'",
        "print(b\"hello\")              # → b'hello'",
        "print(bytes([72, 101, 108]))  # → b'Hel'",
        "print(bytes.fromhex(\"ff00ab\"))  # → b'\\xff\\x00\\xab'",
        "b = b\"ABC\"; print(b[0])      # → 65",
        "print(b\"hello\".hex())         # → 68656c6c6f",
        "print(bytes(\"hello\", \"utf-8\"))  # → b'hello' (из строки с кодировкой)"
      ],
      "related": [
        "bytearray",
        "b-...-байт-строки",
        "bytes.decode",
        "str.encode"
      ],
      "related_errors": []
    },
    {
      "id": "complex",
      "title": "complex",
      "kind": "term",
      "summary": {
        "ru": "Встроенный тип для комплексных чисел вида a+bj. Поддерживает арифметику, атрибуты .real и .imag, метод conjugate().",
        "en": "Built-in type for complex numbers of the form a+bj. Supports arithmetic, the .real and .imag attributes and the conjugate() method."
      },
      "body": {
        "ru": "Мнимая единица пишется с коэффициентом: 1j, а голое j — это имя переменной (NameError). Комплексные числа нельзя сравнивать через < и > (TypeError) — упорядоченности у них нет, работают только == и !=. Атрибуты .real и .imag всегда float, даже если число задано целыми.",
        "en": "The imaginary unit needs a coefficient — write 1j; a bare j is just a variable name (NameError). Complex numbers have no ordering, so < and > raise TypeError — only == and != work. The .real and .imag attributes are always floats, even when you built the number from ints."
      },
      "syntax": "complex(real, imag)  a+bj  z.real  z.imag  abs(z)  z.conjugate()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#complex",
      "version": "",
      "section": "Типы данных",
      "subcat": "числа",
      "color_group": "op",
      "aliases": [
        "комплексные числа",
        "мнимая часть числа"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "z = 3 + 4j; print(z)           # → (3+4j)",
        "print(complex(3, 4))            # → (3+4j)",
        "print(z.real, z.imag)           # → 3.0 4.0",
        "print(abs(z))                   # → 5.0",
        "print((1+2j) * (3+4j))          # → (-5+10j)",
        "print(z.conjugate())            # → (3-4j)",
        "print(complex(\"3+4j\"))          # → (3+4j) (разбор строки)"
      ],
      "related": [
        "float",
        "complex.conjugate",
        "cmath"
      ],
      "related_errors": []
    },
    {
      "id": "ellipsis-...",
      "title": "Ellipsis ...",
      "kind": "term",
      "summary": {
        "ru": "Специальный синглтон Ellipsis (литерал ...). Используется как заглушка тела функции/класса, в аннотациях типов (Callable[..., int]), в NumPy для многомерных срезов.",
        "en": "The special singleton Ellipsis (written as the literal ...). Used as a placeholder body of a function or class, in type annotations (Callable[..., int]) and in NumPy for multi-dimensional slices."
      },
      "body": {
        "ru": "... — это полноценный объект-синглтон (сравнивай через is), а не пустышка: pass — оператор, а ... — выражение, возвращающее сам Ellipsis. Удобен как уникальный сентинел, когда None — допустимое значение и не годится в роли «ничего не передано».",
        "en": "... is a real singleton object (compare it with is), not a no-op: pass is a statement, whereas ... is an expression that evaluates to the Ellipsis object. It's handy as a unique sentinel when None is itself a valid value and can't stand for \"nothing passed\"."
      },
      "syntax": "...  Ellipsis  type(...) -> ellipsis",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/constants.html#Ellipsis",
      "version": "",
      "section": "Типы данных",
      "subcat": "специальные",
      "color_group": "op",
      "aliases": [
        "многоточие",
        "три точки",
        "заглушка вместо кода"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(...)                # → Ellipsis",
        "print(... is Ellipsis)    # → True",
        "def stub(): ...           # заглушка тела вместо pass",
        "print(type(...))          # → <class 'ellipsis'>",
        "x = ...; print(x == Ellipsis)  # → True",
        "print(bool(...))          # → True"
      ],
      "related": [
        "nonetype",
        "callable-arg-ret",
        "protocol"
      ],
      "related_errors": []
    },
    {
      "id": "float",
      "title": "float",
      "kind": "term",
      "summary": {
        "ru": "Тип числа с плавающей точкой двойной точности (64 бит, IEEE 754). Принимает числа, строки и специальные значения: 'inf', '-inf', 'nan'.",
        "en": "Double-precision floating-point number type (64-bit, IEEE 754). Accepts numbers, strings and the special values 'inf', '-inf' and 'nan'."
      },
      "body": {
        "ru": "Из-за двоичного представления сравнивать float через == опасно (0.1 + 0.2 != 0.3) — используй math.isclose. Особый случай — nan: он не равен ничему, включая самого себя, поэтому x == x для nan даёт False, а проверять надо через math.isnan(x).",
        "en": "Because of binary representation, comparing floats with == is unreliable (0.1 + 0.2 != 0.3) — use math.isclose. The special value nan equals nothing, not even itself, so x == x is False for a nan — test it with math.isnan(x) instead."
      },
      "syntax": "float([x])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#float",
      "version": "",
      "section": "Типы данных",
      "subcat": "числа",
      "color_group": "op",
      "aliases": [
        "дробное число",
        "число с плавающей точкой",
        "вещественное число"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "x = 3.14  # литерал float",
        "y = 1e6  # 1000000.0 (экспоненциальная запись)",
        "z = float(\"2.71\")  # из строки → 2.71",
        "print(round(0.1 + 0.2, 10))  # → 0.3 (ошибка точности IEEE 754)",
        "import math; print(math.inf)  # → inf",
        "print(float(\"nan\"))  # → nan",
        "print(0.1 + 0.2 == 0.3)  # → False (проблема точности float)",
        "print(float(42))  # → 42.0 (int → float)"
      ],
      "related": [
        "int",
        "decimal.decimal",
        "math.isclose",
        "float.is_integer"
      ],
      "related_errors": []
    },
    {
      "id": "int",
      "title": "int",
      "kind": "term",
      "summary": {
        "ru": "Целое число произвольной точности. Принимает строку с основанием системы счисления: int('ff', 16) → 255.",
        "en": "Arbitrary-precision integer. Accepts a string together with its numeric base: int('ff', 16) → 255."
      },
      "body": {
        "ru": "Деление / всегда возвращает float (5 / 2 → 2.5) — для целого результата бери //. Ещё тонкость: bool — подкласс int, поэтому True == 1, isinstance(True, int) истинно, а True + True даёт 2.",
        "en": "The / operator always returns a float (5 / 2 → 2.5) — use // when you want an integer result. Also note that bool is a subclass of int, so True == 1, isinstance(True, int) is True, and True + True evaluates to 2."
      },
      "syntax": "int([x]) | int(x, base)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#int",
      "version": "",
      "section": "Типы данных",
      "subcat": "числа",
      "color_group": "op",
      "aliases": [
        "целое число",
        "целочисленный тип"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "x = 42  # обычное целое",
        "y = -7  # отрицательное",
        "n = int(\"123\")  # из строки → 123",
        "b = 0b1010  # двоичный → 10",
        "h = 0xFF  # шестнадцатеричный → 255",
        "big = 10 ** 100  # Python поддерживает big int без ограничений",
        "print(int(\"FF\", 16))  # → 255 (из строки с основанием)",
        "print(int(3.99))  # → 3 (усечение, а не округление)"
      ],
      "related": [
        "float",
        "преобразование-типов",
        "системы-счисления"
      ],
      "related_errors": []
    },
    {
      "id": "isinstance",
      "title": "isinstance()",
      "kind": "function",
      "summary": {
        "ru": "Встроенная функция проверки принадлежности объекта к типу или кортежу типов. В отличие от type(), учитывает наследование.",
        "en": "Built-in function that checks whether an object belongs to a type or to a tuple of types. Unlike type(), it honors inheritance."
      },
      "body": {
        "ru": "Главная ловушка — bool считается int: isinstance(True, int) возвращает True, потому что bool наследуется от int. Если нужен ровно один тип без учёта наследников, сравнивай type(x) is int; но в идиоматичном Python явных проверок типа обычно избегают в пользу утиной типизации.",
        "en": "The main trap is that bool counts as int: isinstance(True, int) returns True because bool subclasses int. When you need one exact type ignoring subclasses, compare type(x) is int; but idiomatic Python usually avoids explicit type checks in favour of duck typing."
      },
      "syntax": "isinstance(object, classinfo) -> bool",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#isinstance",
      "version": "",
      "section": "Типы данных",
      "subcat": "интроспекция",
      "color_group": "op",
      "aliases": [
        "проверить тип переменной",
        "проверка типа объекта",
        "принадлежность к классу"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "print(isinstance(42, int))  # → True",
        "print(isinstance(3.14, float))  # → True",
        "print(isinstance(True, int))  # → True (bool — подкласс int)",
        "print(isinstance(\"hi\", (int, str)))  # → True (кортеж типов)",
        "print(isinstance([], list))  # → True",
        "def add(a, b):",
        "    if not isinstance(a, (int, float)):",
        "        raise TypeError(\"Нужно число\")",
        "    return a + b",
        "print(add(2, 3))  # → 5"
      ],
      "related": [
        "type",
        "issubclass",
        "hasattr"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "nonetype",
      "title": "NoneType",
      "kind": "term",
      "summary": {
        "ru": "Тип единственного объекта None — отсутствие значения. Используется как значение по умолчанию, возвращаемое значение функций без return, и sentinel.",
        "en": "The type of the single object None — the absence of a value. Used as a default value, as the return value of functions without a return, and as a sentinel."
      },
      "body": {
        "ru": "Сравнивай только через is / is not, а не ==: None — единственный объект своего типа, а == можно переопределить в чужом классе и получить сюрприз. Идиома name or \"Гость\" тоже подводит — она заменяет любое ложное значение (0, \"\", [], False), а не только None; для «только None» пиши name if name is not None else \"Гость\".",
        "en": "Compare with is / is not, never ==: None is the sole instance of its type, and == can be overridden by some class and surprise you. The name or \"Guest\" idiom is also a trap — it replaces any falsy value (0, \"\", [], False), not just None; for a None-only check use name if name is not None else \"Guest\"."
      },
      "syntax": "None",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#the-null-object",
      "version": "",
      "section": "Типы данных",
      "subcat": "none",
      "color_group": "op",
      "aliases": [
        "пустое значение",
        "отсутствие значения",
        "ничего не возвращает"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "x = None  # явное присваивание",
        "print(x is None)  # → True (правильная проверка на None)",
        "def f(): pass",
        "print(f())  # → None (функция без return возвращает None)",
        "def greet(name=None):",
        "    name = name or \"Гость\"",
        "    return f\"Привет, {name}\"",
        "print(greet())  # → Привет, Гость"
      ],
      "related": [
        "is-is-not",
        "optional-x-x-none",
        "параметры-по-умолчанию",
        "return"
      ],
      "related_errors": []
    },
    {
      "id": "str",
      "title": "str",
      "kind": "term",
      "summary": {
        "ru": "Неизменяемая последовательность символов Unicode. Поддерживает богатый набор методов форматирования, поиска и преобразования.",
        "en": "An immutable sequence of Unicode characters. Provides a rich set of formatting, searching and conversion methods."
      },
      "body": {
        "ru": "Строки неизменяемы, поэтому методы вроде upper(), replace(), strip() не меняют строку на месте, а возвращают новую — результат надо присвоить, иначе он потеряется. По той же причине склейка через += в цикле квадратична по времени: собирай куски в список и объединяй одним \"\".join().",
        "en": "Strings are immutable, so methods like upper(), replace() and strip() don't change the string in place but return a new one — you must assign the result or it's lost. For the same reason, building a string with += in a loop is quadratic; collect the pieces in a list and join them once with \"\".join()."
      },
      "syntax": "str([object]) | \"текст\" | 'текст'",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#func-str",
      "version": "",
      "section": "Типы данных",
      "subcat": "строки",
      "color_group": "op",
      "aliases": [
        "строка",
        "строковый тип",
        "текстовые данные"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "greeting = \"Привет\"  # строковый литерал",
        "full = \"Иван\" + \" \" + \"Иванов\"  # конкатенация → Иван Иванов",
        "line = \"-\" * 20  # умножение → --------------------",
        "print(len(\"Python\"))  # → 6",
        "print(\"Py\" in \"Python\")  # → True",
        "print(\"Python\"[0])  # → P (индексирование)",
        "print(\"Python\"[1:4])  # → yth (срез)",
        "print(str(42), str(3.14), str(None))  # → 42 3.14 None (приведение к строке)",
        "print(repr(str(42)))  # → '42' (результат — строка, а не число)"
      ],
      "related": [
        "создание-строк",
        "срезы-строк",
        "f-строки",
        "bytes"
      ],
      "related_errors": []
    },
    {
      "id": "type",
      "title": "type()",
      "kind": "function",
      "summary": {
        "ru": "Встроенная функция, возвращающая тип объекта. С одним аргументом возвращает тип; с тремя — создаёт новый класс динамически.",
        "en": "Built-in function that returns the type of an object. With one argument it returns the type; with three it creates a new class dynamically."
      },
      "body": {
        "ru": "Для проверки типа обычно нужен isinstance, а не type(x) == C: type() возвращает ровно класс объекта и игнорирует наследование, поэтому type(True) — это bool, а не int. Форма с тремя аргументами — низкоуровневый конструктор классов (то, что вызывает metaclass), в обычном коде почти не встречается.",
        "en": "For type checks you usually want isinstance rather than type(x) == C: type() returns the object's exact class and ignores inheritance, so type(True) is bool, not int. The three-argument form is the low-level class constructor (what a metaclass invokes) and rarely appears in ordinary code."
      },
      "syntax": "type(object) -> type\ntype(name, bases, dict) -> type",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#type",
      "version": "",
      "section": "Типы данных",
      "subcat": "интроспекция",
      "color_group": "op",
      "aliases": [
        "узнать тип объекта",
        "какой тип у переменной",
        "определить тип данных"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "print(type(42))  # → <class 'int'>",
        "print(type(3.14))  # → <class 'float'>",
        "print(type(\"hi\"))  # → <class 'str'>",
        "print(type(True))  # → <class 'bool'>",
        "print(type(None))  # → <class 'NoneType'>",
        "x = 42",
        "print(type(x) == int)  # → True (сравнение типов)",
        "print(type(True) == type(1))  # → False (bool != int при type())",
        "A = type(\"A\", (), {\"x\": 42})  # три аргумента — динамическое создание класса",
        "print(A.x)  # → 42"
      ],
      "related": [
        "isinstance",
        "issubclass",
        "class"
      ],
      "related_errors": []
    },
    {
      "id": "преобразование-типов",
      "title": "Преобразование типов",
      "kind": "term",
      "summary": {
        "ru": "Явное приведение одного типа к другому с помощью встроенных функций int(), float(), str(), bool(). При невозможной конвертации возникает ValueError или TypeError.",
        "en": "Explicit conversion of one type into another with the built-in int(), float(), str() and bool(). A conversion that is not possible raises ValueError or TypeError."
      },
      "body": {
        "ru": "bool() коварнее всего: любая непустая строка истинна, поэтому bool(\"False\") и bool(\"0\") — это True. А int() от float отбрасывает дробь в сторону нуля (int(-3.9) → -3, не округление), тогда как int(\"3.9\") сразу падает с ValueError — строку с точкой сначала надо прогнать через float().",
        "en": "bool() is the sneakiest: any non-empty string is truthy, so bool(\"False\") and bool(\"0\") are both True. And int() of a float truncates toward zero (int(-3.9) → -3, not rounding), while int(\"3.9\") raises ValueError outright — a decimal string must go through float() first."
      },
      "syntax": "int(x) | float(x) | str(x) | bool(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#int",
      "version": "",
      "section": "Типы данных",
      "subcat": "преобразование",
      "color_group": "op",
      "aliases": [
        "приведение типов",
        "преобразовать строку в число",
        "конвертация типов"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(int(3.9))  # → 3 (отбрасывание дробной части)",
        "print(int(\"42\"))  # → 42",
        "print(float(7))  # → 7.0",
        "print(str(100))  # → \"100\"",
        "print(bool(0))  # → False",
        "print(bool(\"\"))  # → False",
        "try:",
        "    int(\"3.14\")",
        "except ValueError as e:",
        "    print(type(e).__name__)  # → ValueError (нельзя напрямую)",
        "print(int(float(\"3.14\")))  # → 3 (через float)"
      ],
      "related": [
        "int",
        "float",
        "str",
        "bool"
      ],
      "related_errors": []
    },
    {
      "id": "and-or-not",
      "title": "and, or, not",
      "kind": "construct",
      "summary": {
        "ru": "Логические операторы. and — оба истинны; or — хотя бы одно истинно; not — отрицание. Используют короткое замыкание (lazy evaluation).",
        "en": "The logical operators. and — both are true; or — at least one is true; not — negation. They short-circuit (lazy evaluation)."
      },
      "body": {
        "ru": "Неочевидное: and и or возвращают не True/False, а сам операнд — \"a or b\" даёт a, если оно истинно, иначе b; отсюда идиома значения по умолчанию (name or \"гость\"). Чистый bool из этой тройки гарантирует только not.",
        "en": "The non-obvious part: and and or return one of the operands, not True/False — \"a or b\" yields a when a is truthy, otherwise b, which is why \"name or 'guest'\" is the classic default-value idiom. Only not is guaranteed to give a clean bool."
      },
      "syntax": "a and b | a or b | not a",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not",
      "version": "",
      "section": "Условный оператор",
      "subcat": "логика",
      "color_group": "op",
      "aliases": [
        "логические операторы",
        "логическое и",
        "отрицание условия"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(True and False)  # → False",
        "print(True or False)  # → True",
        "print(not True)  # → False",
        "age, has_id = 20, True",
        "print(age >= 18 and has_id)  # → True",
        "x = 5",
        "print(x < 0 or x > 3)  # → True",
        "print(1 < x < 10)  # → True (цепочка = неявный and)",
        "name = \"\" or \"Гость\"",
        "print(name)  # → Гость (короткое замыкание or)",
        "val = None",
        "safe = val and val.strip()  # → None (не вызывает ошибку из-за and)"
      ],
      "related": [
        "операторы-сравнения",
        "bool",
        "побитовые-операторы",
        "if"
      ],
      "related_errors": []
    },
    {
      "id": "if",
      "title": "if",
      "kind": "construct",
      "summary": {
        "ru": "Оператор условного ветвления. Выполняет блок кода, если условие истинно. Условие — любое выражение, которое Python приводит к bool.",
        "en": "The conditional statement. It runs a block of code if the condition is true. The condition is any expression Python converts to a bool."
      },
      "body": {
        "ru": "Условие — не обязательно сравнение: пустой список, 0, None и \"\" сами по себе ложны, поэтому \"if lst:\" читается как \"список непуст\". Из-за этого \"if x:\" и \"if x is not None:\" — разные проверки: 0 или пустая строка провалят первую, но пройдут вторую.",
        "en": "The condition need not be a comparison: an empty list, 0, None and \"\" are all falsy on their own, so \"if lst:\" reads as \"the list is non-empty\". Because of that, \"if x:\" and \"if x is not None:\" differ — 0 or an empty string fail the first but pass the second."
      },
      "syntax": "if условие:\n    блок",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#if",
      "version": "",
      "section": "Условный оператор",
      "subcat": "ветвление",
      "color_group": "op",
      "aliases": [
        "если",
        "проверка условия",
        "условный оператор"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "x = 5",
        "if x > 0:",
        "    print(\"положительное\")  # → положительное",
        "    if \"Python\" in \"Я учу Python\":",
        "        print(\"нашли\")  # → нашли",
        "        lst = [1, 2, 3]",
        "    if lst:",
        "        print(\"список не пуст\")  # → список не пуст",
        "        name = None",
        "    if name is None:",
        "        name = \"Гость\"  # name → \"Гость\"",
        "        flag = True",
        "    if flag:",
        "        print(\"флаг установлен\")  # → флаг установлен",
        "if x > 0:",
        "    if x < 10:",
        "        print(\"однозначное\")  # → однозначное"
      ],
      "related": [
        "if-else",
        "if-elif-else",
        "операторы-сравнения",
        "тернарный-оператор"
      ],
      "related_errors": []
    },
    {
      "id": "if-elif-else",
      "title": "if-elif-else",
      "kind": "construct",
      "summary": {
        "ru": "Каскадный оператор выбора из нескольких вариантов. Проверяет условия последовательно, выполняет первый истинный блок.",
        "en": "A cascade that picks one of several branches. It tests the conditions in turn and runs the first block whose condition is true."
      },
      "body": {
        "ru": "Проверка идёт сверху вниз и останавливается на первом истинном условии — остальные даже не вычисляются, поэтому порядок веток важен. Ставьте более узкие условия раньше: если написать elif score >= 60 до elif score >= 90, широкая ветка перехватит всё и строгая никогда не сработает.",
        "en": "The checks run top to bottom and stop at the first true condition — the rest aren't even evaluated, so branch order matters. Put stricter conditions first: if \"score >= 60\" comes before \"score >= 90\", the broad branch swallows everything and the strict one never fires."
      },
      "syntax": "if условие1:\n    ...\nelif условие2:\n    ...\nelse:\n    ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#the-if-statement",
      "version": "",
      "section": "Условный оператор",
      "subcat": "ветвление",
      "color_group": "op",
      "aliases": [
        "иначе если",
        "несколько условий подряд",
        "цепочка условий"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "score = 75",
        "if score >= 90:",
        "    grade = \"A\"",
        "elif score >= 75:",
        "    grade = \"B\"",
        "elif score >= 60:",
        "    grade = \"C\"",
        "else:",
        "    grade = \"F\"",
        "    print(grade)  # → B",
        "    hour = 14",
        "    if hour < 12:",
        "        print(\"утро\")",
        "    elif hour < 18:",
        "        print(\"день\")  # → день",
        "    else:",
        "        print(\"вечер\")"
      ],
      "related": [
        "if-else",
        "match-case",
        "вложенные-условия",
        "if"
      ],
      "related_errors": []
    },
    {
      "id": "if-else",
      "title": "if-else",
      "kind": "construct",
      "summary": {
        "ru": "Двухветочный оператор условия. Если условие истинно — выполняется if-блок, иначе — else-блок.",
        "en": "A two-branch conditional. If the condition is true the if block runs, otherwise the else block."
      },
      "body": {
        "ru": "Когда обе ветки лишь присваивают одной переменной, компактнее тернарное условное выражение вида \"A if условие else B\" — тот же выбор в одну строку вместо четырёх строк if-else. Полный if-else оставляйте для веток, где несколько действий.",
        "en": "When both branches just assign to one variable, the ternary conditional expression \"A if condition else B\" is tighter — the same choice in one line instead of a four-line if-else. Keep the full if-else for branches that do several things."
      },
      "syntax": "if условие:\n    блок1\nelse:\n    блок2",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#the-if-statement",
      "version": "",
      "section": "Условный оператор",
      "subcat": "ветвление",
      "color_group": "op",
      "aliases": [
        "иначе",
        "две ветви условия"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "x = -3",
        "if x >= 0:",
        "    print(\"неотрицательное\")",
        "else:",
        "    print(\"отрицательное\")  # → отрицательное",
        "    n = 7",
        "    if n % 2 == 0:",
        "        print(\"чётное\")",
        "    else:",
        "        print(\"нечётное\")  # → нечётное",
        "        s = \"\"",
        "    if s:",
        "        print(\"не пусто\")",
        "    else:",
        "        print(\"пусто\")  # → пусто",
        "        val = None",
        "        result = val if val is not None else 0",
        "        print(result)  # → 0"
      ],
      "related": [
        "if",
        "if-elif-else",
        "тернарный-оператор",
        "and-or-not"
      ],
      "related_errors": []
    },
    {
      "id": "in",
      "title": "in",
      "kind": "construct",
      "summary": {
        "ru": "Оператор принадлежности. Проверяет, содержится ли элемент в коллекции. Работает со строками, списками, кортежами, множествами, словарями (по ключам).",
        "en": "The membership operator. It checks whether an item is contained in a collection. Works with strings, lists, tuples, sets and dictionaries (by key)."
      },
      "body": {
        "ru": "Скрытая цена: в списке, кортеже и строке in — это линейный перебор O(n), а в множестве и словаре — почти мгновенный поиск по хешу O(1). Если постоянно проверяете принадлежность в большой коллекции, держите её множеством, а не списком.",
        "en": "The hidden cost: on a list, tuple or string, in is a linear O(n) scan, whereas on a set or dict it's a near-instant O(1) hash lookup. If you keep testing membership against a large collection, store it as a set, not a list."
      },
      "syntax": "элемент in коллекция -> bool",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#in",
      "version": "3.8",
      "section": "Условный оператор",
      "subcat": "принадлежность",
      "color_group": "op",
      "aliases": [
        "проверка вхождения",
        "содержится ли элемент",
        "оператор принадлежности"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(\"a\" in \"banana\")  # → True",
        "print(3 in [1, 2, 3, 4])  # → True",
        "print(5 not in [1, 2, 3])  # → True",
        "d = {\"a\": 1, \"b\": 2}",
        "print(\"a\" in d)  # → True (по ключам)",
        "print(1 in d.values())  # → True (по значениям)",
        "print(\"py\" in \"python\")  # → True (подстрока)",
        "print(7 in {1, 3, 7, 9})  # → True (множество)"
      ],
      "related": [
        "in-not-in-для-строк",
        "in-not-in-для-списков",
        "in-not-in-для-словаря",
        "in-not-in-для-множеств-o-1"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "is-is-not",
      "title": "is / is not",
      "kind": "construct",
      "summary": {
        "ru": "Операторы тождества. Проверяют, указывают ли две переменные на ОДИН объект в памяти. Используйте is/is not для проверки None, True, False — не ==.",
        "en": "The identity operators. They check whether two variables point at ONE object in memory. Use is/is not to test for None, True and False — not ==."
      },
      "body": {
        "ru": "CPython заранее кеширует маленькие целые (примерно от -5 до 256) и некоторые строки, поэтому is для них может «случайно» вернуть True — а для больших чисел та же проверка внезапно даст False. Отсюда правило: is только для None и других синглтонов, а обычные значения (числа, строки, списки) сравнивайте через ==.",
        "en": "CPython pre-caches small integers (roughly -5 to 256) and some strings, so is may return True for them by accident — while the same check on larger numbers suddenly gives False. Hence the rule: use is only for None and other singletons, and compare ordinary values (numbers, strings, lists) with ==."
      },
      "syntax": "a is b | a is not b",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#identity-comparisons",
      "version": "3.8",
      "section": "Условный оператор",
      "subcat": "тождество",
      "color_group": "op",
      "aliases": [
        "сравнение по ссылке",
        "тождественность объектов",
        "один и тот же объект в памяти"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(None is None)  # → True",
        "x = None",
        "print(x is None)  # → True (правильная проверка)",
        "print(x == None)  # → True (работает, но стиль PEP8 — is)",
        "a = [1, 2]; b = a",
        "print(a is b)  # → True (одна ссылка)",
        "c = [1, 2]",
        "print(a is c)  # → False (разные объекты с одинаковым значением)",
        "print(a == c)  # → True (равные по значению)"
      ],
      "related": [
        "операторы-сравнения",
        "nonetype",
        "id"
      ],
      "related_errors": []
    },
    {
      "id": "match-case",
      "title": "match / case",
      "kind": "construct",
      "summary": {
        "ru": "Structural Pattern Matching (Python 3.10+). Позволяет сопоставлять значение с образцами: литералами, типами, структурами. case _ — универсальный паттерн (аналог else). Guard (if) добавляет дополнительное условие в ветку case.",
        "en": "Structural pattern matching (Python 3.10+). It matches a value against patterns: literals, types, structures. case _ is the catch-all pattern (the counterpart of else). A guard (if) adds an extra condition to a case branch."
      },
      "body": {
        "ru": "Главная ловушка: одиночное имя в case — это не сравнение с переменной, а захват: оно всегда совпадает и связывает значение с этим именем. Чтобы сверять с константой, нужен литерал или имя через точку (например, Status.OK), иначе такой case перехватит вообще всё. Работает только с Python 3.10 и новее.",
        "en": "The main trap: a bare name in a case does not compare against an existing variable — it captures, always matching and binding the value to that name. To match a constant you need a literal or a dotted name (for example Status.OK), otherwise that case swallows everything. Available only in Python 3.10 and later."
      },
      "syntax": "match subject:\n    case pattern1: ...\n    case pattern2 if guard: ...\n    case _: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#the-match-statement",
      "version": "3.10",
      "section": "Условный оператор",
      "subcat": "pattern matching",
      "color_group": "op",
      "aliases": [
        "сопоставление с образцом",
        "структурное сопоставление",
        "свитч"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "match \"quit\":",
        "case \"quit\": print(\"выход\")   # → выход",
        "case \"help\": print(\"помощь\")  # → помощь",
        "case _:      print(\"неизвестно\")",
        "point = (0, 1)",
        "match point:",
        "case (0, 0): print(\"начало\")",
        "case (0, y): print(f\"ось Y, y={y}\")  # → ось Y, y=1",
        "case (x, y) if x == y: print(\"диагональ\")",
        "case _: print(\"общий\")",
        "match 42:",
        "case int(n) if n > 10: print(f\"большое {n}\")  # → большое 42",
        "case _: print(\"другое\")"
      ],
      "related": [
        "if-elif-else",
        "распаковка-кортежа",
        "isinstance"
      ],
      "related_errors": []
    },
    {
      "id": "вложенные-условия",
      "title": "Вложенные условия",
      "kind": "construct",
      "summary": {
        "ru": "Оператор if внутри другого if. Позволяет проверять комбинации условий. Следует использовать осторожно — глубокая вложенность снижает читаемость.",
        "en": "An if inside another if. It lets you test combinations of conditions. Use it carefully — deep nesting hurts readability."
      },
      "body": {
        "ru": "Чаще всего вложенность — сигнал упростить: два if подряд без else сливаются в один через and, а глубокие ветки разворачиваются ранним return или continue, убирающим лишние уровни. Это тот случай, когда плоский код почти всегда читается лучше вложенного.",
        "en": "Nesting is often a hint to simplify: two ifs in a row without an else collapse into one via and, and deep branches flatten out with an early return or continue that strips the extra levels. This is a case where flat code almost always reads better than nested."
      },
      "syntax": "if условие1:\n    if условие2:\n        блок",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#the-if-statement",
      "version": "",
      "section": "Условный оператор",
      "subcat": "ветвление",
      "color_group": "op",
      "aliases": [
        "условие внутри условия",
        "вложенность проверок"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "x = 15",
        "if x > 0:",
        "if x % 2 == 0:",
        "print(\"положительное чётное\")",
        "else:",
        "print(\"положительное нечётное\")  # → положительное нечётное",
        "age, has_ticket = 20, True",
        "if age >= 18:",
        "if has_ticket:",
        "print(\"вход разрешён\")  # → вход разрешён",
        "n = 100",
        "if n >= 10:",
        "if n <= 999:",
        "print(\"двух/трёхзначное\")  # → двух/трёхзначное",
        "a, b = 5, 10",
        "if a > 0:",
        "if b > 0:",
        "if a + b > 12:",
        "print(\"сумма > 12\")  # → сумма > 12"
      ],
      "related": [
        "if-elif-else",
        "and-or-not",
        "if"
      ],
      "related_errors": []
    },
    {
      "id": "операторы-сравнения",
      "title": "Операторы сравнения",
      "kind": "construct",
      "summary": {
        "ru": "Операторы ==, !=, >, <, >=, <= сравнивают два значения и возвращают bool. Python поддерживает цепочки сравнений: a < b < c.",
        "en": "The operators ==, !=, >, <, >= and <= compare two values and return a bool. Python also supports chained comparisons: a < b < c."
      },
      "body": {
        "ru": "Главная засада — сравнивать дробные числа через ==: из-за двоичного округления 0.1 + 0.2 не равно 0.3, поэтому для float берите math.isclose. В цепочке a < b < c средний операнд вычисляется один раз, а проверка обрывается на первом же ложном звене.",
        "en": "The main pitfall is comparing floats with ==: because of binary rounding, 0.1 + 0.2 is not equal to 0.3, so reach for math.isclose with floats. In a chain like a < b < c the middle operand is evaluated only once, and the check stops at the first false link."
      },
      "syntax": "a == b | a != b | a > b | a < b | a >= b | a <= b",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#comparisons",
      "version": "3.8",
      "section": "Условный оператор",
      "subcat": "операторы",
      "color_group": "op",
      "aliases": [
        "сравнение значений",
        "больше меньше равно",
        "проверка на равенство"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(5 == 5)  # → True",
        "print(5 != 3)  # → True",
        "print(7 > 3)  # → True",
        "print(2 < 1)  # → False",
        "print(5 >= 5)  # → True",
        "print(\"abc\" < \"abd\")  # → True (лексикографически)",
        "x = 5",
        "print(1 < x < 10)  # → True (цепочка сравнений)",
        "print(1 < x < 4)  # → False"
      ],
      "related": [
        "is-is-not",
        "цепочка-сравнений",
        "and-or-not",
        "bool"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "тернарный-оператор",
      "title": "Тернарный оператор",
      "kind": "construct",
      "summary": {
        "ru": "Компактная форма if-else в одну строку. Возвращает одно из двух значений в зависимости от условия. Читается: «значение_если_истина if условие else значение_если_ложь».",
        "en": "The compact one-line form of if-else. It returns one of two values depending on the condition. Read it as: value_if_true if condition else value_if_false."
      },
      "body": {
        "ru": "В отличие от обычного if, это выражение — оно возвращает значение, поэтому его можно вставить прямо туда, где оператор недопустим: в аргумент функции, в f-строку, в тело генератора. Порядок необычный (сначала результат, потом условие), а вложенные тернары ради «elif» лучше не плодить — читаемость падает быстро.",
        "en": "Unlike a regular if, this is an expression — it returns a value, so you can drop it right where a statement isn't allowed: a function argument, an f-string, a comprehension body. The order is unusual (result first, condition second), and stacking nested ternaries to fake an elif quickly wrecks readability."
      },
      "syntax": "значение_если_истина if условие else значение_если_ложь",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#conditional-expressions",
      "version": "3.5",
      "section": "Условный оператор",
      "subcat": "ветвление",
      "color_group": "op",
      "aliases": [
        "условие в одну строку",
        "однострочное ветвление",
        "краткая форма условия"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "x = 7",
        "result = \"чётное\" if x % 2 == 0 else \"нечётное\"",
        "print(result)  # → нечётное",
        "a, b = 3, 9",
        "bigger = a if a > b else b",
        "print(bigger)  # → 9",
        "n = -5",
        "abs_n = n if n >= 0 else -n",
        "print(abs_n)  # → 5",
        "age = 20",
        "status = \"совершеннолетний\" if age >= 18 else \"несовершеннолетний\"",
        "print(status)  # → совершеннолетний",
        "lst = [1, 2, 3]",
        "print(\"есть\" if lst else \"пусто\")  # → есть",
        "name = None",
        "print(name if name else \"Гость\")  # → Гость"
      ],
      "related": [
        "if-else",
        "условный-list-comprehension",
        "if"
      ],
      "related_errors": []
    },
    {
      "id": "csv",
      "title": "csv",
      "kind": "term",
      "summary": {
        "ru": "Модуль csv — чтение и запись CSV-файлов. reader/writer для строк, DictReader/DictWriter для словарей.",
        "en": "The csv module reads and writes CSV files. reader/writer work with rows, DictReader/DictWriter with dictionaries."
      },
      "body": {
        "ru": "Файл под csv открывают с newline='' — модуль сам управляет переводами строк, и без этого на Windows между записями появятся пустые строки. reader отдаёт все поля строками: '30' останется текстом, приводить к int или float нужно вручную. DictReader берёт имена полей из первой строки файла — если заголовка нет, передайте fieldnames явно, иначе первая запись данных молча станет заголовком.",
        "en": "Open the file with newline='' — the module handles line endings itself, and without it Windows gives you a blank line between every record. reader hands back every field as a string: '30' stays text until you convert it yourself. DictReader takes the field names from the first line, so if the file has no header row, pass fieldnames explicitly or your first data record silently becomes the header."
      },
      "syntax": "import csv\ncsv.reader(f)\ncsv.writer(f)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#open",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "csv",
      "color_group": "op",
      "aliases": [
        "чтение таблицы из файла",
        "файл с разделителями"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "import csv, io",
        "# writer",
        "output = io.StringIO()",
        "writer = csv.writer(output)",
        "writer.writerow(['name','age','city'])",
        "writer.writerow(['Alice',30,'Moscow'])",
        "writer.writerow(['Bob',25,'SPb'])",
        "print(output.getvalue())  # → CSV строки",
        "# reader",
        "output.seek(0)",
        "reader = csv.reader(output)",
        "for row in reader:",
        "    print(row)",
        "    # DictWriter",
        "    output2 = io.StringIO()",
        "    fields = ['name','age']",
        "    w = csv.DictWriter(output2, fieldnames=fields)",
        "    w.writeheader()",
        "    w.writerow({'name':'Alice','age':30})",
        "    print(output2.getvalue())",
        "    # DictReader",
        "    output2.seek(0)",
        "    for row in csv.DictReader(output2):",
        "        print(row)  # → OrderedDict/dict",
        "# Запись в файл",
        "with open('data.csv','w',newline='',encoding='utf-8') as f:",
        "    w = csv.writer(f)",
        "    w.writerows([['a',1],['b',2]])",
        "    # Чтение с другим разделителем",
        "    data = 'a;b;c\\n1;2;3'",
        "    for row in csv.reader(io.StringIO(data), delimiter=';'):",
        "        print(row)  # → ['a','b','c'] / ['1','2','3']",
        "        # Настройка quoting",
        "        output3 = io.StringIO()",
        "        w2 = csv.writer(output3, quoting=csv.QUOTE_ALL)",
        "        w2.writerow(['hello world', '42', 'a,b'])",
        "        print(output3.getvalue())"
      ],
      "related": [
        "json",
        "open",
        "итерация-по-файлу"
      ],
      "related_errors": []
    },
    {
      "id": "file-buffering",
      "title": "buffering=",
      "kind": "term",
      "summary": {
        "ru": "Параметр open(), задающий политику буферизации: 0 — без буфера (только бинарный режим), 1 — построчно, >1 — размер буфера в байтах, -1 — системный default.",
        "en": "The open() parameter that sets the buffering policy: 0 — unbuffered (binary mode only), 1 — line buffered, >1 — the buffer size in bytes, -1 — the system default."
      },
      "body": {
        "ru": "Пока буфер не заполнен, данные лежат в памяти, а не в файле: если параллельно смотреть лог другой программой или процесс аварийно упал, свежих строк там просто не будет — сбрасывает их flush() или close() (в том числе выход из with). buffering=1 нужен именно для таких «живых» логов, но работает только в текстовом режиме: в бинарном Python выдаст предупреждение и возьмёт обычный размер буфера.",
        "en": "Until the buffer fills up the data sits in memory, not in the file: if another program is tailing the log, or the process dies, the latest lines are simply not there — only flush() or close() (including leaving a with block) push them out. buffering=1 exists for exactly that live-log case, but it applies to text mode only: in binary mode Python warns and falls back to the normal buffer size."
      },
      "syntax": "open(file, mode='r', buffering=-1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#open",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "параметры open()",
      "color_group": "module",
      "aliases": [
        "построчная запись в файл",
        "размер буфера при открытии файла"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "# без буфера (binary only)",
        "f = open('raw.bin', 'wb', buffering=0)",
        "# построчная буферизация",
        "f = open('out.txt', 'w', buffering=1)",
        "# буфер 4096 байт",
        "f = open('data.txt', 'r', buffering=4096)"
      ],
      "related": [
        "file-flush",
        "буферизация",
        "open",
        "file-errors"
      ],
      "related_errors": []
    },
    {
      "id": "file-errors",
      "title": "errors=",
      "kind": "term",
      "summary": {
        "ru": "Параметр open(), определяющий реакцию на ошибки кодировки: 'strict' (по умолчанию, исключение), 'ignore', 'replace', 'backslashreplace'.",
        "en": "The open() parameter that decides what happens on an encoding error: 'strict' (the default, raises), 'ignore', 'replace', 'backslashreplace'."
      },
      "body": {
        "ru": "'ignore' и 'replace' не чинят файл, а молча теряют данные: байты либо исчезают, либо превращаются в U+FFFD, и восстановить оригинал уже нельзя — сначала стоит выяснить настоящую кодировку. Если задача «прочитать, поправить, записать обратно», берите errors='surrogateescape': только он даёт точный round-trip непонятных байтов. В бинарном режиме параметр запрещён — там нечего декодировать.",
        "en": "'ignore' and 'replace' do not repair anything, they silently destroy data: bytes either vanish or turn into U+FFFD, and the original is gone for good — figure out the file's real encoding first. When the job is read, edit, write back, use errors='surrogateescape': it is the only one of these that round-trips undecodable bytes intact. The parameter is rejected in binary mode, where nothing is decoded at all."
      },
      "syntax": "open(file, encoding='utf-8', errors='strict')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#open",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "параметры open()",
      "color_group": "module",
      "aliases": [
        "ошибка кодировки при чтении файла",
        "игнорировать битые символы",
        "не удаётся декодировать файл"
      ],
      "keywords": [],
      "tags": [
        "module"
      ],
      "examples": [
        "# игнорировать некорректные байты",
        "with open('broken.txt', encoding='utf-8', errors='ignore') as f:",
        "    text = f.read()",
        "# заменить на ?",
        "with open('mixed.txt', encoding='utf-8', errors='replace') as f:",
        "    text = f.read()"
      ],
      "related": [
        "open",
        "unicodedecodeerror",
        "file-buffering"
      ],
      "related_errors": []
    },
    {
      "id": "file-flush",
      "title": "flush()",
      "kind": "function",
      "summary": {
        "ru": "Принудительно сбрасывает внутренний буфер записи в файл или поток. Полезно, если нужно убедиться, что данные записаны немедленно.",
        "en": "Forces the internal write buffer out to the file or stream. Useful when the data has to be written right away."
      },
      "body": {
        "ru": "Закрытие файла (в том числе выход из блока with) сбрасывает буфер само, поэтому явный flush() нужен редко — в основном когда процесс работает долго и данные должен увидеть кто-то другой: лог в реальном времени, файл, который параллельно читает другая программа. И flush() отдаёт данные операционной системе, а не гарантированно на диск: если важно пережить внезапное выключение питания, после него зовут os.fsync(file.fileno()).",
        "en": "Closing a file (including leaving a with block) flushes it for you, so an explicit flush() is rarely needed — mostly in long-running processes where someone else has to see the data now: a live log, or a file another program is reading. Also note that flush() hands the bytes to the operating system, not necessarily to the physical disk; if the data must survive a power loss, follow it with os.fsync(file.fileno())."
      },
      "syntax": "file.flush()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.IOBase.flush",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "буфер",
      "color_group": "module",
      "aliases": [
        "сбросить буфер записи",
        "принудительная запись в файл",
        "данные не записались в файл"
      ],
      "keywords": [
        "flush"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "import sys",
        "# немедленный вывод без буферизации",
        "print('загрузка...', end='', flush=True)",
        "# или явно:",
        "sys.stdout.write('статус')",
        "sys.stdout.flush()",
        "with open('log.txt', 'w') as f:",
        "    f.write('событие')",
        "    f.flush()  # сразу на диск"
      ],
      "related": [
        "file-buffering",
        "print-file-flush",
        "file.write",
        "буферизация"
      ],
      "related_errors": []
    },
    {
      "id": "file-tell",
      "title": "tell()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает текущую позицию курсора в файле в байтах. Используй вместе с seek() для навигации по файлу.",
        "en": "Returns the current position of the cursor in the file, in bytes. Use it together with seek() to move around a file."
      },
      "body": {
        "ru": "В бинарном режиме tell() отдаёт настоящее смещение в байтах, а в текстовом — непрозрачное число: с ним нельзя делать арифметику, единственное законное применение — вернуть его обратно в seek(). Поэтому, если нужно прыгать на N байт вперёд и считать позиции, файл открывают с 'b'. В текстовом режиме ещё и переводы строк транслируются, так что число из tell() не совпадает с количеством прочитанных символов.",
        "en": "In binary mode tell() gives a genuine byte offset; in text mode it returns an opaque cookie you must not do arithmetic on — the only legal use is handing it back to seek(). If you need to compute positions or jump N bytes ahead, open the file with 'b'. Text mode also translates line endings, so the number from tell() will not match how many characters you have read."
      },
      "syntax": "file.tell() -> int",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.IOBase.tell",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "позиция в файле",
      "color_group": "module",
      "aliases": [
        "позиция курсора в файле",
        "текущее смещение в файле"
      ],
      "keywords": [
        "tell"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "with open('data.txt', 'rb') as f:",
        "    f.read(5)",
        "    pos = f.tell()      # 5",
        "    f.read(3)",
        "    print(f.tell())     # 8",
        "    f.seek(0)           # вернуться в начало",
        "    print(f.tell())     # 0"
      ],
      "related": [
        "file-truncate",
        "file.read",
        "open"
      ],
      "related_errors": []
    },
    {
      "id": "file-truncate",
      "title": "truncate()",
      "kind": "function",
      "summary": {
        "ru": "Обрезает файл до указанного размера в байтах. Если size не указан — обрезает до текущей позиции курсора. Файл должен быть открыт на запись.",
        "en": "Truncates the file to the given size in bytes. With no size it truncates at the current cursor position. The file has to be open for writing."
      },
      "body": {
        "ru": "Главная ловушка: truncate() не двигает курсор. Если в режиме 'r+' прочитать файл целиком, затем сделать f.truncate(0) и сразу писать, запись уйдёт со старой позиции, а начало файла добьётся нулевыми байтами — перед записью нужен f.seek(0). Если size больше текущего размера, файл, наоборот, расширяется нулями, а не даёт ошибку.",
        "en": "The classic trap: truncate() does not move the file position. Open in 'r+', read the whole file, call f.truncate(0) and write immediately, and the write starts at the old offset with the gap padded by null bytes — you need f.seek(0) first. If size is larger than the current length, the file is extended with zero bytes instead of raising."
      },
      "syntax": "file.truncate(size=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.IOBase.truncate",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "изменение размера",
      "color_group": "module",
      "aliases": [
        "обрезать файл",
        "очистить содержимое файла",
        "уменьшить размер файла"
      ],
      "keywords": [
        "truncate"
      ],
      "tags": [
        "module"
      ],
      "examples": [
        "# очистить файл",
        "with open('data.txt', 'r+') as f:",
        "    f.truncate(0)",
        "# обрезать до 100 байт",
        "with open('big.bin', 'r+b') as f:",
        "    f.truncate(100)"
      ],
      "related": [
        "file-tell",
        "file.write",
        "open"
      ],
      "related_errors": []
    },
    {
      "id": "file.read",
      "title": "read()",
      "kind": "function",
      "summary": {
        "ru": "Читает файл целиком в одну строку, либо ровно size символов, если аргумент задан. На большом файле съедает всю память — тогда лучше читать построчно.",
        "en": "Read the whole file into a single string, or exactly size characters if given; on a large file this loads everything into memory — iterate by lines instead."
      },
      "body": {
        "ru": "Курсор назад не отматывается: после f.read() файл прочитан до конца, и второй вызов внутри того же with вернёт пустую строку — самая частая причина «почему-то ничего не прочиталось». Чтобы прочитать заново, нужен f.seek(0). Аргумент size в текстовом режиме считает символы, а в бинарном — байты, и для не-ASCII это разные величины.",
        "en": "The cursor does not rewind: after f.read() the file is exhausted, so a second call inside the same with block returns an empty string — the usual cause of \"my file suddenly reads as empty\". Call f.seek(0) to read it again. The size argument counts characters in text mode but bytes in binary mode, and for non-ASCII data those are not the same thing."
      },
      "syntax": "f.read(size=-1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.TextIOBase.read",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "чтение",
      "color_group": "op",
      "aliases": [
        "прочитать файл целиком",
        "считать содержимое файла в строку"
      ],
      "keywords": [
        "read"
      ],
      "tags": [
        "op"
      ],
      "examples": [
        "with open('demo.txt', 'w') as f:",
        "    f.write('line1\\nline2\\nline3\\n')",
        "with open('demo.txt') as f:",
        "    content = f.read()",
        "print(repr(content))  # → 'line1\\nline2\\nline3\\n'",
        "with open('demo.txt') as f:",
        "    print(f.read(5))  # → line1 (ровно 5 символов)"
      ],
      "related": [
        "file.readline",
        "file.readlines",
        "file.write"
      ],
      "related_errors": []
    },
    {
      "id": "file.readline",
      "title": "readline()",
      "kind": "function",
      "summary": {
        "ru": "Читает одну строку вместе с завершающим '\\n'. По достижении конца файла возвращает пустую строку — по этому признаку и определяют конец.",
        "en": "Read a single line including its trailing '\\n'; at end of file it returns an empty string, which is how EOF is detected."
      },
      "body": {
        "ru": "Пустая строка в середине файла возвращается как '\\n' и остаётся истинной, а '' приходит только на настоящем конце файла — поэтому цикл вида while line := f.readline() не обрывается досрочно на пустой строке. Если файл не заканчивается переводом строки, последняя строка придёт без '\\n', так что rstrip('\\n') надёжнее среза [:-1]. Для прохода по всему файлу проще for line in f — то же самое, но лениво и без ручного условия выхода.",
        "en": "A blank line in the middle of a file comes back as '\\n', which is truthy; only real end of file yields '', so a while line := f.readline() loop will not stop early on an empty line. If the file does not end with a newline, the last line arrives without '\\n', which is why rstrip('\\n') is safer than slicing off [:-1]. To walk the whole file, for line in f does the same job lazily and without a manual exit condition."
      },
      "syntax": "f.readline(size=-1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.IOBase.readline",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "чтение",
      "color_group": "op",
      "aliases": [
        "прочитать одну строку из файла",
        "считать следующую строку файла"
      ],
      "keywords": [
        "readline"
      ],
      "tags": [
        "op"
      ],
      "examples": [
        "with open('demo.txt', 'w') as f:",
        "    f.write('line1\\nline2\\n')",
        "with open('demo.txt') as f:",
        "    first = f.readline()",
        "    second = f.readline()",
        "print(repr(first))   # → 'line1\\n'",
        "print(repr(second))  # → 'line2\\n'"
      ],
      "related": [
        "file.read",
        "file.readlines"
      ],
      "related_errors": []
    },
    {
      "id": "file.readlines",
      "title": "readlines()",
      "kind": "function",
      "summary": {
        "ru": "Читает весь файл в список строк, сохраняя '\\n' на концах. Снять переносы помогает .rstrip() или .splitlines() на результате .read().",
        "en": "Read the whole file into a list of lines, keeping the trailing '\\n'; strip them with .rstrip() or use .splitlines() on the result of .read()."
      },
      "body": {
        "ru": "Для обычного прохода по строкам readlines() не нужен: for line in f делает то же самое лениво, не поднимая весь файл в память и не строя список. Аргумент hint не режет ровно по заданному размеру — чтение идёт целыми строками, пока суммарный объём не превысит hint, поэтому вернуться может чуть больше запрошенного.",
        "en": "For a plain pass over the lines you do not need readlines(): for line in f does the same thing lazily, without pulling the whole file into memory or building a list. The hint argument is not a hard cut — whole lines are read until their combined size exceeds hint, so you can get back somewhat more than you asked for."
      },
      "syntax": "f.readlines(hint=-1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.IOBase.readlines",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "чтение",
      "color_group": "op",
      "aliases": [
        "прочитать файл в список строк",
        "получить список строк файла"
      ],
      "keywords": [
        "readlines"
      ],
      "tags": [
        "op"
      ],
      "examples": [
        "with open('demo.txt', 'w') as f:",
        "    f.write('line1\\nline2\\nline3\\n')",
        "with open('demo.txt') as f:",
        "    lines = f.readlines()",
        "print(lines)  # → ['line1\\n', 'line2\\n', 'line3\\n']",
        "with open('demo.txt') as f:",
        "    print([s.rstrip() for s in f.readlines()])  # → ['line1', 'line2', 'line3']"
      ],
      "related": [
        "file.read",
        "file.readline"
      ],
      "related_errors": []
    },
    {
      "id": "file.write",
      "title": "write()",
      "kind": "function",
      "summary": {
        "ru": "Записывает строку в файл и возвращает число записанных символов. Переносы строк сам не добавляет — '\\n' нужно писать явно.",
        "en": "Write a string to the file and return the number of characters written; it adds no line breaks, so '\\n' must be written explicitly."
      },
      "body": {
        "ru": "В текстовом режиме write() принимает только строку: число или список сначала приводите к str() или подставляйте в f-строку, иначе TypeError; в режиме 'wb' наоборот, нужны bytes. Помните и про режим открытия — 'w' обрезает файл до нуля прямо в момент open(), даже если вы потом ничего не записали; дописывать в конец можно только с 'a'.",
        "en": "In text mode write() accepts a string and nothing else: convert numbers or lists with str() or an f-string first, otherwise you get a TypeError; in 'wb' mode it is the opposite and only bytes are allowed. Watch the file mode too — 'w' truncates the file to zero at the moment of open(), even if you never write anything afterwards; to add to the end you need 'a'."
      },
      "syntax": "f.write(s)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.TextIOBase.write",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "запись",
      "color_group": "op",
      "aliases": [
        "записать в файл",
        "запись строки в файл",
        "сохранить текст в файл"
      ],
      "keywords": [
        "write"
      ],
      "tags": [
        "op"
      ],
      "examples": [
        "with open('out.txt', 'w') as f:",
        "    f.write('Hello')",
        "    f.write('\\nWorld')",
        "with open('out.txt', 'w') as f:",
        "    n = f.write('Hello World')",
        "print(n)  # → 11"
      ],
      "related": [
        "file.writelines",
        "file.read"
      ],
      "related_errors": []
    },
    {
      "id": "file.writelines",
      "title": "writelines()",
      "kind": "function",
      "summary": {
        "ru": "Записывает все строки из итерируемого подряд. Разделители НЕ добавляет — '\\n' должен быть в самих строках, иначе всё склеится в одну.",
        "en": "Write every string from an iterable in sequence; it adds no separators, so '\\n' must already be in the strings or everything runs together."
      },
      "body": {
        "ru": "Имя обманывает: метод ничего не знает про «строки» и просто вызывает write() для каждого элемента подряд. Он парный к readlines(), который сохраняет '\\n' внутри элементов, поэтому f_out.writelines(f_in.readlines()) копирует файл точь-в-точь; а вот после splitlines() или strip() переводы строк придётся дописывать самому. На вход годится любой итерируемый объект — генератор, другой файл — а в бинарном режиме элементы должны быть bytes, иначе TypeError.",
        "en": "The name misleads: the method knows nothing about \"lines\", it just calls write() on each item in turn. It pairs with readlines(), which keeps the '\\n' inside each item, so f_out.writelines(f_in.readlines()) reproduces a file exactly — but after splitlines() or strip() you have to put the newlines back yourself. Any iterable works as input, including a generator or another file object; in binary mode the items must be bytes or you get a TypeError."
      },
      "syntax": "f.writelines(lines)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/io.html#io.IOBase.writelines",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "запись",
      "color_group": "op",
      "aliases": [
        "записать список строк в файл",
        "записать несколько строк сразу"
      ],
      "keywords": [
        "writelines"
      ],
      "tags": [
        "op"
      ],
      "examples": [
        "lines = ['line1\\n', 'line2\\n', 'line3\\n']",
        "with open('out2.txt', 'w') as f:",
        "    f.writelines(lines)",
        "with open('out3.txt', 'w') as f:",
        "    f.writelines(f'{i}\\n' for i in range(5))"
      ],
      "related": [
        "file.write"
      ],
      "related_errors": []
    },
    {
      "id": "json",
      "title": "json",
      "kind": "term",
      "summary": {
        "ru": "Сериализация/десериализация JSON. dumps/loads — строки. dump/load — файлы. Поддерживает dict, list, str, int, float, bool, None.",
        "en": "JSON serialization and deserialization. dumps/loads work with strings, dump/load with files. Supports dict, list, str, int, float, bool and None."
      },
      "body": {
        "ru": "Обратная дорога не тождественна: кортеж после dumps/loads вернётся списком, а нестроковые ключи словаря станут строками — 1 превратится в '1'. По умолчанию ensure_ascii=True, и кириллица уезжает в \\uXXXX-экранирование; для читаемого файла передавайте ensure_ascii=False. Множества, datetime и Decimal модуль не умеет и бросает TypeError — приводите их к поддерживаемым типам сами или через параметр default.",
        "en": "A round trip is not an identity: a tuple comes back as a list, and non-string dict keys come back as strings, so 1 turns into '1'. ensure_ascii is True by default, which escapes anything non-Latin into \\uXXXX — pass ensure_ascii=False when you want a human-readable file. Sets, datetime and Decimal are unsupported and raise TypeError; convert them yourself or hook the default parameter."
      },
      "syntax": "import json\njson.dumps(obj)\njson.loads(s)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#open",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "json",
      "color_group": "op",
      "aliases": [
        "сериализация данных",
        "сохранить словарь в файл"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "import json",
        "data = {'name':'Alice','age':30,'langs':['Python','Rust']}",
        "s = json.dumps(data)",
        "print(s)  # → {\"name\": \"Alice\", \"age\": 30, ...}",
        "# indent для форматирования",
        "print(json.dumps(data, indent=2))  # → красивый JSON",
        "# loads — из строки",
        "parsed = json.loads('{\"x\":1,\"y\":[1,2,3]}')",
        "print(parsed, type(parsed))  # → dict",
        "# dump/load — через файл",
        "with open('data.json','w') as f:",
        "json.dump(data, f, indent=2)",
        "with open('data.json') as f:",
        "loaded = json.load(f)",
        "print(loaded['name'])  # → Alice",
        "# ensure_ascii=False — для кириллицы",
        "d2 = {'имя':'Алиса'}",
        "print(json.dumps(d2, ensure_ascii=False))  # → {\"имя\": \"Алиса\"}",
        "# JSONDecodeError",
        "try:",
        "json.loads('invalid')",
        "except json.JSONDecodeError as e:",
        "print(e)  # → Expecting value...",
        "# Кастомный default для нестандартных типов",
        "from datetime import date",
        "def default(obj):",
        "if isinstance(obj, date):",
        "return obj.isoformat()",
        "raise TypeError",
        "print(json.dumps({'date': date(2024,1,1)}, default=default))  # → {\"date\":\"2024-01-01\"}",
        "# sort_keys",
        "print(json.dumps({'b':2,'a':1}, sort_keys=True))  # → {\"a\": 1, \"b\": 2}"
      ],
      "related": [
        "json.dumps",
        "json.loads",
        "pickle",
        "csv"
      ],
      "related_errors": []
    },
    {
      "id": "open",
      "title": "open()",
      "kind": "function",
      "summary": {
        "ru": "Открывает файл. Режимы: r (чтение), w (запись, перезапись), a (добавление), x (создание), b (бинарный). Используй with.",
        "en": "Opens a file. Modes: r (read), w (write, truncating), a (append), x (create), b (binary). Use it with with."
      },
      "body": {
        "ru": "Всегда указывай encoding='utf-8' явно: без него текстовый режим берёт кодировку из локали системы, и файл, который спокойно читается на Linux, роняет UnicodeDecodeError на Windows с cp1251. Режим 'w' обрезает файл до нуля уже в момент открытия, до первой записи, — если нужно создать файл, но не затереть существующий, бери 'x': он бросит FileExistsError вместо потери данных.",
        "en": "Always pass encoding='utf-8' explicitly: without it text mode falls back to the system locale, so a file that reads fine on Linux blows up with UnicodeDecodeError on a Windows machine using cp1251. Mode 'w' empties the file the moment it is opened, before any write happens — if you want to create a file without clobbering an existing one, use 'x', which raises FileExistsError instead of destroying data."
      },
      "syntax": "open(path, mode='r', encoding=None)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#open",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "открытие",
      "color_group": "op",
      "aliases": [
        "открыть файл",
        "режимы открытия файла",
        "открыть файл на запись"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "# Чтение текстового файла",
        "with open('/etc/hostname', 'r', encoding='utf-8') as f:",
        "content = f.read()",
        "print(content.strip())  # → hostname",
        "# Запись файла",
        "with open('test.txt', 'w', encoding='utf-8') as f:",
        "f.write('Hello, World!')",
        "# Добавление к файлу",
        "with open('test.txt', 'a') as f:",
        "f.write('\\nSecond line')",
        "# Бинарный режим",
        "with open('test.bin', 'wb') as f:",
        "f.write(bytes([0,1,2,3]))",
        "with open('test.bin', 'rb') as f:",
        "print(f.read())  # → b'\\x00\\x01\\x02\\x03'",
        "# Создание нового файла (x — ошибка если существует)",
        "import os",
        "try:",
        "with open('new_file.txt', 'x') as f:",
        "f.write('new')",
        "except FileExistsError:",
        "pass",
        "# С pathlib",
        "from pathlib import Path",
        "path = Path('test.txt')",
        "with path.open('r') as f:",
        "print(f.readline())  # → Hello, World!",
        "# encoding=utf-8 обязателен для портируемости",
        "with open('test.txt', 'r', encoding='utf-8') as f:",
        "for line in f:",
        "print(line.strip())"
      ],
      "related": [
        "file.read",
        "file.write",
        "итерация-по-файлу",
        "path.read_text"
      ],
      "related_errors": [
        "FileNotFoundError",
        "PermissionError",
        "IsADirectoryError"
      ]
    },
    {
      "id": "os",
      "title": "os",
      "kind": "term",
      "summary": {
        "ru": "Модуль os — интерфейс к операционной системе. Работа с файлами, директориями, переменными окружения.",
        "en": "The os module is the interface to the operating system: files, directories and environment variables."
      },
      "body": {
        "ru": "os.listdir() отдаёт голые имена файлов без директории, поэтому открыть их напрямую нельзя — имя надо склеить с путём через os.path.join(); порядок имён произвольный (как отдала файловая система), сортировку делайте сами. Для самих путей в новом коде обычно берут pathlib, а за os остаются процессы, переменные окружения (os.environ) и операции вроде os.remove/os.rename.",
        "en": "os.listdir() returns bare file names without the directory, so you cannot open them as-is — join them with the directory via os.path.join(); the order is whatever the filesystem gives, so sort it yourself if you need it. New code usually reaches for pathlib for path manipulation and keeps os for processes, environment variables (os.environ) and operations like os.remove/os.rename."
      },
      "syntax": "import os\nos.getcwd()\nos.listdir()\nos.path.join()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#open",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "os",
      "color_group": "op",
      "aliases": [
        "работа с операционной системой",
        "доступ к файловой системе"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "import os",
        "print(os.getcwd())  # → текущая директория",
        "files = os.listdir('/tmp')",
        "print(files[:3])  # → список файлов",
        "# path.join — кроссплатформенное соединение путей",
        "path = os.path.join('/tmp', 'subdir', 'file.txt')",
        "print(path)  # → subdir/file.txt",
        "# path.exists / is_file / is_dir",
        "print(os.path.exists('/tmp'))     # → True",
        "print(os.path.isfile('/tmp'))     # → False",
        "print(os.path.isdir('/tmp'))      # → True",
        "# mkdir / makedirs / remove",
        "os.makedirs('test_os/sub', exist_ok=True)",
        "print(os.path.isdir('test_os/sub'))  # → True",
        "# environ",
        "home = os.environ.get('HOME', 'unknown')",
        "print(home)  # → /home/user или unknown",
        "# os.walk — рекурсивный обход",
        "for root, dirs, files in os.walk('test_os'):",
        "print(root, dirs, files)"
      ],
      "related": [
        "pathlib.path",
        "os.listdir",
        "os.path.join",
        "os.getcwd"
      ],
      "related_errors": []
    },
    {
      "id": "pathlib.path",
      "title": "pathlib.Path",
      "kind": "term",
      "summary": {
        "ru": "Объектно-ориентированный API для работы с путями файловой системы. Кроссплатформенный.",
        "en": "An object-oriented API for filesystem paths. Cross-platform."
      },
      "body": {
        "ru": "Пути склеиваются оператором /, но если справа стоит абсолютный путь, левая часть просто отбрасывается: Path('/tmp') / '/etc' даёт Path('/etc') — частая причина «файл сохранился не туда». Объекты Path неизменяемы: with_suffix(), parent и прочее возвращают новый путь, а не меняют старый; заворачивать Path в str() перед open() и функциями stdlib не нужно, они принимают Path напрямую.",
        "en": "Paths are joined with the / operator, but an absolute path on the right discards everything on the left: Path('/tmp') / '/etc' is Path('/etc') — a common reason a file ends up somewhere unexpected. Path objects are immutable, so with_suffix(), parent and friends return a new path instead of mutating the old one, and you do not need str() around a Path: open() and the standard library accept Path directly."
      },
      "syntax": "from pathlib import Path\np = Path('/some/path')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#open",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "pathlib",
      "color_group": "op",
      "aliases": [
        "путь к файлу",
        "работа с путями",
        "объект пути"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "from pathlib import Path",
        "p = Path('/tmp')",
        "print(p.exists())   # → True",
        "print(p.is_dir())   # → True",
        "print(p.is_file())  # → False",
        "# Создание пути",
        "f = Path('/tmp') / 'test' / 'file.txt'",
        "print(f)        # → /tmp/test/file.txt",
        "print(f.parent) # → /tmp/test",
        "print(f.name)   # → file.txt",
        "print(f.stem)   # → file",
        "print(f.suffix) # → .txt",
        "# Чтение и запись",
        "test_path = Path('/tmp/pathlib_test.txt')",
        "test_path.write_text('Hello from pathlib!', encoding='utf-8')",
        "print(test_path.read_text())  # → Hello from pathlib!",
        "# glob",
        "tmp = Path('/tmp')",
        "txt_files = list(tmp.glob('*.txt'))",
        "print(len(txt_files), 'txt files')",
        "# mkdir",
        "new_dir = Path('/tmp/new_test_dir')",
        "new_dir.mkdir(exist_ok=True)",
        "print(new_dir.exists())  # → True",
        "# iterdir",
        "for item in Path('/tmp').iterdir():",
        "if item.is_file():",
        "pass  # перебираем файлы",
        "# stat и размер",
        "f2 = Path('/tmp/pathlib_test.txt')",
        "print(f2.stat().st_size)  # → размер в байтах"
      ],
      "related": [
        "path",
        "os",
        "path.exists",
        ".stem-.suffix-.suffixes-.name-.parent-.p"
      ],
      "related_errors": []
    },
    {
      "id": "pickle",
      "title": "pickle",
      "kind": "term",
      "summary": {
        "ru": "Сериализация Python-объектов в бинарный формат. Сохраняет произвольные объекты. Не безопасно для ненадёжных данных!",
        "en": "Serialization of Python objects into a binary format. It stores arbitrary objects. Not safe for untrusted data!"
      },
      "body": {
        "ru": "Файл обязательно открывается в бинарном режиме ('wb' для dump, 'rb' для load) — с обычным 'w' будет TypeError. Формат чисто питоновский и завязан на код: при загрузке классы объектов должны импортироваться по тому же пути, что и при сохранении, поэтому после переименования модуля или класса старые .pkl перестают читаться — для обмена данными с другими программами берите JSON.",
        "en": "The file must be opened in binary mode ('wb' for dump, 'rb' for load); plain 'w' raises TypeError. The format is Python-only and tied to your code: on load the classes must be importable under exactly the same module path as when they were saved, so renaming a module or class breaks old .pkl files — use JSON when the data has to travel to other programs."
      },
      "syntax": "import pickle\npickle.dump(obj, f)\npickle.load(f)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#open",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "pickle",
      "color_group": "op",
      "aliases": [
        "сериализация объектов",
        "сохранить объект в файл",
        "загрузить объект из файла"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "import pickle",
        "data = {'key': [1,2,3], 'val': (4,5)}",
        "with open('data.pkl','wb') as f:",
        "pickle.dump(data, f)",
        "with open('data.pkl','rb') as f:",
        "loaded = pickle.load(f)",
        "print(loaded)  # → {'key':[1,2,3],'val':(4,5)}",
        "# dumps/loads — в bytes",
        "bytes_data = pickle.dumps([1,'hello',3.14])",
        "print(bytes_data[:10])  # → бинарные данные",
        "print(pickle.loads(bytes_data))  # → [1,'hello',3.14]",
        "# Сериализация объекта класса",
        "class Point:",
        "def __init__(self,x,y): self.x=x;self.y=y",
        "def __repr__(self): return f'Point({self.x},{self.y})'",
        "p = Point(1,2)",
        "bytes_p = pickle.dumps(p)",
        "print(pickle.loads(bytes_p))  # → Point(1,2)",
        "# Протоколы",
        "print(pickle.HIGHEST_PROTOCOL)  # → 5",
        "bytes2 = pickle.dumps([1,2,3], protocol=2)",
        "print(pickle.loads(bytes2))  # → [1,2,3]",
        "# ВНИМАНИЕ: pickle небезопасен для чужих данных!",
        "# Никогда не делай pickle.load() из ненадёжного источника!"
      ],
      "related": [
        "json",
        "open",
        "csv"
      ],
      "related_errors": []
    },
    {
      "id": "stdin-stdout",
      "title": "stdin / stdout",
      "kind": "term",
      "summary": {
        "ru": "sys.stdin — стандартный ввод. sys.stdout — стандартный вывод. Используются для потокового ввода/вывода.",
        "en": "sys.stdin is the standard input, sys.stdout the standard output. They are used for streaming input and output."
      },
      "body": {
        "ru": "Строки из sys.stdin приходят вместе с '\\n' на конце — почти всегда нужен .strip(), иначе сравнение с ожидаемым ответом развалится на невидимом символе. Цикл for line in sys.stdin завершается только на конце ввода (в терминале это Ctrl+D, на Windows Ctrl+Z и Enter), поэтому задачи с неизвестным числом строк читают именно так, а не через фиксированный range. Когда вывод уходит в файл или в конвейер, sys.stdout буферизуется и строки могут появиться позже, чем ожидаешь, — помогает print(..., flush=True).",
        "en": "Lines read from sys.stdin still carry their trailing '\\n', so a .strip() is almost always needed — otherwise a comparison with the expected answer fails on an invisible character. Iterating with for line in sys.stdin stops only at end of input (Ctrl+D in a terminal, Ctrl+Z then Enter on Windows), which is why problems with an unknown number of lines are read this way rather than with a fixed range. When output is redirected to a file or a pipe, sys.stdout becomes block-buffered and lines may surface later than expected; print(..., flush=True) fixes that."
      },
      "syntax": "import sys\nfor line in sys.stdin: ...\nprint(..., file=sys.stdout)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#open",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "потоки",
      "color_group": "op",
      "aliases": [
        "стандартный ввод",
        "стандартный вывод",
        "чтение всех строк ввода"
      ],
      "keywords": [
        "stdin",
        "stdout"
      ],
      "tags": [
        "op"
      ],
      "examples": [
        "import sys",
        "# Чтение из stdin построчно",
        "# echo 'hello' | python script.py",
        "# for line in sys.stdin:",
        "#     print(line.strip())",
        "print('stdin demo skipped')  # → не интерактивный режим",
        "# print в stderr",
        "print('Error message', file=sys.stderr)",
        "# Перенаправление stdout",
        "import io",
        "buffer = io.StringIO()",
        "sys.stdout = buffer",
        "print('captured')",
        "sys.stdout = sys.__stdout__",
        "print('got:', buffer.getvalue())  # → got: captured\\n",
        "# input() — высокоуровневый stdin",
        "name = input('Name: ') if False else 'Alice'  # skip interactive",
        "print(f'Hello, {name}')  # → Hello, Alice",
        "# Чтение всего stdin",
        "# data = sys.stdin.read()  # в неинтерактивном режиме",
        "# Строки из stdin",
        "import io",
        "fake_stdin = io.StringIO('line1\\nline2\\nline3')",
        "for line in fake_stdin:",
        "    print(line.strip())"
      ],
      "related": [
        "sys.stdin-sys.stdout-sys.stderr",
        "input",
        "print"
      ],
      "related_errors": []
    },
    {
      "id": "итерация-по-файлу",
      "title": "Итерация по файлу",
      "kind": "term",
      "summary": {
        "ru": "Файловый объект — итерируемый. Каждая итерация возвращает строку. Эффективно для больших файлов.",
        "en": "A file object is iterable. Every iteration yields one line. Efficient for large files."
      },
      "body": {
        "ru": "Каждая строка приходит вместе с завершающим переводом строки, поэтому её почти всегда чистят через strip() или rstrip('\\n') — у последней строки файла '\\n' может и не быть. Итератор одноразовый и ленивый: после цикла файл исчерпан, и второй for по тому же объекту не даст ничего, пока не сделать f.seek(0). Именно ленивость и отличает такой обход от f.readlines() и f.read().split('\\n'), которые затягивают весь файл в память.",
        "en": "Each line arrives with its trailing newline attached, which is why it is almost always cleaned up with strip() or rstrip('\\n') — though the final line of a file may have no '\\n' at all. The iterator is lazy and single-pass: once the loop ends the file is exhausted, and a second for over the same object yields nothing until you call f.seek(0). That laziness is exactly what separates this from f.readlines() or f.read().split('\\n'), which pull the whole file into memory."
      },
      "syntax": "for line in f:\n    ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#open",
      "version": "",
      "section": "Файлы и I_O",
      "subcat": "чтение",
      "color_group": "op",
      "aliases": [
        "чтение файла построчно",
        "цикл по строкам файла",
        "перебрать строки файла"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "with open('/tmp/demo.txt') as f:",
        "for line in f:",
        "print(line.strip())  # → line1 / line2 / line3",
        "# Фильтрация строк",
        "with open('/tmp/demo.txt') as f:",
        "lines = [l.strip() for l in f if l.strip()]",
        "print(lines)  # → ['line1','line2','line3']",
        "# Подсчёт строк",
        "with open('/tmp/demo.txt') as f:",
        "count = sum(1 for _ in f)",
        "print(count)  # → 3",
        "# Первые N строк",
        "with open('/tmp/demo.txt') as f:",
        "head = [next(f) for _ in range(2)]",
        "print([l.strip() for l in head])  # → ['line1','line2']",
        "# enumerate для нумерации строк",
        "with open('/tmp/demo.txt') as f:",
        "for i, line in enumerate(f, 1):",
        "print(i, line.strip())"
      ],
      "related": [
        "file.readlines",
        "file.readline",
        "open",
        "file.read"
      ],
      "related_errors": []
    },
    {
      "id": "args",
      "title": "*args",
      "kind": "term",
      "summary": {
        "ru": "Принимает произвольное число позиционных аргументов как кортеж.",
        "en": "Accepts any number of positional arguments as a tuple."
      },
      "body": {
        "ru": "Внутри функции args — именно кортеж, а не список, поэтому менять его на месте нельзя. Любой параметр, объявленный после *args, становится keyword-only: передать его позиционно уже не выйдет, только по имени. Звёздочка в вызове f(*seq) делает обратное — распаковывает последовательность в отдельные аргументы.",
        "en": "Inside the function args is a tuple, not a list, so you can't mutate it in place. Any parameter declared after *args becomes keyword-only — it can no longer be passed positionally, only by name. A star at the call site, f(*seq), does the reverse: it unpacks a sequence into separate arguments."
      },
      "syntax": "def f(*args): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/controlflow.html#arbitrary-argument-lists",
      "version": "",
      "section": "Функции",
      "subcat": "параметры",
      "color_group": "op",
      "aliases": [
        "произвольное число аргументов",
        "переменное количество аргументов",
        "звёздочка в параметрах функции"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def my_sum(*args):",
        "    return sum(args)",
        "print(my_sum(1, 2, 3))  # → 6",
        "print(my_sum(10, 20, 30, 40))  # → 100",
        "def show(*args):",
        "    for a in args:",
        "        print(a)",
        "        show('a', 'b', 'c')  # → a b c",
        "def first_last(*args):",
        "    return args[0], args[-1]",
        "print(first_last(1,2,3,4))  # → (1, 4)",
        "def concat(*args, sep=' '):",
        "    return sep.join(str(a) for a in args)",
        "print(concat('x','y','z'))  # → x y z",
        "nums = [4, 5, 6]",
        "print(my_sum(*nums))  # → 15 (распаковка)",
        "def mixed(a, b, *args):",
        "    return a, b, args",
        "print(mixed(1,2,3,4))  # → (1, 2, (3, 4))"
      ],
      "related": [
        "kwargs",
        "параметры-и-аргументы",
        "распаковка-списка"
      ],
      "related_errors": []
    },
    {
      "id": "def",
      "title": "def",
      "kind": "term",
      "summary": {
        "ru": "Объявление функции с помощью ключевого слова def. Тело функции отделяется отступом.",
        "en": "A function declaration with the def keyword. The body of the function is set off by indentation."
      },
      "body": {
        "ru": "def выполняется в тот момент, когда до него доходит поток, а значения аргументов по умолчанию вычисляются один раз — при определении, а не при каждом вызове. Поэтому изменяемый дефолт (def f(x, acc=[])) живёт между вызовами и накапливает данные; лечится это через acc=None и создание списка уже внутри тела.",
        "en": "def is executed when control reaches it, and default argument values are evaluated once — at definition time, not on every call. So a mutable default (def f(x, acc=[])) is shared across calls and accumulates data; the fix is acc=None plus building the list inside the body."
      },
      "syntax": "def имя(параметры):\n    тело",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#def",
      "version": "",
      "section": "Функции",
      "subcat": "определение",
      "color_group": "op",
      "aliases": [
        "объявление функции",
        "создать свою функцию",
        "как написать функцию"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def greet():",
        "    print('Hello!')  # → Hello!",
        "def add(a, b):",
        "    return a + b  # → вернёт сумму",
        "def square(x):",
        "    return x * x",
        "print(square(5))  # → 25",
        "def say(name):",
        "    \"\"\"Docstring.\"\"\"",
        "    print(f'Hi, {name}')  # → Hi, Alice",
        "def check(n):",
        "    if n > 0:",
        "        return 'pos'",
        "    return 'non-pos'  # → два return",
        "def nothing():",
        "    pass  # → тело-заглушка",
        "result = add(3, 4)",
        "print(result)  # → 7"
      ],
      "related": [
        "return",
        "параметры-и-аргументы",
        "lambda",
        "аннотации-типов-type-hints"
      ],
      "related_errors": []
    },
    {
      "id": "filter",
      "title": "filter()",
      "kind": "function",
      "summary": {
        "ru": "Отбирает элементы, для которых функция возвращает True. Возвращает итератор.",
        "en": "Keeps the items for which the function returns True. Returns an iterator."
      },
      "body": {
        "ru": "Результат — ленивый итератор: он считается по мере обхода и исчерпывается после первого прохода, так что повторный list() по нему вернёт пустоту. Особый случай — filter(None, iterable): без функции отсеиваются все ложные по истинности элементы (0, '', None, пустые контейнеры).",
        "en": "The result is a lazy iterator: it's computed as you traverse it and is exhausted after the first pass, so a second list() over it yields nothing. Special case — filter(None, iterable): with no function it drops every falsy element (0, '', None, empty containers)."
      },
      "syntax": "filter(func, iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#filter",
      "version": "",
      "section": "Функции",
      "subcat": "функциональное",
      "color_group": "op",
      "aliases": [
        "фильтрация списка",
        "отобрать элементы по условию",
        "отфильтровать элементы"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "nums = [1, -2, 3, -4, 5]",
        "pos = list(filter(lambda x: x > 0, nums))",
        "print(pos)  # → [1, 3, 5]",
        "evens = list(filter(lambda x: x % 2 == 0, range(10)))",
        "print(evens)  # → [0, 2, 4, 6, 8]",
        "words = ['', 'hello', '', 'world']",
        "non_empty = list(filter(None, words))",
        "print(non_empty)  # → ['hello', 'world']",
        "# filter с функцией",
        "def is_odd(n): return n % 2 != 0",
        "odds = list(filter(is_odd, range(10)))",
        "print(odds)  # → [1, 3, 5, 7, 9]",
        "# filter ленивый",
        "f = filter(lambda x: x > 2, [1,2,3,4])",
        "print(next(f))  # → 3",
        "# Отбор непустых словарей",
        "data = [{'a':1}, {}, {'b':2}]",
        "non_empty_d = list(filter(None, data))",
        "print(non_empty_d)  # → [{'a':1},{'b':2}]",
        "# filter + map",
        "result = list(map(lambda x: x**2, filter(lambda x: x%2==0, range(6))))",
        "print(result)  # → [0, 4, 16]",
        "print(list(filter(str.isdigit, \"a1b2c3\")))  # → ['1', '2', '3']"
      ],
      "related": [
        "map"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "global-nonlocal",
      "title": "global / nonlocal",
      "kind": "term",
      "summary": {
        "ru": "global объявляет переменную из глобального пространства. nonlocal — из охватывающей функции (не глобальной).",
        "en": "global declares a variable from the global namespace. nonlocal — one from an enclosing function (not the global scope)."
      },
      "body": {
        "ru": "Объявление нужно только для присваивания: читать глобальную переменную или менять на месте изменяемый объект (list.append) можно и без global. nonlocal требует, чтобы переменная уже существовала в охватывающей функции, — создать новую он не может и до глобальных имён не дотягивается.",
        "en": "You only need these to rebind: reading a global, or mutating a mutable object in place (list.append), works without global. nonlocal requires the variable to already exist in an enclosing function — it can't create a new one and won't reach the global scope."
      },
      "syntax": "global x\nnonlocal y",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/simple_stmts.html#the-global-statement",
      "version": "",
      "section": "Функции",
      "subcat": "область видимости",
      "color_group": "op",
      "aliases": [
        "изменить глобальную переменную в функции",
        "переменная не меняется внутри функции",
        "доступ к внешней переменной"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "counter = 0",
        "def increment():",
        "global counter",
        "counter += 1",
        "increment()",
        "print(counter)  # → 1",
        "def make_counter():",
        "count = 0",
        "def inc():",
        "nonlocal count",
        "count += 1",
        "return count",
        "return inc",
        "c = make_counter()",
        "print(c(), c(), c())  # → 1 2 3",
        "total = 0",
        "def add(x):",
        "global total",
        "total += x",
        "add(5); add(3)",
        "print(total)  # → 8",
        "def outer():",
        "x = 0",
        "def middle():",
        "nonlocal x",
        "x = 10",
        "def inner():",
        "nonlocal x",
        "x += 1",
        "inner()",
        "middle()",
        "return x",
        "print(outer())  # → 11",
        "# global можно объявить несколько переменных",
        "a = b = 0",
        "def reset():",
        "global a, b",
        "a = b = 1",
        "reset()",
        "print(a, b)  # → 1 1",
        "def acc():",
        "s = 0",
        "def add(n):",
        "nonlocal s",
        "s += n",
        "return s",
        "return add",
        "f = acc()",
        "print(f(3), f(4))  # → 3 7"
      ],
      "related": [
        "локальные-и-глобальные-переменные",
        "замыкания",
        "unboundlocalerror",
        "globals"
      ],
      "related_errors": []
    },
    {
      "id": "kwargs",
      "title": "**kwargs",
      "kind": "term",
      "summary": {
        "ru": "Принимает произвольное число ключевых аргументов как словарь.",
        "en": "Accepts any number of keyword arguments as a dictionary."
      },
      "body": {
        "ru": "Внутри функции kwargs — обычный словарь, и начиная с Python 3.7 он хранит аргументы в порядке их передачи. В сигнатуре **kwargs идёт последним; двойная звёздочка в вызове f(**d) делает обратное — раскладывает словарь в именованные аргументы.",
        "en": "Inside the function kwargs is a plain dict, and since Python 3.7 it preserves the order in which arguments were passed. In the signature **kwargs comes last; a double star at the call site, f(**d), does the reverse — spreading a dict into keyword arguments."
      },
      "syntax": "def f(**kwargs): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/controlflow.html#keyword-arguments",
      "version": "",
      "section": "Функции",
      "subcat": "параметры",
      "color_group": "op",
      "aliases": [
        "ключевые аргументы",
        "именованные аргументы",
        "словарь аргументов функции"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def show(**kwargs):",
        "    for k, v in kwargs.items():",
        "        print(k, v)",
        "        show(a=1, b=2)  # → a 1 / b 2",
        "def config(**kwargs):",
        "    return kwargs",
        "print(config(host='localhost', port=80))  # → {'host': 'localhost', 'port': 80}",
        "def connect(host, port, **kwargs):",
        "    print(host, port, kwargs)",
        "    connect('srv', 443, ssl=True)  # → srv 443 {'ssl': True}",
        "def merge(base, **overrides):",
        "    d = base.copy()",
        "    d.update(overrides)",
        "    return d",
        "print(merge({'a':1}, b=2))  # → {'a':1,'b':2}",
        "def forward(*args, **kwargs):",
        "    print(args, kwargs)",
        "    forward(1, 2, x=3)  # → (1,2) {'x':3}",
        "    opts = {'timeout': 30, 'retry': 3}",
        "    show(**opts)  # → timeout 30 / retry 3",
        "def all_args(a, *args, **kwargs):",
        "    return a, args, kwargs",
        "print(all_args(1,2,3,x=4))  # → (1,(2,3),{'x':4})"
      ],
      "related": [
        "args",
        "параметры-и-аргументы",
        "слияние-и-распаковка-словарей"
      ],
      "related_errors": []
    },
    {
      "id": "lambda",
      "title": "lambda",
      "kind": "term",
      "summary": {
        "ru": "Анонимная функция — выражение, возвращающее функцию. Только одно выражение.",
        "en": "An anonymous function — an expression that produces a function. Its body is a single expression."
      },
      "body": {
        "ru": "Главная ловушка — позднее связывание: lambda, созданная в цикле, запоминает саму переменную, а не её текущее значение, поэтому все такие функции потом увидят её последнее значение. PEP 8 не советует присваивать lambda имени (sq = lambda...) — для именованной функции берите def; lambda хороша именно как одноразовый аргумент для key=, map() или filter().",
        "en": "The main trap is late binding: a lambda created inside a loop captures the variable itself, not its current value, so every such function ends up seeing the loop's final value. PEP 8 discourages binding a lambda to a name (sq = lambda...) — use def for a named function; lambda shines as a throwaway argument to key=, map() or filter()."
      },
      "syntax": "lambda параметры: выражение",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/expressions.html#lambda",
      "version": "3.5",
      "section": "Функции",
      "subcat": "lambda",
      "color_group": "op",
      "aliases": [
        "анонимная функция",
        "безымянная функция",
        "функция в одну строку"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "sq = lambda x: x ** 2",
        "print(sq(4))  # → 16",
        "add = lambda a, b: a + b",
        "print(add(3, 5))  # → 8",
        "words = ['banana', 'apple', 'cherry']",
        "words.sort(key=lambda w: len(w))",
        "print(words)  # → ['apple', 'banana', 'cherry']",
        "nums = [1, -2, 3, -4]",
        "pos = list(filter(lambda x: x > 0, nums))",
        "print(pos)  # → [1, 3]",
        "doubled = list(map(lambda x: x * 2, [1,2,3]))",
        "print(doubled)  # → [2, 4, 6]",
        "pairs = [(1,'b'),(2,'a'),(3,'c')]",
        "pairs.sort(key=lambda p: p[1])",
        "print(pairs)  # → [(2,'a'),(1,'b'),(3,'c')]",
        "# Вложенная lambda (каррирование)",
        "mul = lambda x: lambda y: x * y",
        "double = mul(2)",
        "print(double(5))  # → 10"
      ],
      "related": [
        "def",
        "sorted-с-key",
        "map",
        "filter"
      ],
      "related_errors": []
    },
    {
      "id": "map",
      "title": "map()",
      "kind": "function",
      "summary": {
        "ru": "Применяет функцию к каждому элементу итерируемого. Возвращает итератор.",
        "en": "Applies a function to every item of an iterable. Returns an iterator."
      },
      "body": {
        "ru": "map возвращает ленивый итератор: ничего не считается, пока вы по нему не пройдёте, а после первого прохода он пуст — оборачивайте в list(), если результат нужен дважды. Часто списковое включение [f(x) for x in xs] читается яснее; map выигрывает, когда функция уже готова, как в map(int, strs).",
        "en": "map returns a lazy iterator: nothing is computed until you iterate it, and it's empty after the first pass — wrap it in list() if you need the result twice. A comprehension [f(x) for x in xs] often reads more clearly; map wins when the function already exists, as in map(int, strs)."
      },
      "syntax": "map(func, iterable)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#map",
      "version": "",
      "section": "Функции",
      "subcat": "функциональное",
      "color_group": "op",
      "aliases": [
        "применить функцию к каждому элементу",
        "преобразовать все элементы списка",
        "преобразовать строки в числа"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "nums = [1, 2, 3, 4]",
        "result = list(map(lambda x: x**2, nums))",
        "print(result)  # → [1, 4, 9, 16]",
        "strs = ['1','2','3']",
        "ints = list(map(int, strs))",
        "print(ints)  # → [1, 2, 3]",
        "floats = list(map(float, ['1.1','2.2']))",
        "print(floats)  # → [1.1, 2.2]",
        "words = ['hello', 'world']",
        "uppered = list(map(str.upper, words))",
        "print(uppered)  # → ['HELLO', 'WORLD']",
        "# map с несколькими итерируемыми",
        "a = [1, 2, 3]",
        "b = [10, 20, 30]",
        "result2 = list(map(lambda x,y: x+y, a, b))",
        "print(result2)  # → [11, 22, 33]",
        "# map ленивый",
        "m = map(str, range(5))",
        "print(next(m))  # → '0'",
        "print(next(m))  # → '1'",
        "names = ['alice', 'bob']",
        "result3 = list(map(str.capitalize, names))",
        "print(result3)  # → ['Alice', 'Bob']",
        "# map по двум последовательностям сразу",
        "print(list(map(lambda x, y: x + y, [1,2,3], [10,20,30])))  # → [11,22,33]"
      ],
      "related": [
        "filter"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "reduce",
      "title": "reduce()",
      "kind": "function",
      "summary": {
        "ru": "Сворачивает итерируемый объект в одно значение, последовательно применяя функцию. Из модуля functools.",
        "en": "Folds an iterable into a single value by applying a function step by step. From the functools module."
      },
      "body": {
        "ru": "В Python 3 reduce вынесли из встроенных в functools намеренно: для суммы, максимума и подобного явные sum(), max(), min() короче и понятнее. Осторожно с пустой последовательностью — без initializer это TypeError, а с ним initializer же и вернётся.",
        "en": "In Python 3 reduce was deliberately moved out of the builtins into functools: for sums, maxima and the like the explicit sum(), max() and min() are shorter and clearer. Beware the empty sequence — without an initializer it raises TypeError, and with one that initializer is what comes back."
      },
      "syntax": "from functools import reduce\nreduce(func, iterable[, initializer])",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functools.html#functools.reduce",
      "version": "",
      "section": "Функции",
      "subcat": "функциональное",
      "color_group": "op",
      "aliases": [
        "свёртка последовательности",
        "свернуть список в одно значение",
        "накопление результата по списку"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "from functools import reduce",
        "result = reduce(lambda a, b: a + b, [1,2,3,4,5])",
        "print(result)  # → 15",
        "product = reduce(lambda a, b: a * b, [1,2,3,4,5])",
        "print(product)  # → 120",
        "maximum = reduce(lambda a, b: a if a > b else b, [3,1,4,1,5,9])",
        "print(maximum)  # → 9",
        "words = ['Hello', ' ', 'World']",
        "sentence = reduce(lambda a, b: a + b, words)",
        "print(sentence)  # → Hello  World",
        "# С начальным значением",
        "total = reduce(lambda a, b: a + b, [1,2,3], 100)",
        "print(total)  # → 106",
        "# Подсчёт вхождений",
        "from collections import Counter",
        "nums = [1,1,2,3,1]",
        "result2 = reduce(lambda acc, x: acc + (1 if x==1 else 0), nums, 0)",
        "print(result2)  # → 3"
      ],
      "related": [
        "functools.reduce",
        "map",
        "filter",
        "sum"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "return",
      "title": "return",
      "kind": "term",
      "summary": {
        "ru": "Возвращает значение из функции. Без return — возвращает None. Может возвращать кортеж.",
        "en": "Returns a value from a function. Without a return the function returns None. It can return a tuple."
      },
      "body": {
        "ru": "Частая ошибка новичка — печатать результат внутри функции вместо return: на экране число видно, но вызывающий код получает None и работать с ним не может. return немедленно завершает функцию, код после него не выполнится; return b, a без скобок отдаёт кортеж.",
        "en": "A classic beginner mistake is printing the result inside the function instead of returning it: the number shows on screen, but the caller receives None and can't use it. return ends the function immediately, so code after it never runs; return b, a without parentheses hands back a tuple."
      },
      "syntax": "return значение",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/simple_stmts.html#return",
      "version": "",
      "section": "Функции",
      "subcat": "возврат",
      "color_group": "op",
      "aliases": [
        "вернуть значение из функции",
        "возврат результата функции",
        "почему функция возвращает None"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def double(x):",
        "return x * 2",
        "print(double(5))  # → 10",
        "def swap(a, b):",
        "return b, a  # кортеж",
        "a, b = swap(1, 2)",
        "print(a, b)  # → 2 1",
        "def nothing():",
        "return  # → None",
        "print(nothing())  # → None",
        "def safe_div(a, b):",
        "if b == 0:",
        "return None  # ранний return",
        "return a / b",
        "print(safe_div(10, 0))  # → None",
        "def classify(n):",
        "if n < 0:",
        "return 'neg'",
        "if n == 0:",
        "return 'zero'",
        "return 'pos'",
        "print(classify(-1))  # → neg",
        "def stats(lst):",
        "return min(lst), max(lst), sum(lst)/len(lst)",
        "print(stats([1,2,3,4]))  # → (1, 4, 2.5)",
        "def first_even(lst):",
        "for x in lst:",
        "if x % 2 == 0:",
        "return x",
        "return None",
        "print(first_even([1,3,4,6]))  # → 4"
      ],
      "related": [
        "def",
        "кортеж-в-return-функции",
        "generator-function-yield",
        "nonetype"
      ],
      "related_errors": []
    },
    {
      "id": "sorted-с-key",
      "title": "sorted() с key=",
      "kind": "term",
      "summary": {
        "ru": "Возвращает новый отсортированный список. key= задаёт функцию для ключа сортировки. reverse= — обратный порядок.",
        "en": "Returns a new sorted list. key= sets the function that produces the sort key. reverse= gives the reverse order."
      },
      "body": {
        "ru": "Сортировка стабильна: элементы с равным ключом сохраняют исходный порядок, поэтому многоуровневую сортировку делают в несколько проходов, начиная с младшего ключа. Функция key вызывается ровно один раз на элемент, а не при каждом сравнении, так что тяжёлый ключ не накладен; в отличие от list.sort() sorted() не меняет исходник и принимает любой итерируемый объект.",
        "en": "The sort is stable: items with equal keys keep their original order, so multi-level sorts are done in several passes starting from the least significant key. The key function is called exactly once per element, not on every comparison, so an expensive key is cheap; unlike list.sort(), sorted() leaves the original untouched and accepts any iterable."
      },
      "syntax": "sorted(iterable, key=None, reverse=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#sorted",
      "version": "",
      "section": "Функции",
      "subcat": "функциональное",
      "color_group": "op",
      "aliases": [
        "отсортировать список",
        "сортировка по убыванию",
        "сортировка по длине слова"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "words = ['banana', 'apple', 'cherry', 'kiwi']",
        "print(sorted(words))  # → алфавит",
        "print(sorted(words, key=len))  # → по длине",
        "print(sorted(words, key=len, reverse=True))  # → ['banana', 'cherry', 'apple', 'kiwi']",
        "# key=str.lower — регистронезависимо",
        "mixed = ['Banana', 'apple', 'Cherry']",
        "print(sorted(mixed, key=str.lower))  # → ['apple', 'Banana', 'Cherry']",
        "# Сортировка кортежей",
        "pairs = [(2,'b'),(1,'a'),(3,'c')]",
        "print(sorted(pairs))  # → [(1,'a'),(2,'b'),(3,'c')]",
        "print(sorted(pairs, key=lambda p: p[1]))  # → по буквам",
        "# Словари по значению",
        "d = {'a':3,'b':1,'c':2}",
        "print(sorted(d, key=d.get))  # → ['b','c','a']",
        "# Вторичный ключ",
        "students = [('Alice',85),('Bob',90),('Carol',85)]",
        "print(sorted(students, key=lambda s: (-s[1], s[0])))  # → Bob, Alice, Carol",
        "nums = [3,-1,4,-1,5,-9]",
        "print(sorted(nums, key=abs))  # → [-1,-1,3,4,5,-9]"
      ],
      "related": [
        "sorted",
        "list.sort",
        "сортировка-ключом-key-lambda",
        "operator.itemgetter"
      ],
      "related_errors": []
    },
    {
      "id": "аннотации-типов-type-hints",
      "title": "Аннотации типов (type hints)",
      "kind": "function",
      "summary": {
        "ru": "Подсказки типов для параметров и возвращаемых значений. Не влияют на выполнение, но помогают IDE и mypy.",
        "en": "Type hints for parameters and return values. They do not affect execution, but they help IDEs and mypy."
      },
      "body": {
        "ru": "Аннотации — это просто метаданные: Python хранит их в __annotations__ и сам никогда не проверяет, поэтому передача строки туда, где объявлен int, не вызовет ошибки — её поймает только mypy. На тех же метаданных построены dataclasses и pydantic, которые читают аннотации во время выполнения; а чтобы сослаться на ещё не определённый класс, его имя берут в кавычки.",
        "en": "Annotations are just metadata: Python stores them in __annotations__ and never checks them itself, so passing a str where an int is declared raises no error — only mypy will flag it. That same metadata powers dataclasses and pydantic, which read annotations at runtime; and to reference a class that isn't defined yet, put its name in quotes."
      },
      "syntax": "def f(x: int) -> str: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/typing.html",
      "version": "",
      "section": "Функции",
      "subcat": "аннотации",
      "color_group": "op",
      "aliases": [
        "подсказки типов",
        "указание типов в функции",
        "типизация параметров"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def greet(name: str) -> str:",
        "    return f'Hello, {name}'",
        "print(greet('Alice'))  # → Hello, Alice",
        "def add(a: int, b: int) -> int:",
        "    return a + b",
        "print(add(2, 3))  # → 5",
        "from typing import Optional",
        "def find(lst: list, val: int) -> Optional[int]:",
        "    try:",
        "        return lst.index(val)",
        "    except ValueError:",
        "        return None",
        "print(find([1,2,3], 2))  # → 1",
        "from typing import Union",
        "def double(x: Union[int, float]) -> Union[int, float]:",
        "    return x * 2",
        "print(double(3.5))  # → 7.0",
        "from typing import List, Dict",
        "def total(prices: List[float]) -> float:",
        "    return sum(prices)",
        "print(total([1.5, 2.5, 3.0]))  # → 7.0",
        "# Python 3.10+ синтаксис",
        "def clamp(x: int | float, lo: int | float, hi: int | float) -> int | float:",
        "    return max(lo, min(x, hi))",
        "print(clamp(5, 0, 3))  # → 3",
        "# Variable annotations",
        "count: int = 0",
        "name: str",
        "def get_items() -> list[int]:",
        "    return [1, 2, 3]"
      ],
      "related": [
        "int-str-list-dict-аннотации",
        "optional-x-x-none",
        "union-x-y-x-y",
        "callable-arg-ret"
      ],
      "related_errors": []
    },
    {
      "id": "вызываемые-объекты-__call__",
      "title": "Вызываемые объекты __call__",
      "kind": "term",
      "summary": {
        "ru": "Объект с методом __call__ можно вызывать как функцию. Используется для stateful callable, декораторов-классов.",
        "en": "An object with a __call__ method can be called like a function. Used for stateful callables and for class-based decorators."
      },
      "body": {
        "ru": "Класс с __call__ — альтернатива замыканию, когда состояние удобнее держать в атрибутах: его видно, можно менять и добавлять вспомогательные методы (reset, __repr__), тогда как в замыкании оно спрятано. Проверить, вызываем ли объект, можно встроенной callable(obj); кстати, обычные функции — тоже объекты с методом __call__.",
        "en": "A class with __call__ is the alternative to a closure when state is better kept in attributes: it stays visible, mutable, and can carry helper methods (reset, __repr__), whereas a closure hides it. Use the built-in callable(obj) to test whether something can be called — and note that ordinary functions are themselves objects with a __call__."
      },
      "syntax": "class F:\n    def __call__(self, x): ...\nf = F()\nf(10)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/datamodel.html#object.__call__",
      "version": "",
      "section": "Функции",
      "subcat": "callable",
      "color_group": "op",
      "aliases": [
        "объект как функция",
        "вызвать экземпляр класса",
        "сделать экземпляр вызываемым"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "class Adder:",
        "def __init__(self, n):",
        "self.n = n",
        "def __call__(self, x):",
        "return x + self.n",
        "add5 = Adder(5)",
        "print(add5(3))  # → 8",
        "class Counter:",
        "def __init__(self):",
        "self.count = 0",
        "def __call__(self):",
        "self.count += 1",
        "return self.count",
        "c = Counter()",
        "print(c(), c(), c())  # → 1 2 3",
        "class Multiplier:",
        "def __init__(self, factor):",
        "self.factor = factor",
        "def __call__(self, x):",
        "return x * self.factor",
        "double = Multiplier(2)",
        "print(list(map(double, [1,2,3,4])))  # → [2,4,6,8]",
        "print(callable(double))  # → True",
        "print(callable(42))     # → False",
        "class Logger:",
        "def __init__(self, func):",
        "self.func = func",
        "self.calls = 0",
        "def __call__(self, *a, **kw):",
        "self.calls += 1",
        "return self.func(*a, **kw)",
        "def add(a,b): return a+b",
        "logged_add = Logger(add)",
        "print(logged_add(1,2))  # → 3",
        "print(logged_add.calls)  # → 1"
      ],
      "related": [
        "callable",
        "декораторы",
        "lambda"
      ],
      "related_errors": []
    },
    {
      "id": "декораторы",
      "title": "Декораторы",
      "kind": "term",
      "summary": {
        "ru": "Функция высшего порядка, оборачивающая другую функцию для изменения её поведения. Используется @синтаксис.",
        "en": "A higher-order function that wraps another function to change its behavior. Applied with the @ syntax."
      },
      "body": {
        "ru": "Запись @deco над def f — это ровно f = deco(f), она выполняется один раз в момент определения функции. Главная грабля: без functools.wraps обёртка подменяет собой оригинал, и f.__name__, docstring и сигнатура теряются — help() и трейсбеки начинают врать; поэтому wrapper оборачивают в @wraps(func).",
        "en": "Writing @deco above def f is exactly f = deco(f), evaluated once when the function is defined. The classic pitfall: without functools.wraps the wrapper stands in for the original, so f.__name__, its docstring and signature are lost — help() and tracebacks start lying; wrap your wrapper in @wraps(func)."
      },
      "syntax": "@decorator\ndef func(): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-decorator",
      "version": "",
      "section": "Функции",
      "subcat": "декораторы",
      "color_group": "op",
      "aliases": [
        "обёртка над функцией",
        "знак собачки перед функцией",
        "обернуть функцию"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def my_dec(func):",
        "def wrapper(*args, **kwargs):",
        "print('before')",
        "result = func(*args, **kwargs)",
        "print('after')",
        "return result",
        "return wrapper",
        "@my_dec",
        "def say(): print('hi')",
        "say()  # → before / hi / after",
        "import time",
        "def timer(func):",
        "def wrapper(*args, **kwargs):",
        "t = time.time()",
        "res = func(*args, **kwargs)",
        "print(f'{func.__name__}: {time.time()-t:.4f}s')",
        "return res",
        "return wrapper",
        "@timer",
        "def slow(): time.sleep(0.01)",
        "slow()  # → slow: 0.01s",
        "from functools import wraps",
        "def logged(func):",
        "@wraps(func)",
        "def wrapper(*a, **kw):",
        "print(f'Calling {func.__name__}')",
        "return func(*a, **kw)",
        "return wrapper",
        "@logged",
        "def add(a,b): return a+b",
        "print(add(1,2))  # → Calling add / 3",
        "# Декоратор с аргументами",
        "def repeat(n):",
        "def decorator(func):",
        "def wrapper(*a, **kw):",
        "for _ in range(n):",
        "func(*a, **kw)",
        "return wrapper",
        "return decorator",
        "@repeat(3)",
        "def hi(): print('hi')",
        "hi()  # → hi hi hi",
        "# Стек декораторов",
        "@my_dec",
        "@logged",
        "def greet(name): return f'Hi {name}'",
        "greet('Alice')  # оба декоратора применяются",
        "# Декоратор для проверки типа",
        "def check_int(func):",
        "def wrapper(n):",
        "if not isinstance(n, int):",
        "raise TypeError('int expected')",
        "return func(n)",
        "return wrapper",
        "@check_int",
        "def triple(n): return n * 3",
        "print(triple(4))  # → 12",
        "# Сохранение имени функции (@wraps)",
        "from functools import wraps",
        "def noop(f):",
        "@wraps(f)",
        "def w(*a,**kw): return f(*a,**kw)",
        "return w",
        "@noop",
        "def my_func(): pass",
        "print(my_func.__name__)  # → my_func"
      ],
      "related": [
        "замыкания",
        "functools.wraps",
        "functools.lru_cache",
        "property"
      ],
      "related_errors": []
    },
    {
      "id": "замыкания",
      "title": "Замыкания",
      "kind": "term",
      "summary": {
        "ru": "Вложенная функция захватывает переменную из охватывающей области. Используется для фабрик функций и инкапсуляции состояния.",
        "en": "A nested function captures a variable from the enclosing scope. Used for function factories and to encapsulate state."
      },
      "body": {
        "ru": "Классическая ловушка: замыкание захватывает саму переменную, а не её значение в момент создания. Поэтому функции, созданные в цикле (lambda: i), все увидят последнее значение i — зафиксируйте его через аргумент по умолчанию (lambda i=i: i). А чтобы захваченную переменную не читать, а переприсваивать, во вложенной функции нужен nonlocal.",
        "en": "The classic trap: a closure captures the variable itself, not its value at creation time. So functions built in a loop (lambda: i) all see i's final value — pin it with a default argument (lambda i=i: i). And to rebind, rather than just read, a captured variable, the inner function needs nonlocal."
      },
      "syntax": "def outer():\n    x = 10\n    def inner():\n        return x\n    return inner",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-closure-variable",
      "version": "",
      "section": "Функции",
      "subcat": "замыкания",
      "color_group": "op",
      "aliases": [
        "функция внутри функции",
        "захват переменной внешней функции",
        "фабрика функций"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def make_adder(n):",
        "def adder(x):",
        "return x + n",
        "return adder",
        "add5 = make_adder(5)",
        "print(add5(3))  # → 8",
        "def make_multiplier(factor):",
        "def multiply(x):",
        "return x * factor",
        "return multiply",
        "double = make_multiplier(2)",
        "print(double(7))  # → 14",
        "def counter():",
        "count = 0",
        "def inc():",
        "nonlocal count",
        "count += 1",
        "return count",
        "return inc",
        "c = counter()",
        "print(c(), c())  # → 1 2",
        "def make_greeting(greeting):",
        "def greet(name):",
        "return f'{greeting}, {name}!'",
        "return greet",
        "hello = make_greeting('Hello')",
        "print(hello('Bob'))  # → Hello, Bob!",
        "# Проверка замыкания",
        "def outer():",
        "x = 'closure'",
        "def inner():",
        "return x",
        "return inner",
        "f = outer()",
        "print(f())  # → closure",
        "# __closure__",
        "def make_pow(n):",
        "def power(x):",
        "return x ** n",
        "return power",
        "p = make_pow(3)",
        "print(p(2))  # → 8",
        "print(p.__closure__[0].cell_contents)  # → 3"
      ],
      "related": [
        "декораторы",
        "global-nonlocal",
        "локальные-и-глобальные-переменные",
        "functools.partial"
      ],
      "related_errors": []
    },
    {
      "id": "локальные-и-глобальные-переменные",
      "title": "Локальные и глобальные переменные",
      "kind": "term",
      "summary": {
        "ru": "LEGB-правило: Local → Enclosing → Global → Built-in. Локальная переменная существует только внутри функции.",
        "en": "The LEGB rule: Local → Enclosing → Global → Built-in. A local variable exists only inside its function."
      },
      "body": {
        "ru": "Главная грабля: любое присваивание имени внутри функции делает его локальным на всё тело — поэтому прочитать глобальную x, а ниже написать x = ..., нельзя, будет UnboundLocalError уже на чтении. Читать глобальные можно без объявления, но чтобы присвоить именно глобальной, а не создать новую локальную, нужен global; для переменной из объемлющей функции — nonlocal.",
        "en": "The main trap: assigning to a name anywhere in a function makes it local for the whole body — so you cannot read the global x and then write x = ... below it; that raises UnboundLocalError on the read. Reading globals needs no declaration, but to assign to a global instead of creating a new local you need global; for a variable from an enclosing function, nonlocal."
      },
      "syntax": "x = 10  # глобальная\ndef f():\n    x = 5  # локальная",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/executionmodel.html#resolution-of-names",
      "version": "3.12",
      "section": "Функции",
      "subcat": "область видимости",
      "color_group": "op",
      "aliases": [
        "область видимости переменных",
        "переменная не видна внутри функции",
        "где видна переменная"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "x = 'global'",
        "def f():",
        "x = 'local'",
        "print(x)  # → local",
        "f()",
        "print(x)  # → global",
        "count = 0",
        "def inc():",
        "global count",
        "count += 1",
        "inc()",
        "print(count)  # → 1",
        "def outer():",
        "n = 10",
        "def inner():",
        "nonlocal n",
        "n += 1",
        "inner()",
        "return n",
        "print(outer())  # → 11",
        "# Ловушка: чтение до присваивания",
        "x2 = 5",
        "def bad():",
        "print(x2)  # UnboundLocalError!",
        "x2 = 10",
        "# Встроенные имена",
        "def f2():",
        "print(len([1,2,3]))  # → 3 (built-in)",
        "f2()",
        "# Enclosing",
        "def outer2():",
        "msg = 'hi'",
        "def inner2():",
        "print(msg)  # → hi (enclosing)",
        "inner2()",
        "outer2()"
      ],
      "related": [
        "global-nonlocal",
        "замыкания",
        "unboundlocalerror",
        "globals"
      ],
      "related_errors": []
    },
    {
      "id": "параметры-и-аргументы",
      "title": "Параметры и аргументы",
      "kind": "term",
      "summary": {
        "ru": "Параметры — имена в определении функции. Аргументы — значения при вызове. Позиционные передаются по порядку, ключевые — по имени.",
        "en": "Parameters are the names in the function definition. Arguments are the values at the call site. Positional ones are passed in order, keyword ones by name."
      },
      "body": {
        "ru": "Позиционные аргументы должны идти строго перед ключевыми: f(a=1, 2) — это SyntaxError, а не ошибка времени выполнения. И наоборот, имена параметров становятся частью контракта функции: вызов по ключу сломается, если позже переименовать параметр.",
        "en": "Positional arguments must all come before keyword ones: f(a=1, 2) is a SyntaxError, not a runtime error. Conversely, parameter names are part of the function's contract — a keyword call breaks if you later rename the parameter."
      },
      "syntax": "def f(a, b): ...\nf(1, 2)  # позиционные\nf(a=1, b=2)  # ключевые",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/glossary.html#term-parameter",
      "version": "",
      "section": "Функции",
      "subcat": "параметры",
      "color_group": "op",
      "aliases": [
        "позиционные аргументы",
        "передача аргументов в функцию",
        "чем отличается параметр от аргумента"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def sub(a, b):",
        "    return a - b",
        "print(sub(10, 3))  # → 7",
        "print(sub(b=3, a=10))  # → 7 (ключевые)",
        "def full_name(first, last):",
        "    return first + ' ' + last",
        "print(full_name('Ivan', 'Petrov'))  # → Ivan Petrov",
        "print(full_name(last='Petrov', first='Ivan'))  # → Ivan Petrov",
        "def power(base, exp):",
        "    return base ** exp",
        "print(power(2, 8))  # → 256",
        "print(power(exp=3, base=2))  # → 8",
        "def greet(name, punct='!'):",
        "    return 'Hello, ' + name + punct",
        "print(greet('Bob'))  # → Hello, Bob!"
      ],
      "related": [
        "параметры-по-умолчанию",
        "args",
        "kwargs",
        "def"
      ],
      "related_errors": []
    },
    {
      "id": "параметры-по-умолчанию",
      "title": "Параметры по умолчанию",
      "kind": "term",
      "summary": {
        "ru": "Параметры могут иметь значение по умолчанию. Изменяемые объекты как default — ловушка! Используй None-паттерн.",
        "en": "Parameters may have a default value. A mutable object as a default is a trap! Use the None pattern instead."
      },
      "body": {
        "ru": "Значение по умолчанию вычисляется один раз — в момент определения функции, а не при каждом вызове. Поэтому один и тот же список или словарь живёт между вызовами и копит изменения; паттерн с None (y=None, внутри if y is None: y = []) создаёт свежий объект при каждом вызове.",
        "en": "A default value is evaluated once, when the function is defined — not on every call. So the same list or dict survives between calls and accumulates changes; the None pattern (y=None, then if y is None: y = [] inside) builds a fresh object each time."
      },
      "syntax": "def f(x, y=10): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/controlflow.html#default-argument-values",
      "version": "",
      "section": "Функции",
      "subcat": "параметры",
      "color_group": "op",
      "aliases": [
        "значение по умолчанию",
        "необязательный аргумент",
        "изменяемый аргумент по умолчанию"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def greet(name='World'):",
        "return f'Hello, {name}!'",
        "print(greet())  # → Hello, World!",
        "print(greet('Alice'))  # → Hello, Alice!",
        "# Ловушка: изменяемый default",
        "def bad(lst=[]):",
        "lst.append(1)",
        "return lst",
        "print(bad())  # → [1]",
        "print(bad())  # → [1, 1] !",
        "# None-паттерн (правильно)",
        "def good(lst=None):",
        "if lst is None:",
        "lst = []",
        "lst.append(1)",
        "return lst",
        "print(good())  # → [1]",
        "print(good())  # → [1]",
        "def connect(host='localhost', port=5432):",
        "return f'{host}:{port}'",
        "print(connect())  # → localhost:5432",
        "print(connect(port=3306))  # → localhost:3306"
      ],
      "related": [
        "параметры-и-аргументы",
        "nonetype",
        "kwargs"
      ],
      "related_errors": []
    },
    {
      "id": "рекурсия",
      "title": "Рекурсия",
      "kind": "term",
      "summary": {
        "ru": "Функция вызывает себя. Требует базовый случай. Глубина по умолчанию ~1000 (sys.setrecursionlimit).",
        "en": "A function calls itself. It needs a base case. The default depth is about 1000 (sys.setrecursionlimit)."
      },
      "body": {
        "ru": "У Python нет оптимизации хвостовой рекурсии, поэтому превышение глубины даёт RecursionError, а не тихое зависание — для глубоких случаев надёжнее цикл. Если рекурсия перевычисляет одни и те же подзадачи (как наивный fib), оберни её в functools.lru_cache — сложность падает с экспоненциальной до линейной.",
        "en": "Python has no tail-call optimization, so exceeding the depth raises RecursionError rather than hanging silently — for deep cases a loop is safer. If the recursion recomputes the same subproblems (like naive fib), wrap it in functools.lru_cache — that cuts the cost from exponential to linear."
      },
      "syntax": "def f(n):\n    if n == 0: return 1\n    return n * f(n-1)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/sys.html#sys.setrecursionlimit",
      "version": "",
      "section": "Функции",
      "subcat": "рекурсия",
      "color_group": "op",
      "aliases": [
        "рекурсивная функция",
        "функция вызывает саму себя",
        "базовый случай рекурсии"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "def factorial(n):",
        "if n == 0:",
        "return 1",
        "return n * factorial(n - 1)",
        "print(factorial(5))  # → 120",
        "def fib(n):",
        "if n <= 1:",
        "return n",
        "return fib(n-1) + fib(n-2)",
        "print(fib(7))  # → 13",
        "def sum_list(lst):",
        "if not lst:",
        "return 0",
        "return lst[0] + sum_list(lst[1:])",
        "print(sum_list([1,2,3,4]))  # → 10",
        "def power(base, exp):",
        "if exp == 0:",
        "return 1",
        "return base * power(base, exp-1)",
        "print(power(2, 10))  # → 1024",
        "import sys",
        "print(sys.getrecursionlimit())  # → 1000",
        "sys.setrecursionlimit(2000)",
        "def count_down(n):",
        "if n <= 0:",
        "return",
        "print(n, end=' ')",
        "count_down(n-1)",
        "count_down(5)  # → 5 4 3 2 1",
        "def flatten(lst):",
        "result = []",
        "for item in lst:",
        "if isinstance(item, list):",
        "result.extend(flatten(item))",
        "else:",
        "result.append(item)",
        "return result",
        "print(flatten([1,[2,[3]],4]))  # → [1,2,3,4]"
      ],
      "related": [
        "recursionerror",
        "sys.setrecursionlimit",
        "числа-фибоначчи",
        "functools.lru_cache"
      ],
      "related_errors": []
    },
    {
      "id": "break",
      "title": "break",
      "kind": "construct",
      "summary": {
        "ru": "Оператор немедленного выхода из ближайшего цикла. При наличии else-блока у цикла — он НЕ выполняется после break.",
        "en": "Statement that leaves the nearest enclosing loop immediately. If the loop has an else block, it is NOT executed after a break."
      },
      "body": {
        "ru": "break выходит только из одного — самого внутреннего — цикла; выпрыгнуть сразу из нескольких вложенных нельзя, нужен флаг, вынос в функцию с return или исключение. Он естественно сочетается с else у цикла: else срабатывает, только если цикл дошёл до конца ни разу не встретив break — классическая идиома «не нашлось».",
        "en": "break leaves only the single innermost loop; you can't jump straight out of several nested loops — use a flag, refactor into a function with return, or raise an exception. It pairs naturally with a loop's else: the else runs only if the loop finished without ever hitting break — the classic 'not found' idiom."
      },
      "syntax": "break",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/simple_stmts.html#break",
      "version": "",
      "section": "Циклы",
      "subcat": "управление",
      "color_group": "op",
      "aliases": [
        "выйти из цикла",
        "прервать цикл",
        "остановить цикл"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "for i in range(10):",
        "    if i == 5:",
        "        break",
        "print(i)  # → 5",
        "lst = [1, 3, 7, 4, 9]",
        "for x in lst:",
        "    if x % 2 == 0:",
        "        print(\"первое чётное:\", x)  # → первое чётное: 4",
        "        break",
        "found = False",
        "target = 7",
        "for x in lst:",
        "    if x == target:",
        "        found = True",
        "        break",
        "print(found)  # → True",
        "i = 0",
        "while True:",
        "    i += 1",
        "    if i >= 3:",
        "        break",
        "print(i)  # → 3"
      ],
      "related": [
        "continue",
        "else-в-циклах",
        "while"
      ],
      "related_errors": []
    },
    {
      "id": "continue",
      "title": "continue",
      "kind": "construct",
      "summary": {
        "ru": "Оператор пропуска оставшейся части тела цикла и перехода к следующей итерации.",
        "en": "Statement that skips the rest of the loop body and moves on to the next iteration."
      },
      "body": {
        "ru": "Осторожно в while: continue перескакивает сразу к проверке условия, минуя всё, что ниже — если счётчик увеличивается после continue, цикл зациклится навсегда. В for такой ловушки нет: итератор сам двигается к следующему элементу.",
        "en": "Watch out in while loops: continue jumps straight back to the condition check, skipping everything below it — if you increment the counter after the continue, the loop hangs forever. In a for loop there's no such trap: the iterator advances to the next item on its own."
      },
      "syntax": "continue",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/simple_stmts.html#continue",
      "version": "",
      "section": "Циклы",
      "subcat": "управление",
      "color_group": "op",
      "aliases": [
        "пропустить итерацию",
        "перейти к следующей итерации",
        "пропустить элемент в цикле"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "for i in range(10):",
        "    if i % 2 == 0:",
        "        continue",
        "print(i, end=\" \")  # → 1 3 5 7 9",
        "for x in [1, 0, 3, 0, 5]:",
        "    if x == 0:",
        "        continue",
        "print(100 // x, end=\" \")  # → 100 33 20 (без деления на 0)",
        "for ch in \"hello world\":",
        "    if ch == \" \":",
        "        continue",
        "print(ch, end=\"\")  # → helloworld",
        "total = 0",
        "for n in range(1, 11):",
        "    if n % 3 == 0:",
        "        continue",
        "total += n",
        "print(total)  # → 37 (сумма 1..10 без кратных 3)"
      ],
      "related": [
        "break",
        "for",
        "else-в-циклах"
      ],
      "related_errors": []
    },
    {
      "id": "else-в-циклах",
      "title": "else в циклах",
      "kind": "construct",
      "summary": {
        "ru": "Блок else у цикла выполняется, только если цикл завершился БЕЗ break. Удобен для паттерна «поиск с флагом».",
        "en": "A loop's else block runs only if the loop finished WITHOUT a break. Handy for the 'search with a flag' pattern."
      },
      "body": {
        "ru": "Ключ к пониманию — читать не «иначе», а «если не было break»: блок else отрабатывает при нормальном завершении цикла (в том числе когда цикл вообще не пошёл из-за пустого итерируемого) и пропускается только при break. Типичная ошибка — считать, что else привязан к if внутри цикла; на самом деле он относится к самому for/while.",
        "en": "Read it not as \"else\" but as \"if no break\": the else block runs when the loop ends normally (including when it never started because the iterable was empty) and is skipped only on break. A common mistake is tying else to the if inside the loop — it actually belongs to the for/while itself."
      },
      "syntax": "for/while ...:\n    ...\nelse:\n    блок_если_не_break",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/tutorial/controlflow.html#else-clauses-on-loops",
      "version": "",
      "section": "Циклы",
      "subcat": "управление",
      "color_group": "op",
      "aliases": [
        "блок после цикла",
        "цикл завершился без прерывания",
        "поиск с флагом"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "for i in range(2, 10):",
        "if 15 % i == 0:",
        "print(f\"15 делится на {i}\")  # → 15 делится на 3",
        "break",
        "else:",
        "print(\"15 простое\")  # не выполнится",
        "target = 99",
        "for x in [1, 2, 3]:",
        "if x == target:",
        "break",
        "else:",
        "print(\"не найдено\")  # → не найдено",
        "i = 0",
        "while i < 3:",
        "i += 1",
        "else:",
        "print(\"while завершён штатно\")  # → while завершён штатно"
      ],
      "related": [
        "break",
        "else-в-try-except",
        "for"
      ],
      "related_errors": []
    },
    {
      "id": "enumerate",
      "title": "enumerate()",
      "kind": "function",
      "summary": {
        "ru": "Встроенная функция, добавляющая счётчик к итерируемому объекту. Возвращает кортежи (индекс, значение). Параметр start задаёт начальный индекс.",
        "en": "Built-in function that adds a counter to an iterable. It returns (index, value) tuples. The start parameter sets the initial index."
      },
      "body": {
        "ru": "enumerate избавляет от неуклюжего обхода через range(len(...)) с ручным доступом по индексу — сразу отдаёт пару (индекс, значение). Возвращает ленивый итератор, а не список, поэтому оборачивать в list() стоит, только если результат действительно нужен целиком.",
        "en": "enumerate frees you from the clumsy range(len(...)) walk with manual indexing — it hands you the (index, value) pair directly. It returns a lazy iterator, not a list, so wrap it in list() only when you actually need the whole result at once."
      },
      "syntax": "enumerate(iterable, start=0)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#enumerate",
      "version": "",
      "section": "Циклы",
      "subcat": "итерация",
      "color_group": "op",
      "aliases": [
        "индекс в цикле",
        "номер элемента",
        "индекс и значение одновременно"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "fruits = [\"яблоко\", \"банан\", \"вишня\"]",
        "for i, fruit in enumerate(fruits):",
        "    print(i, fruit)  # 0 яблоко / 1 банан / 2 вишня",
        "    for i, fruit in enumerate(fruits, start=1):",
        "        print(f\"{i}. {fruit}\")  # 1. яблоко / 2. банан / 3. вишня",
        "        lst = [10, 20, 30, 40]",
        "for i, v in enumerate(lst):",
        "    if v == 20:",
        "        print(\"индекс 20:\", i)  # → индекс 20: 1",
        "        nums = [5, 3, 8, 1]",
        "        max_i, max_v = max(enumerate(nums), key=lambda x: x[1])",
        "        print(max_i, max_v)  # → 2 8",
        "        print(dict(enumerate(\"abc\")))  # → {0: 'a', 1: 'b', 2: 'c'}"
      ],
      "related": [
        "zip",
        "reversed"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "for",
      "title": "for",
      "kind": "construct",
      "summary": {
        "ru": "Цикл перебора элементов итерируемого объекта. Выполняет тело цикла для каждого элемента: списка, строки, range, словаря и т.д.",
        "en": "A loop over the items of an iterable. It runs the loop body for every item: of a list, a string, a range, a dictionary and so on."
      },
      "body": {
        "ru": "Переменная цикла не исчезает после его конца — она сохраняет последнее значение и остаётся видимой в остальном коде. Вторая частая ловушка — менять список (добавлять или удалять элементы) прямо во время обхода: итерация собьётся, безопаснее идти по копии или собирать новый список.",
        "en": "The loop variable does not vanish when the loop ends — it keeps its last value and stays visible in the surrounding code. The other common trap is mutating a list (adding or removing items) while iterating over it: the iteration goes wrong, so walk over a copy or build a new list instead."
      },
      "syntax": "for переменная in итерируемое:\n    блок",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#for",
      "version": "",
      "section": "Циклы",
      "subcat": "цикл for",
      "color_group": "op",
      "aliases": [
        "перебрать элементы списка",
        "цикл по элементам",
        "пройтись по строке посимвольно"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "for x in [1, 2, 3]:",
        "    print(x, end=\" \")  # → 1 2 3",
        "    for ch in \"abc\":",
        "        print(ch, end=\"\")  # → abc",
        "    for i in range(5):",
        "        print(i, end=\" \")  # → 0 1 2 3 4",
        "        d = {\"a\": 1, \"b\": 2}",
        "    for k, v in d.items():",
        "        print(f\"{k}:{v}\", end=\" \")  # → a:1 b:2",
        "    for i, val in enumerate([\"x\", \"y\", \"z\"], start=1):",
        "        print(f\"{i}.{val}\", end=\" \")  # → 1.x 2.y 3.z",
        "    for a, b in zip([1, 2], [10, 20]):",
        "        print(a + b, end=\" \")  # → 11 22"
      ],
      "related": [
        "while",
        "range",
        "enumerate",
        "break"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "for-...-in-reversed",
      "title": "for ... in reversed()",
      "kind": "construct",
      "summary": {
        "ru": "reversed() возвращает итератор, обходящий последовательность в обратном порядке. Работает со списками, кортежами, строками, range и любыми объектами с __reversed__.",
        "en": "reversed() returns an iterator that walks a sequence backwards. It works with lists, tuples, strings, ranges and any object with __reversed__."
      },
      "body": {
        "ru": "В отличие от среза sequence[::-1], reversed() ничего не копирует, а возвращает ленивый итератор — экономит память на длинных последовательностях. Именно поэтому ему нужна настоящая последовательность (с длиной и индексацией либо с __reversed__): обычный генератор или файловый объект в reversed() не завернуть.",
        "en": "Unlike the slice sequence[::-1], reversed() copies nothing and returns a lazy iterator, saving memory on long sequences. That is also why it needs a real sequence (with a length and indexing, or __reversed__): you cannot pass a plain generator or a file object to reversed()."
      },
      "syntax": "for item in reversed(sequence): ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#reversed",
      "version": "",
      "section": "Циклы",
      "subcat": "цикл for",
      "color_group": "op",
      "aliases": [
        "обход в обратном порядке",
        "перебрать список с конца",
        "цикл в обратную сторону"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "for x in reversed([1,2,3]): print(x, end=\" \")  # → 3 2 1",
        "print(list(reversed(range(5))))   # → [4, 3, 2, 1, 0]",
        "for ch in reversed(\"abc\"): print(ch, end=\"\")  # → cba",
        "t = (10, 20, 30)",
        "for v in reversed(t): print(v, end=\" \")  # → 30 20 10",
        "print(list(reversed([\"a\",\"b\",\"c\"])))  # → ['c', 'b', 'a']"
      ],
      "related": [
        "reversed",
        "срезы-с-шагом-2-1",
        "list.reverse",
        "range"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "range",
      "title": "range()",
      "kind": "term",
      "summary": {
        "ru": "Неизменяемая последовательность целых чисел: не хранит все числа в памяти, а вычисляет их на лету. Используется в for-циклах и срезах.",
        "en": "An immutable sequence of integers: it does not hold all the numbers in memory but computes them on the fly. Used in for loops and in slices."
      },
      "body": {
        "ru": "range — не генератор, а полноценная неизменяемая последовательность: её можно индексировать, срезать, спрашивать len() и проходить сколько угодно раз (генератор исчерпался бы после первого прохода). Проверка x in range(...) для целых при этом идёт за O(1) — Python считает по арифметике, а не перебирает числа.",
        "en": "range is not a generator but a full immutable sequence: you can index it, slice it, call len() on it and iterate over it as many times as you like (a generator would be exhausted after the first pass). And x in range(...) with an integer runs in O(1) — Python does the arithmetic instead of scanning every number."
      },
      "syntax": "range(stop) | range(start, stop) | range(start, stop, step)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#func-range",
      "version": "",
      "section": "Циклы",
      "subcat": "range",
      "color_group": "op",
      "aliases": [
        "диапазон чисел",
        "цикл от 1 до n",
        "числа с шагом"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "print(list(range(5)))  # → [0, 1, 2, 3, 4]",
        "print(list(range(2, 8)))  # → [2, 3, 4, 5, 6, 7]",
        "print(list(range(0, 10, 2)))  # → [0, 2, 4, 6, 8]",
        "print(list(range(10, 0, -2)))  # → [10, 8, 6, 4, 2]",
        "print(list(range(5, 5)))  # → [] (пустой при start==stop)",
        "print(list(range(-3, 3)))  # → [-3, -2, -1, 0, 1, 2]",
        "print(sum(range(101)))  # → 5050 (сумма 1..100)",
        "for i in range(3, 0, -1):",
        "    print(i, end=\" \")  # → 3 2 1"
      ],
      "related": [
        "for",
        "len",
        "enumerate",
        "for-...-in-reversed"
      ],
      "related_errors": []
    },
    {
      "id": "while",
      "title": "while",
      "kind": "construct",
      "summary": {
        "ru": "Цикл с предусловием. Выполняет тело, пока условие истинно. Требует явного изменения условия, иначе — бесконечный цикл.",
        "en": "A loop with a pre-condition. It runs its body while the condition is true. The condition has to be changed explicitly, otherwise the loop never ends."
      },
      "body": {
        "ru": "Главное отличие от for: while берут, когда число повторений заранее неизвестно — читать до стоп-значения, крутить до сходимости, ждать события; для перебора готовой последовательности берут for. Малоизвестно, что у while есть ветка else — она срабатывает, когда цикл завершился сам по условию, а не через break.",
        "en": "Reach for while rather than for when you don't know the iteration count ahead of time — reading until a sentinel, looping until convergence, waiting for an event; use for to walk a ready-made sequence. Little-known: while has an else branch that runs only when the loop ends on its condition, never after a break."
      },
      "syntax": "while условие:\n    блок",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#while",
      "version": "",
      "section": "Циклы",
      "subcat": "цикл while",
      "color_group": "op",
      "aliases": [
        "цикл с условием",
        "цикл пока условие истинно",
        "бесконечный цикл"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "i = 0",
        "while i < 5:",
        "print(i, end=\" \"); i += 1  # → 0 1 2 3 4",
        "total = 0; n = 10",
        "while n > 0:",
        "total += n; n -= 1",
        "print(total)  # → 55 (сумма 1..10)",
        "lst = [3, 7, 1, 9, 2]",
        "i = 0",
        "while i < len(lst) and lst[i] != 9:",
        "i += 1",
        "print(i)  # → 3 (индекс элемента 9)",
        "count = 0",
        "while True:",
        "count += 1",
        "if count == 5:",
        "break",
        "print(count)  # → 5"
      ],
      "related": [
        "for",
        "break",
        "continue",
        "else-в-циклах"
      ],
      "related_errors": []
    },
    {
      "id": "zip",
      "title": "zip()",
      "kind": "function",
      "summary": {
        "ru": "Встроенная функция, объединяющая несколько итерируемых объектов в один. Возвращает кортежи из соответствующих элементов. Останавливается по наименьшему.",
        "en": "Built-in function that joins several iterables into one. It returns tuples of the corresponding items. It stops at the shortest one."
      },
      "body": {
        "ru": "В Python 3 zip() возвращает ленивый итератор, а не список, и исчерпывается после первого прохода — чтобы пройти дважды или обратиться по индексу, оберните его в list(). Идиома zip(*pairs) распаковывает список пар обратно в отдельные последовательности, а на строках матрицы работает как транспонирование.",
        "en": "In Python 3 zip() returns a lazy iterator, not a list, and is used up after one pass — wrap it in list() if you need to iterate twice or index into it. The zip(*pairs) idiom unzips a list of pairs back into separate sequences, and applied to a matrix's rows it transposes it."
      },
      "syntax": "zip(*iterables)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#zip",
      "version": "",
      "section": "Циклы",
      "subcat": "итерация",
      "color_group": "op",
      "aliases": [
        "объединить два списка попарно",
        "параллельный перебор списков",
        "пары из двух списков"
      ],
      "keywords": [],
      "tags": [
        "op",
        "builtin"
      ],
      "examples": [
        "names = [\"Анна\", \"Борис\", \"Вера\"]",
        "scores = [95, 78, 88]",
        "for name, score in zip(names, scores):",
        "    print(f\"{name}: {score}\")  # Анна: 95 / Борис: 78 / Вера: 88",
        "    a, b, c = [1, 2], [3, 4], [5, 6]",
        "    for x, y, z in zip(a, b, c):",
        "        print(x + y + z, end=\" \")  # → 9 12",
        "        d = dict(zip([\"a\", \"b\", \"c\"], [1, 2, 3]))",
        "        print(d)  # → {\"a\": 1, \"b\": 2, \"c\": 3}",
        "        from itertools import zip_longest",
        "        result = list(zip_longest([1, 2, 3], [10, 20], fillvalue=0))",
        "        print(result)  # → [(1, 10), (2, 20), (3, 0)]",
        "        # словарь из двух списков",
        "        print(dict(zip(['a','b','c'], [1,2,3])))  # → {'a':1,'b':2,'c':3}",
        "        print(list(zip([1, 2, 3], [4, 5])))  # → [(1, 4), (2, 5)] (по короткому)"
      ],
      "related": [
        "enumerate",
        "reversed"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "zip-strict-true",
      "title": "zip(strict=True)",
      "kind": "function",
      "summary": {
        "ru": "Параметр strict=True (Python 3.10+) заставляет zip() выбрасывать ValueError, если переданные итерируемые объекты имеют разную длину. Без strict= zip молча останавливается на самом коротком.",
        "en": "The strict=True parameter (Python 3.10+) makes zip() raise ValueError if the iterables passed have different lengths. Without strict= zip silently stops at the shortest one."
      },
      "body": {
        "ru": "Ставьте strict=True всякий раз, когда равная длина — это инвариант, на который вы рассчитываете: молчаливая потеря хвоста превращается в громкую ошибку прямо в месте бага. Учтите, что проверка ленивая: ValueError вылетает не при вызове zip(), а по ходу итерации — в момент, когда более короткий объект исчерпался.",
        "en": "Turn on strict=True whenever equal length is an invariant you rely on: a silently dropped tail becomes a loud error right where the bug is. Note the check is lazy — the ValueError fires not when you call zip() but mid-iteration, the moment the shorter iterable runs out."
      },
      "syntax": "zip(*iterables, strict=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/functions.html#zip",
      "version": "3.10",
      "section": "Циклы",
      "subcat": "итерация",
      "color_group": "op",
      "aliases": [
        "проверка одинаковой длины списков",
        "ошибка при разной длине списков"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "print(list(zip([1,2],[\"a\",\"b\"])))      # → [(1, 'a'), (2, 'b')]",
        "print(list(zip([1,2,3],[\"a\",\"b\"])))    # → [(1, 'a'), (2, 'b')]  (3 потеряна)",
        "try:",
        "list(zip([1,2,3],[\"a\",\"b\"], strict=True))",
        "except ValueError as e:",
        "print(\"ValueError:\", e)  # → ValueError: zip() has arguments...",
        "print(list(zip([1,2],[\"a\",\"b\"], strict=True)))  # → [(1, 'a'), (2, 'b')]"
      ],
      "related": [
        "zip",
        "itertools.zip_longest",
        "valueerror"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "вложенные-циклы",
      "title": "Вложенные циклы",
      "kind": "construct",
      "summary": {
        "ru": "Цикл внутри другого цикла. Внутренний цикл выполняется полностью на каждой итерации внешнего. break/continue работают только с ближайшим циклом.",
        "en": "A loop inside another loop. The inner loop runs in full on every iteration of the outer one. break/continue affect only the nearest loop."
      },
      "body": {
        "ru": "Следите за сложностью: вложенность перемножает длины, и два цикла по n элементов — это уже O(n²), которое быстро становится узким местом. В Python нет метки для break, поэтому выйти сразу из всех уровней нельзя — заводят флаг либо выносят циклы в отдельную функцию и делают return.",
        "en": "Mind the complexity: nesting multiplies the lengths, so two loops over n items are already O(n²), which becomes a bottleneck fast. Python has no labelled break, so you can't jump out of every level at once — use a flag, or move the loops into a function and return."
      },
      "syntax": "for i in ...:\n    for j in ...:\n        блок",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#the-for-statement",
      "version": "",
      "section": "Циклы",
      "subcat": "вложенность",
      "color_group": "op",
      "aliases": [
        "цикл в цикле",
        "двойной цикл",
        "перебор двумерного списка"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "for i in range(1, 4):",
        "for j in range(1, 4):",
        "print(f\"{i}*{j}={i*j}\", end=\"  \")",
        "print()  # таблица умножения 3x3",
        "matrix = [[1, 2, 3], [4, 5, 6]]",
        "for row in matrix:",
        "for val in row:",
        "print(val, end=\" \")  # → 1 2 3 4 5 6",
        "found = False",
        "for i in range(3):",
        "for j in range(3):",
        "if i == 1 and j == 1:",
        "found = True",
        "break",
        "if found:",
        "break",
        "print(found)  # → True",
        "pairs = [(i, j) for i in range(3) for j in range(3) if i != j]",
        "print(len(pairs))  # → 6"
      ],
      "related": [
        "for",
        "вложенные-списки-матрицы",
        "break",
        "itertools.product"
      ],
      "related_errors": [
        "TypeError",
        "IndexError"
      ]
    },
    {
      "id": "обработка-цифр-числа",
      "title": "Обработка цифр числа",
      "kind": "construct",
      "summary": {
        "ru": "Паттерн перебора цифр числа через строковое преобразование или деление на 10. Используется для задач: сумма цифр, произведение, подсчёт, максимальная цифра.",
        "en": "The pattern for walking the digits of a number: convert it to a string, or divide by 10 repeatedly. Used for digit sum, product, counting and largest-digit problems."
      },
      "body": {
        "ru": "Вариант while n > 0 вообще не заходит в тело при n = 0 и даёт пустой результат — ноль приходится обрабатывать отдельно. Для отрицательных чисел нужен abs(): в str(n) минус станет символом '-', на котором int(d) упадёт, а % и // с отрицательными работают не так, как обычно ждут.",
        "en": "The while n > 0 version never enters the body when n = 0 and yields nothing — zero has to be special-cased. Negative numbers need abs(): str(n) puts a '-' in the string that int(d) chokes on, and % and // behave counterintuitively on negatives."
      },
      "syntax": "for d in str(abs(n)): | while n > 0: d = n % 10; n //= 10",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex",
      "version": "",
      "section": "Циклы",
      "subcat": "практика",
      "color_group": "op",
      "aliases": [
        "сумма цифр числа",
        "разбить число на цифры",
        "количество цифр в числе"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "n = 12345",
        "digit_sum = sum(int(d) for d in str(n))",
        "print(digit_sum)  # → 15",
        "product = 1",
        "for d in str(n):",
        "    product *= int(d)",
        "    print(product)  # → 120",
        "    count = len(str(abs(n)))",
        "    print(count)  # → 5 (количество цифр)",
        "    max_digit = max(int(d) for d in str(n))",
        "    print(max_digit)  # → 5",
        "    n2 = 4321",
        "    reversed_n = int(str(n2)[::-1])",
        "    print(reversed_n)  # → 1234 (перевёрнутое число)"
      ],
      "related": [
        "остаток",
        "целочисленное-деление",
        "divmod",
        "системы-счисления"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "распаковка-в-for",
      "title": "Распаковка в for",
      "kind": "construct",
      "summary": {
        "ru": "При итерации по последовательности кортежей (или других итерируемых) можно сразу распаковывать элементы в несколько переменных прямо в заголовке for.",
        "en": "When iterating over a sequence of tuples (or of other iterables) you can unpack the items into several variables right in the for header."
      },
      "body": {
        "ru": "Число переменных в заголовке должно точно совпадать с числом элементов в каждом кортеже — иначе на этой итерации вылетит ValueError (not enough / too many values to unpack), причём уже в рантайме, а не при разборе кода. Если длина «хвоста» плавает, выручает звёздочка: for first, *rest in ... соберёт остаток в список. А вложенная распаковка вроде for i, (x, y) in enumerate(...) требует скобок вокруг внутренней пары — без них Python будет ждать три отдельные переменные.",
        "en": "The count of target variables must exactly match the number of items in each tuple, or that iteration raises ValueError (not enough / too many values to unpack) — at runtime, not at parse time. When the tail length varies, a star helps: for first, *rest in ... collects the remainder into a list. Nested unpacking like for i, (x, y) in enumerate(...) needs the parentheses around the inner pair, otherwise Python expects three separate variables."
      },
      "syntax": "for x, y in iterable: ...\nfor a, b, c in iterable: ...",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/reference/compound_stmts.html#the-for-statement",
      "version": "",
      "section": "Циклы",
      "subcat": "цикл for",
      "color_group": "op",
      "aliases": [
        "две переменные в цикле",
        "перебор пар значений"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "for x, y in [(1,2),(3,4),(5,6)]:",
        "    print(x+y, end=\" \")  # → 3 7 11",
        "    pairs = {\"a\":1,\"b\":2,\"c\":3}",
        "    for k, v in pairs.items():",
        "        print(f\"{k}={v}\", end=\" \")  # → a=1 b=2 c=3",
        "    for i, (x,y) in enumerate([(1,2),(3,4)]):",
        "        print(i, x, y)  # → 0 1 2  затем  1 3 4",
        "    for a, *rest in [(1,2,3),(4,5,6)]:",
        "        print(a, rest)  # → 1 [2, 3]  затем  4 [5, 6]"
      ],
      "related": [
        "enumerate",
        "zip",
        "распаковка-кортежа",
        "dict.items"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "cmath",
      "title": "cmath",
      "kind": "term",
      "summary": {
        "ru": "Модуль cmath предоставляет математические функции для комплексных чисел.",
        "en": "The cmath module provides mathematical functions for complex numbers."
      },
      "body": {
        "ru": "cmath всегда возвращает complex, даже когда результат по сути вещественный: cmath.sqrt(4) даст (2+0j), и передать это туда, где ждут float, уже не получится. Берите его ровно там, где math падает с ValueError — корень или логарифм отрицательного числа; сравнивать комплексные через < или > нельзя, будет TypeError, упорядочены только их модули через abs().",
        "en": "cmath always hands back a complex, even when the answer is really a real number: cmath.sqrt(4) is (2+0j), which will not slot into code expecting a float. Reach for it exactly where math raises ValueError — roots and logs of negative numbers; and complex values cannot be ordered, so < or > raises TypeError and you have to compare abs() instead."
      },
      "syntax": "import cmath\ncmath.sqrt(-1)  # → 1j",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/cmath.html",
      "version": "",
      "section": "Числа и математика",
      "subcat": "cmath",
      "color_group": "op",
      "aliases": [
        "комплексные числа",
        "корень из отрицательного числа",
        "мнимая единица"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "import cmath",
        "cmath.sqrt(-1) # → 1j",
        "cmath.sqrt(-4) # → 2j",
        "cmath.phase(1+1j) # → 0.785... (π/4)",
        "cmath.polar(1+1j) # → (1.414..., 0.785...)",
        "cmath.exp(1j * cmath.pi) # → (-1+0j)  формула Эйлера"
      ],
      "related": [
        "complex",
        "math.sqrt",
        "complex.conjugate"
      ],
      "related_errors": []
    },
    {
      "id": "complex.conjugate",
      "title": "complex.conjugate",
      "kind": "function",
      "summary": {
        "ru": "Возвращает комплексно-сопряжённое: меняет знак мнимой части (a+bj → a−bj). Ключевая операция над комплексными числами.",
        "en": "Return the complex conjugate: flip the sign of the imaginary part (a+bj → a−bj)."
      },
      "body": {
        "ru": "Следите за скобками: real и imag — атрибуты, а conjugate — метод, и без () вы получите не число, а объект связанного метода. Тот же метод есть у int и float (возвращает само значение), поэтому числовой код может звать его, не выясняя предварительно тип. Произведение z * z.conjugate() даёт квадрат модуля, но остаётся complex с нулевой мнимой частью — если нужен обычный float, берите abs(z) ** 2 или .real.",
        "en": "Watch the parentheses: real and imag are attributes, but conjugate is a method, so dropping the () hands you a bound method object instead of a number. int and float carry the same method (it returns the value unchanged), so numeric code can call it without checking the type first. z * z.conjugate() is the squared modulus, yet it stays a complex with a zero imaginary part — use abs(z) ** 2 or .real when you want a plain float."
      },
      "syntax": "z.conjugate()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex",
      "version": "",
      "section": "Числа и математика",
      "subcat": "комплексные",
      "color_group": "op",
      "aliases": [
        "комплексно-сопряжённое число",
        "сопряжённое число",
        "смена знака мнимой части"
      ],
      "keywords": [],
      "tags": [
        "complex"
      ],
      "examples": [
        "print((3 + 4j).conjugate())                 # → (3-4j)",
        "print(complex(2, -5).conjugate())           # → (2+5j)",
        "print((3 + 4j) * (3 + 4j).conjugate())      # → (25+0j)",
        "print((3 + 4j).conjugate().imag)            # → -4.0",
        "print((2 - 3j).conjugate().conjugate())     # → (2-3j)",
        "print((7 + 0j).conjugate())                 # → (7-0j)"
      ],
      "related": [
        "complex",
        "cmath",
        "float.conjugate"
      ],
      "related_errors": []
    },
    {
      "id": "complex.from_number",
      "title": "complex.from_number()",
      "kind": "function",
      "summary": {
        "ru": "Классметод: строит комплексное число из числового аргумента (делегирует __complex__, затем __float__, затем __index__). В отличие от complex(), строку не принимает. Python 3.14+.",
        "en": "Class method that builds a complex number from a single numeric argument (delegates to __complex__, then __float__, then __index__). Unlike complex(), it does not accept strings. Python 3.14+."
      },
      "body": {
        "ru": "Метод появился только в 3.14 — на более старых интерпретаторах, включая многие проверяющие системы, обращение к нему упадёт с AttributeError, так что для переносимости остаётся обычный complex(x). Смысл from_number — строгость: ровно один числовой аргумент, а на строку он отвечает TypeError, тогда как complex('1+2j') молча разберёт текст, что удобно при вводе и опасно, когда вы ждали именно число.",
        "en": "The method landed in 3.14, so on older interpreters — graders included — touching it raises AttributeError; plain complex(x) stays the portable choice. Its whole point is strictness: exactly one numeric argument, and a string gets a TypeError, whereas complex('1+2j') happily parses text — handy for user input, dangerous when you expected a number."
      },
      "syntax": "classmethod complex.from_number(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#complex.from_number",
      "version": "3.14",
      "section": "Числа и математика",
      "subcat": "комплексные числа",
      "color_group": "op",
      "aliases": [
        "создать комплексное число из числа",
        "преобразовать число в комплексное",
        "комплексное число из числа"
      ],
      "keywords": [
        "complex.from_number",
        "from_number"
      ],
      "tags": [
        "complex"
      ],
      "examples": [
        "print(complex.from_number(3))  # → (3+0j)",
        "print(complex.from_number(2.5))  # → (2.5+0j)",
        "print(complex.from_number(1+2j))  # → (1+2j)",
        "print(complex.from_number(True))  # → (1+0j)",
        "try: complex.from_number('1+2j')  # строка не число",
        "except TypeError as e: print(type(e).__name__)  # → TypeError"
      ],
      "related": [
        "complex",
        "complex.conjugate",
        "cmath",
        "classmethod"
      ],
      "related_errors": [
        "TypeError"
      ]
    },
    {
      "id": "decimal.decimal",
      "title": "decimal.Decimal",
      "kind": "term",
      "summary": {
        "ru": "Точная десятичная арифметика без ошибок float. Незаменима в финансовых вычислениях.",
        "en": "Exact decimal arithmetic, free of float error. Indispensable in financial calculations."
      },
      "body": {
        "ru": "Создавайте Decimal из строки: Decimal(0.1) берёт уже испорченное двоичное значение float и честно печатает его во всех подробностях, а Decimal('0.1') — ровно одну десятую. Смешивать Decimal с float в арифметике Python откажется (TypeError), с int — пожалуйста; и точность по умолчанию — 28 значащих цифр контекста, поэтому деление вроде 10/3 всё же округляется, просто предсказуемо и в десятичной системе.",
        "en": "Build a Decimal from a string: Decimal(0.1) swallows the already-inexact binary float and prints it in full gory detail, while Decimal('0.1') is exactly one tenth. Arithmetic between Decimal and float is a TypeError (with int it is fine), and the default context keeps 28 significant digits, so a division like 10/3 still gets rounded — just predictably, in decimal."
      },
      "syntax": "from decimal import Decimal\nDecimal('1.1') + Decimal('2.2')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.Decimal",
      "version": "",
      "section": "Числа и математика",
      "subcat": "decimal",
      "color_group": "op",
      "aliases": [
        "точная десятичная арифметика",
        "почему 0.1 + 0.2 не равно 0.3",
        "вычисления с деньгами"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "from decimal import Decimal",
        "Decimal('1.1') + Decimal('2.2') # → Decimal('3.3')",
        "1.1 + 2.2 # → 3.3000000000000003",
        "Decimal('10') / Decimal('3') # → Decimal('3.333...3')",
        "Decimal('0.1') * 3 == Decimal('0.3') # → True",
        "Decimal(1) / Decimal(7) # → Decimal('0.1428...')  (28 цифр)"
      ],
      "related": [
        "fractions.fraction",
        "decimal.getcontext",
        "float",
        "math.isclose"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.getcontext",
      "title": "decimal.getcontext()",
      "kind": "function",
      "summary": {
        "ru": "Возвращает текущий контекст Decimal для этого потока. Его поля prec (точность) и rounding можно менять прямо на месте — они влияют на все операции.",
        "en": "Return the current thread's Decimal context; its prec and rounding fields can be mutated in place and affect every subsequent operation."
      },
      "body": {
        "ru": "prec — это общее число значащих цифр результата, а не знаков после запятой, и на сам конструктор контекст не действует: Decimal('1.23456789') сохранит все цифры, округление случится только при арифметике. Контекст потоко-локальный, так что в новом потоке снова увидите умолчания; а когда точность нужна лишь на пару операций, чище блок with decimal.localcontext(), чем правка полей глобального контекста, которую потом легко забыть откатить.",
        "en": "prec counts significant digits of a result, not digits after the point, and the context does not touch the constructor: Decimal('1.23456789') keeps every digit, and rounding only happens once you do arithmetic. The context is per-thread, so a freshly started thread sees the defaults again; and when you need a different precision for just a few operations, a with decimal.localcontext() block beats poking the global context and forgetting to restore it."
      },
      "syntax": "decimal.getcontext()\ndecimal.getcontext().prec = 50",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.getcontext",
      "version": "",
      "section": "Числа и математика",
      "subcat": "decimal",
      "color_group": "op",
      "aliases": [
        "настройка точности вычислений",
        "число знаков после запятой",
        "режим округления"
      ],
      "keywords": [
        "decimal.getcontext",
        "getcontext"
      ],
      "tags": [
        "op"
      ],
      "examples": [
        "import decimal",
        "ctx = decimal.getcontext()",
        "ctx.prec # → 28 (по умолчанию)",
        "ctx.prec = 5",
        "decimal.Decimal(1) / decimal.Decimal(7) # → Decimal('0.14286')",
        "decimal.getcontext().rounding = decimal.ROUND_HALF_UP"
      ],
      "related": [
        "decimal.setcontext"
      ],
      "related_errors": []
    },
    {
      "id": "decimal.setcontext",
      "title": "decimal.setcontext()",
      "kind": "function",
      "summary": {
        "ru": "Заменяет текущий контекст Decimal целиком на переданный. Удобно, когда нужен заранее собранный Context, а не правка полей по одному.",
        "en": "Replace the current Decimal context wholesale with the given one — useful when you have a prebuilt Context rather than tweaking fields one by one."
      },
      "body": {
        "ru": "Меняется контекст только активного потока — остальные продолжают жить со своим, и восстанавливать прежний после setcontext придётся вручную. Если точность нужна временно, надёжнее with decimal.localcontext(...): он вернёт старый контекст сам; и учтите, что переданный вами объект Context хранится как есть, так что дальнейшая правка его полей сразу подействует на все вычисления.",
        "en": "Only the calling thread's context changes — other threads keep their own, and restoring the previous one after setcontext is your job. For a temporary setting, with decimal.localcontext(...) is safer because it puts the old context back for you; also note that the Context object you pass is kept as is, so mutating its fields later immediately affects every subsequent operation."
      },
      "syntax": "decimal.setcontext(ctx)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/decimal.html#decimal.setcontext",
      "version": "",
      "section": "Числа и математика",
      "subcat": "decimal",
      "color_group": "op",
      "aliases": [
        "установить контекст вычислений",
        "заменить настройки округления целиком"
      ],
      "keywords": [
        "decimal.setcontext",
        "setcontext"
      ],
      "tags": [
        "op"
      ],
      "examples": [
        "import decimal",
        "decimal.setcontext(decimal.Context(prec=10))",
        "decimal.getcontext().prec # → 10",
        "decimal.Decimal(1) / decimal.Decimal(3) # → Decimal('0.3333333333')"
      ],
      "related": [
        "decimal.getcontext"
      ],
      "related_errors": []
    },
    {
      "id": "float.as_integer_ratio",
      "title": "float.as_integer_ratio",
      "kind": "function",
      "summary": {
        "ru": "Возвращает пару (числитель, знаменатель) — точное представление числа как несократимой дроби из целых.",
        "en": "Return a pair (numerator, denominator) exactly representing the float as a ratio of integers."
      },
      "body": {
        "ru": "Дробь описывает то число, которое реально лежит в памяти, а не то, что ты набрал в коде: у 0.1 получатся не 1 и 10, а пара огромных целых, ведь знаменатель всегда степень двойки. Нужна «человеческая» дробь — стройте fractions.Fraction от строки, а не от float. На бесконечности метод бросает OverflowError, на nan — ValueError.",
        "en": "The ratio describes the number actually stored in memory, not the literal you typed: 0.1 comes back as a pair of huge integers rather than 1 and 10, since the denominator is always a power of two. If you want the fraction a human would write, build fractions.Fraction from a string instead of from the float. On infinities the method raises OverflowError, on nan a ValueError."
      },
      "syntax": "x.as_integer_ratio()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#float.as_integer_ratio",
      "version": "",
      "section": "Числа и математика",
      "subcat": "как дробь",
      "color_group": "op",
      "aliases": [
        "числитель и знаменатель",
        "дробное число как обыкновенная дробь"
      ],
      "keywords": [],
      "tags": [
        "float"
      ],
      "examples": [
        "print((0.5).as_integer_ratio())  # → (1, 2)",
        "print((0.25).as_integer_ratio())  # → (1, 4)",
        "print((2.0).as_integer_ratio())  # → (2, 1)"
      ],
      "related": [
        "fractions.fraction",
        "int.as_integer_ratio",
        "float.hex"
      ],
      "related_errors": []
    },
    {
      "id": "float.conjugate",
      "title": "float.conjugate",
      "kind": "function",
      "summary": {
        "ru": "Возвращает комплексно-сопряжённое числа; для float (действительного) — само число. Часть общего числового интерфейса.",
        "en": "Return the complex conjugate; for a float (a real number) it is the number itself."
      },
      "body": {
        "ru": "У действительного числа сопряжение ничего не меняет, так что в обычном коде этот вызов не нужен: он существует ради общего числового протокола вместе с .real и .imag, чтобы формула, написанная для complex, работала и для float без проверок типа. Результат остаётся float — сделать из числа комплексное метод не может, для этого есть complex().",
        "en": "Conjugating a real number changes nothing, so you would not write this call in ordinary code: it exists for the shared numeric protocol alongside .real and .imag, letting a formula written for complex run on floats without type checks. The result stays a float — the method never promotes a number to complex; complex() does that."
      },
      "syntax": "x.conjugate()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex",
      "version": "",
      "section": "Числа и математика",
      "subcat": "как число",
      "color_group": "op",
      "aliases": [],
      "keywords": [],
      "tags": [
        "float"
      ],
      "examples": [
        "print((3.5).conjugate())            # → 3.5",
        "print((-1.0).conjugate())           # → -1.0",
        "print(type((3.5).conjugate()))      # → <class 'float'>",
        "print((2.5).conjugate() == 2.5)     # → True",
        "print((3.5).imag)                   # → 0.0",
        "print(complex(3.5).conjugate())     # → (3.5-0j)"
      ],
      "related": [
        "complex.conjugate",
        "int.conjugate",
        "complex"
      ],
      "related_errors": []
    },
    {
      "id": "float.from_number",
      "title": "float.from_number()",
      "kind": "function",
      "summary": {
        "ru": "Классовый метод: строит float из числового объекта (int, float или объекта с __float__). Строки не принимает — в отличие от float(). Python 3.14+.",
        "en": "Class method building a float from a number (int, float or an object with __float__). Unlike float(), it rejects strings. Python 3.14+."
      },
      "body": {
        "ru": "Метод появился, чтобы отделить «преобразовать число» от «разобрать строку»: float() делает и то и другое, поэтому случайно проглатывает строку, пришедшую из input(). from_number на str и bytes отвечает TypeError, но от потери точности не спасает — Decimal с длинной мантиссой молча округлится, а слишком большое int даст OverflowError.",
        "en": "The method exists to separate \"convert a number\" from \"parse a string\": float() does both, so a string that sneaks in from input() is silently accepted. from_number raises TypeError on str and bytes, yet it does not shield you from precision loss — a Decimal with a long mantissa is rounded, and an int too large for a float raises OverflowError."
      },
      "syntax": "float.from_number(x)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#float.from_number",
      "version": "3.14",
      "section": "Числа и математика",
      "subcat": "преобразование типов",
      "color_group": "op",
      "aliases": [
        "создать вещественное из числа",
        "преобразование числа в вещественное",
        "строгое приведение к вещественному"
      ],
      "keywords": [
        "float.from_number",
        "from_number"
      ],
      "tags": [
        "float"
      ],
      "examples": [
        "print(float.from_number(7))                 # → 7.0",
        "print(float.from_number(-2.5))              # → -2.5",
        "from decimal import Decimal",
        "print(float.from_number(Decimal('1.25')))   # → 1.25",
        "from fractions import Fraction",
        "print(float.from_number(Fraction(1, 4)))    # → 0.25"
      ],
      "related": [
        "float",
        "float.fromhex",
        "float.is_integer",
        "int"
      ],
      "related_errors": [
        "TypeError",
        "OverflowError"
      ]
    },
    {
      "id": "float.fromhex",
      "title": "float.fromhex",
      "kind": "function",
      "summary": {
        "ru": "Классовый метод: разбирает шестнадцатеричную строку (формат float.hex()) обратно в число с плавающей точкой без потерь.",
        "en": "Class method: parse a hexadecimal string (as produced by float.hex()) back into a float."
      },
      "body": {
        "ru": "Все цифры после точки читаются как шестнадцатеричные, а префикс 0x необязателен — поэтому '3.5' здесь не три с половиной, а 3 + 5/16 = 3.3125. Метод нужен ровно для одного: перегнать float между программами бит в бит; для обычного разбора введённого пользователем текста берут float(s). Строка не того формата даёт ValueError, а не молчаливый ноль.",
        "en": "Every digit after the point is hexadecimal and the 0x prefix is optional, so '3.5' here is not three and a half but 3 + 5/16 = 3.3125. The method exists for one job: moving a float between programs bit for bit; for parsing ordinary user text you want float(s). A malformed string raises ValueError rather than quietly returning zero."
      },
      "syntax": "float.fromhex(s)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#float.fromhex",
      "version": "",
      "section": "Числа и математика",
      "subcat": "hex",
      "color_group": "op",
      "aliases": [
        "шестнадцатеричная строка в вещественное число",
        "разобрать шестнадцатеричную запись числа"
      ],
      "keywords": [],
      "tags": [
        "float"
      ],
      "examples": [
        "print(float.fromhex('0x1.8p+1'))            # → 3.0",
        "print(float.fromhex('0x1.0p+0'))            # → 1.0",
        "print(float.fromhex('-0x1p-1'))             # → -0.5",
        "print(float.fromhex('3.5'))                 # → 3.3125",
        "print(float.fromhex((0.1).hex()) == 0.1)    # → True",
        "print(float.fromhex('hello'))               # → ValueError"
      ],
      "related": [
        "float.hex",
        "float",
        "bytes.fromhex"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "float.hex",
      "title": "float.hex",
      "kind": "function",
      "summary": {
        "ru": "Возвращает шестнадцатеричную строку — точное представление float без потерь; парная функция — float.fromhex().",
        "en": "Return a hexadecimal string — a lossless representation of the float; inverse of float.fromhex()."
      },
      "body": {
        "ru": "Метод есть только у float: для целых существует встроенная hex(), а bytes.hex() делает совсем другое. Обычный repr числа в CPython и так восстанавливается без потерь, поэтому .hex() берут не ради сохранности значения, а чтобы увидеть мантиссу и порядок явно — например, разглядеть, что 0.1 хранится чуть больше десятой доли. Формат всегда нормализован: одна цифра до точки и показатель степени двойки после p.",
        "en": "Only floats have this method — integers use the built-in hex(), and bytes.hex() is an unrelated thing. Since repr of a float already round-trips in CPython, .hex() is not about preserving the value but about seeing the significand and the exponent explicitly: it is how you notice that 0.1 is stored slightly above one tenth. The output is always normalised — one digit before the point, a power of two after p."
      },
      "syntax": "x.hex()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#float.hex",
      "version": "",
      "section": "Числа и математика",
      "subcat": "hex",
      "color_group": "op",
      "aliases": [
        "вещественное число в шестнадцатеричную строку",
        "точное представление дробного числа"
      ],
      "keywords": [],
      "tags": [
        "float"
      ],
      "examples": [
        "print((3.5).hex())                          # → 0x1.c000000000000p+1",
        "print((1.0).hex())                          # → 0x1.0000000000000p+0",
        "print((0.1).hex())                          # → 0x1.999999999999ap-4",
        "print((-2.0).hex())                         # → -0x1.0000000000000p+1",
        "print((0.0).hex())                          # → 0x0.0p+0",
        "print(float.fromhex((0.1).hex()) == 0.1)    # → True"
      ],
      "related": [
        "float.fromhex",
        "float.as_integer_ratio",
        "hex"
      ],
      "related_errors": []
    },
    {
      "id": "float.is_integer",
      "title": "float.is_integer",
      "kind": "function",
      "summary": {
        "ru": "Возвращает True, если число с плавающей точкой имеет целое значение (дробная часть равна нулю), иначе False.",
        "en": "Return True if the float has an integral value (no fractional part), else False."
      },
      "body": {
        "ru": "Метод говорит о значении, а не о типе: 4.0 остаётся float, и is_integer() ничего не преобразует — для этого нужен int(). Главная ловушка — округление: результат вещественных вычислений, целый «по математике», часто отличается от целого в последних битах и даёт False, а inf и nan дают False всегда. Наоборот, у очень больших float (по модулю от 2**52) соседние представимые значения отличаются уже не меньше чем на единицу, поэтому там ответ всегда True.",
        "en": "This is a statement about the value, not the type: 4.0 is still a float, and is_integer() converts nothing — use int() for that. The usual trap is rounding: a computed result that is mathematically whole often differs in the last bits and returns False, while inf and nan always return False. At the other end, floats with magnitude 2**52 or larger are spaced at least one apart, so every one of them reports True."
      },
      "syntax": "x.is_integer()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#float.is_integer",
      "version": "",
      "section": "Числа и математика",
      "subcat": "как дробь",
      "color_group": "op",
      "aliases": [
        "проверить, целое ли число",
        "нулевая дробная часть"
      ],
      "keywords": [],
      "tags": [
        "float"
      ],
      "examples": [
        "print((4.0).is_integer())  # → True",
        "print((4.5).is_integer())  # → False",
        "print((-2.0).is_integer())  # → True"
      ],
      "related": [
        "int.is_integer",
        "float.as_integer_ratio",
        "преобразование-типов"
      ],
      "related_errors": []
    },
    {
      "id": "fractions.fraction",
      "title": "fractions.Fraction",
      "kind": "term",
      "summary": {
        "ru": "Представляет рациональные числа точно как пару целых (числитель/знаменатель). Без погрешностей float.",
        "en": "Represents rational numbers exactly, as a pair of integers (numerator and denominator). Without float error."
      },
      "body": {
        "ru": "Fraction(0.1) добросовестно берёт двоичное приближение float и превращается в монстра 3602879701896397/36028797018963968 — из десятичной записи стройте через строку Fraction('0.1') или пару целых. Дробь неизменяема и сокращается сразу при создании, а вот в арифметике с float результат становится float, и вся точность теряется; после длинных цепочек сложений знаменатели разрастаются, и привести дробь к обозримому виду помогает limit_denominator().",
        "en": "Fraction(0.1) faithfully takes the binary approximation of the float and turns into the monster 3602879701896397/36028797018963968 — from decimal text, build it as Fraction('0.1') or from a pair of integers. A Fraction is immutable and is reduced to lowest terms at construction, but mixing it with a float in arithmetic yields a float and throws the exactness away; after long chains of additions the denominators blow up, and limit_denominator() is what brings the value back to a readable approximation."
      },
      "syntax": "from fractions import Fraction\nFraction(1, 3)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/fractions.html#fractions.Fraction",
      "version": "",
      "section": "Числа и математика",
      "subcat": "fractions",
      "color_group": "op",
      "aliases": [
        "дроби",
        "рациональные числа",
        "обыкновенная дробь"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "from fractions import Fraction",
        "Fraction(1, 3) + Fraction(1, 6) # → Fraction(1, 2)",
        "Fraction('0.1') + Fraction('0.2') # → Fraction(3, 10)",
        "Fraction(1, 3) * 3 # → Fraction(1, 1)",
        "Fraction(22, 7) # → Fraction(22, 7)",
        "float(Fraction(1, 3)) # → 0.3333333333333333"
      ],
      "related": [
        "decimal.decimal",
        "float.as_integer_ratio",
        "math.gcd"
      ],
      "related_errors": []
    },
    {
      "id": "int.as_integer_ratio",
      "title": "int.as_integer_ratio",
      "kind": "function",
      "summary": {
        "ru": "Возвращает пару (числитель, знаменатель) с знаменателем 1 — целое как несократимая дробь. Полезно для единообразия с float/Fraction.",
        "en": "Return a pair (numerator, denominator) with denominator 1 — the integer as a ratio."
      },
      "body": {
        "ru": "Сам по себе метод ничего не вычисляет — знаменатель всегда 1; он нужен, чтобы обрабатывать int, float, Decimal и Fraction единообразно, без разветвления по типу. Появился в Python 3.8, на более старых версиях у int его нет. И не ждите «человеческой» дроби от float.as_integer_ratio(): он возвращает точное двоичное представление, поэтому у 0.1 знаменателем окажется огромная степень двойки, а не 10.",
        "en": "The method computes nothing on its own — the denominator is always 1; it exists so that int, float, Decimal and Fraction can be handled through one interface without branching on type. It was added in Python 3.8, so older versions lack it on int. Do not expect a human-looking fraction from float.as_integer_ratio() either: it gives the exact binary value, so 0.1 comes back with a huge power of two as the denominator, not 10."
      },
      "syntax": "n.as_integer_ratio()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#int.as_integer_ratio",
      "version": "3.8",
      "section": "Числа и математика",
      "subcat": "как дробь",
      "color_group": "op",
      "aliases": [
        "целое как дробь",
        "числитель и знаменатель целого"
      ],
      "keywords": [],
      "tags": [
        "int"
      ],
      "examples": [
        "print((5).as_integer_ratio())   # → (5, 1)",
        "print((-8).as_integer_ratio())  # → (-8, 1)",
        "print((0).as_integer_ratio())   # → (0, 1)",
        "print(True.as_integer_ratio())  # → (1, 1)",
        "a, b = (3).as_integer_ratio(), (0.75).as_integer_ratio()",
        "print(a, b)  # → (3, 1) (3, 4)"
      ],
      "related": [
        "float.as_integer_ratio",
        "fractions.fraction",
        "int.is_integer"
      ],
      "related_errors": []
    },
    {
      "id": "int.bit_count",
      "title": "int.bit_count",
      "kind": "function",
      "summary": {
        "ru": "Возвращает число единичных битов в двоичной записи модуля числа (популяционный счёт). Добавлен в Python 3.10.",
        "en": "Return the number of ones in the binary representation of the absolute value (population count). Added in 3.10."
      },
      "body": {
        "ru": "Метод появился только в 3.10 — на более старом интерпретаторе будет AttributeError, и тогда единицы считают по строке из bin(). Знак не учитывается: у отрицательных берётся модуль, никакого дополнительного кода тут нет. Классическое применение — расстояние Хэмминга: сделать XOR двух чисел и посчитать единицы в результате.",
        "en": "The method exists only from 3.10 on; on an older interpreter you get AttributeError and fall back to counting ones in the bin() string. The sign is ignored — negatives are counted by absolute value, not by a two's-complement pattern. A classic use is Hamming distance: XOR the two numbers and count the ones."
      },
      "syntax": "n.bit_count()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#int.bit_count",
      "version": "3.10",
      "section": "Числа и математика",
      "subcat": "биты",
      "color_group": "op",
      "aliases": [
        "количество единиц в двоичной записи",
        "подсчёт единичных битов"
      ],
      "keywords": [],
      "tags": [
        "int"
      ],
      "examples": [
        "print((7).bit_count())    # → 3",
        "print((255).bit_count())  # → 8",
        "print((0).bit_count())    # → 0"
      ],
      "related": [
        "int.bit_length",
        "битовые-операции",
        "bin"
      ],
      "related_errors": []
    },
    {
      "id": "int.bit_length",
      "title": "int.bit_length",
      "kind": "function",
      "summary": {
        "ru": "Возвращает количество бит, необходимых для записи модуля числа в двоичном виде (без знака и ведущих нулей); у нуля — 0.",
        "en": "Return the number of bits needed to represent the absolute value in binary (0 for 0)."
      },
      "body": {
        "ru": "У отрицательных берётся модуль, так что знак на результат не влияет. Для n > 0 это ровно floor(log2(n)) + 1, но, в отличие от math.log2, считается точно: log2 сначала переводит число во float, и для 2**64 - 1 получается ровно 64.0 — формула ошибётся на единицу, а bit_length вернёт верные 64.",
        "en": "Negatives are taken by absolute value, so the sign never affects the result. For n > 0 this equals floor(log2(n)) + 1, but unlike math.log2 it is exact: log2 converts the int to a float first, and 2**64 - 1 rounds to exactly 64.0, so the formula is off by one while bit_length still returns 64."
      },
      "syntax": "n.bit_length()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#int.bit_length",
      "version": "3.1",
      "section": "Числа и математика",
      "subcat": "биты",
      "color_group": "op",
      "aliases": [
        "сколько бит занимает число",
        "длина двоичной записи числа",
        "количество двоичных разрядов"
      ],
      "keywords": [],
      "tags": [
        "int"
      ],
      "examples": [
        "print((255).bit_length())   # → 8",
        "print((1024).bit_length())  # → 11",
        "print((0).bit_length())     # → 0"
      ],
      "related": [
        "int.bit_count",
        "bin",
        "системы-счисления"
      ],
      "related_errors": []
    },
    {
      "id": "int.conjugate",
      "title": "int.conjugate",
      "kind": "function",
      "summary": {
        "ru": "Возвращает комплексно-сопряжённое числа; для целого (действительного) — само число. Часть общего числового интерфейса.",
        "en": "Return the complex conjugate; for an integer (a real number) it is the number itself."
      },
      "body": {
        "ru": "",
        "en": ""
      },
      "syntax": "n.conjugate()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex",
      "version": "",
      "section": "Числа и математика",
      "subcat": "как число",
      "color_group": "op",
      "aliases": [],
      "keywords": [],
      "tags": [
        "int"
      ],
      "examples": [
        "print((5).conjugate())   # → 5",
        "print((-3).conjugate())  # → -3",
        "print((0).conjugate())   # → 0",
        "print((5).real, (5).imag)  # → 5 0",
        "print((2 + 3j).conjugate())  # → (2-3j)",
        "print([n.conjugate() for n in [1, -2, 2j]])  # → [1, -2, -2j]"
      ],
      "related": [
        "complex.conjugate",
        "float.conjugate",
        "complex"
      ],
      "related_errors": []
    },
    {
      "id": "int.from_bytes",
      "title": "int.from_bytes",
      "kind": "function",
      "summary": {
        "ru": "Классовый метод: восстанавливает целое из объекта bytes по заданному порядку байт (обратен to_bytes).",
        "en": "Class method: reconstruct an integer from a bytes object using the given byte order."
      },
      "body": {
        "ru": "Порядок байт нигде не записан — его надо знать заранее: одни и те же два байта дадут 7 при 'big' и 1792 при 'little', и никакой ошибки при неверном выборе не будет. При signed=False по умолчанию старший байт 0xFF читается как большое положительное число, а не как -1, поэтому для данных, записанных со знаком, обязателен signed=True. Аргумент byteorder стал необязательным (со значением 'big') только с Python 3.11 — в старых версиях его указывают явно.",
        "en": "The byte order is not stored anywhere, you must know how the data was written: the same two bytes decode to 7 with 'big' and to 1792 with 'little', and a wrong guess raises nothing. With the default signed=False a leading 0xFF reads as a large positive number instead of -1, so pass signed=True whenever the producer used a signed encoding. byteorder only became optional (defaulting to 'big') in Python 3.11."
      },
      "syntax": "int.from_bytes(bytes, byteorder='big', *, signed=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#int.from_bytes",
      "version": "3.2",
      "section": "Числа и математика",
      "subcat": "байты",
      "color_group": "op",
      "aliases": [
        "байты в число",
        "собрать целое из байтов"
      ],
      "keywords": [],
      "tags": [
        "int"
      ],
      "examples": [
        "print(int.from_bytes(b'\\x00\\x07', 'big'))  # → 7",
        "print(int.from_bytes(b'\\xff', 'little'))    # → 255",
        "print(int.from_bytes(b'\\x01\\x00', 'little'))  # → 1"
      ],
      "related": [
        "int.to_bytes",
        "bytes",
        "bytes.fromhex"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "int.is_integer",
      "title": "int.is_integer",
      "kind": "function",
      "summary": {
        "ru": "Всегда возвращает True — у целого числа нет дробной части. Добавлен в Python 3.12 для единообразия с float.is_integer().",
        "en": "Always returns True — an integer has no fractional part. Added in 3.12 for parity with float.is_integer()."
      },
      "body": {
        "ru": "Смысл метода — единый интерфейс: функция, принимающая и int, и float, может звать is_integer() без проверки типа аргумента. Но на Python 3.11 и ниже у int его нет, и вызов упадёт с AttributeError, поэтому в коде, который должен работать на старых версиях, надёжнее isinstance(x, int) или сравнение значения с его целой частью.",
        "en": "The point is uniformity: code that accepts both int and float can call is_integer() without first checking the type. On Python 3.11 and earlier, though, int has no such method and the call raises AttributeError, so for code that must run on older versions prefer isinstance(x, int) or comparing the value with its truncated form."
      },
      "syntax": "n.is_integer()",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#int.is_integer",
      "version": "3.12",
      "section": "Числа и математика",
      "subcat": "как дробь",
      "color_group": "op",
      "aliases": [],
      "keywords": [],
      "tags": [
        "int"
      ],
      "examples": [
        "print((5).is_integer())   # → True",
        "print((-3).is_integer())  # → True",
        "print((0).is_integer())   # → True",
        "print((5.0).is_integer())  # → True",
        "print((2.5).is_integer())  # → False",
        "print([v.is_integer() for v in (7, 7.0, 7.5)])  # → [True, True, False]"
      ],
      "related": [
        "float.is_integer",
        "int.as_integer_ratio",
        "int"
      ],
      "related_errors": []
    },
    {
      "id": "int.to_bytes",
      "title": "int.to_bytes",
      "kind": "function",
      "summary": {
        "ru": "Сериализует целое в объект bytes заданной длины и порядка байт (byteorder «big»/«little»); signed=True — для отрицательных.",
        "en": "Serialize the integer to a bytes object of the given length and byte order."
      },
      "body": {
        "ru": "Длину считаете вы: число, не влезающее в length байт, даёт OverflowError, а не обрезается молча; минимально нужное количество — (n.bit_length() + 7) // 8. Отрицательные значения требуют signed=True, иначе тот же OverflowError о переводе отрицательного в беззнаковое. Значения по умолчанию (length=1, byteorder='big') появились только в Python 3.11 — раньше оба аргумента были обязательными.",
        "en": "Sizing is on you: a value that does not fit in length bytes raises OverflowError instead of being truncated, and the minimum needed is (n.bit_length() + 7) // 8. Negative numbers require signed=True, otherwise you get the same OverflowError about converting a negative int to unsigned. The defaults length=1 and byteorder='big' only appeared in Python 3.11; before that both arguments were mandatory."
      },
      "syntax": "n.to_bytes(length=1, byteorder='big', *, signed=False)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/stdtypes.html#int.to_bytes",
      "version": "3.2",
      "section": "Числа и математика",
      "subcat": "байты",
      "color_group": "op",
      "aliases": [
        "число в байты",
        "перевести целое в байты",
        "порядок байт"
      ],
      "keywords": [],
      "tags": [
        "int"
      ],
      "examples": [
        "print((7).to_bytes(2, 'big'))       # → b'\\x00\\x07'",
        "print((255).to_bytes(1, 'little'))  # → b'\\xff'",
        "print((1).to_bytes(2, 'little'))    # → b'\\x01\\x00'"
      ],
      "related": [
        "int.from_bytes",
        "bytes",
        "int.bit_length"
      ],
      "related_errors": [
        "OverflowError",
        "ValueError"
      ]
    },
    {
      "id": "math.acosh",
      "title": "math.acosh",
      "kind": "function",
      "summary": {
        "ru": "Обратный гиперболический косинус (аргумент ≥ 1).",
        "en": "Inverse hyperbolic cosine (argument ≥ 1)."
      },
      "body": {
        "ru": "Область определения начинается с единицы: любой аргумент меньше 1, включая 0 и отрицательные, даёт ValueError с текстом math domain error, а не nan — значит, вход надо либо проверять заранее, либо ловить именно ValueError. Результат всегда неотрицательный, это главная ветвь; для аргументов вне промежутка от 1 до бесконечности есть cmath.acosh, возвращающий комплексное число.",
        "en": "The domain starts at 1: anything below it, including 0 and negatives, raises ValueError with \"math domain error\" instead of returning nan, so either validate the input or catch that ValueError specifically. The result is always non-negative — this is the principal branch; for arguments outside [1, inf) use cmath.acosh, which returns a complex value."
      },
      "syntax": "math.acosh(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.acosh",
      "version": "",
      "section": "Числа и математика",
      "subcat": "гиперболические",
      "color_group": "module",
      "aliases": [
        "ареакосинус",
        "обратный гиперболический косинус"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.acosh(1))   # → 0.0",
        "print(round(math.acosh(2), 3))   # → 1.317",
        "print(round(math.acosh(10), 3))  # → 2.993",
        "print(round(math.acosh(math.cosh(1.5)), 6))  # → 1.5",
        "print(math.acosh(0.5))  # → ValueError"
      ],
      "related": [
        "math.cosh",
        "math.acos",
        "math.asinh"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "math.asinh",
      "title": "math.asinh",
      "kind": "function",
      "summary": {
        "ru": "Обратный гиперболический синус.",
        "en": "Inverse hyperbolic sine."
      },
      "body": {
        "ru": "В отличие от acosh определён на всей вещественной оси и не падает: домашней ошибки ValueError здесь не бывает ни при каком вещественном входе. Учебниковая формула через логарифм суммы x и корня из x в квадрате плюс единица теряет точность при больших отрицательных x из-за вычитания близких величин — asinh считает это аккуратно. Растёт логарифмически, поэтому его берут как «мягкий логарифм» для шкал, где встречаются нули и отрицательные значения.",
        "en": "Unlike acosh it is defined for every real number and never raises a domain ValueError, whatever you pass in. The textbook formula — a logarithm of x plus the square root of x squared plus one — loses accuracy for large negative x because of cancellation, while asinh handles that carefully. It grows logarithmically, which is why it is used as a \"soft log\" scale for data containing zeros and negative values."
      },
      "syntax": "math.asinh(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.asinh",
      "version": "",
      "section": "Числа и математика",
      "subcat": "гиперболические",
      "color_group": "module",
      "aliases": [
        "ареасинус",
        "обратный гиперболический синус"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.asinh(0))   # → 0.0",
        "print(round(math.asinh(1), 3))   # → 0.881",
        "print(round(math.asinh(-2), 3))  # → -1.444",
        "print(round(math.asinh(math.sinh(2)), 6))  # → 2.0",
        "print(round(math.asinh(1000), 3))  # → 7.601"
      ],
      "related": [
        "math.sinh",
        "math.asin",
        "math.acosh"
      ],
      "related_errors": []
    },
    {
      "id": "math.atanh",
      "title": "math.atanh",
      "kind": "function",
      "summary": {
        "ru": "Обратный гиперболический тангенс (|аргумент| < 1).",
        "en": "Inverse hyperbolic tangent (|argument| < 1)."
      },
      "body": {
        "ru": "Интервал открытый: на самих границах math.atanh(1) и math.atanh(-1) дают ValueError (math domain error), потому что функция там уходит в бесконечность — и всё, что по модулю больше единицы, тоже ValueError. Не путай с math.atan: у арктангенса аргумент любой вещественный, а ограничен результат, здесь ровно наоборот.",
        "en": "The domain is open at both ends: math.atanh(1) and math.atanh(-1) already raise ValueError (math domain error) because the function blows up there, and anything with modulus above one fails the same way. Do not mix it up with math.atan, where the argument is unrestricted and the result is bounded — here it is the other way round."
      },
      "syntax": "math.atanh(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.atanh",
      "version": "",
      "section": "Числа и математика",
      "subcat": "гиперболические",
      "color_group": "module",
      "aliases": [
        "ареатангенс",
        "обратный гиперболический тангенс"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.atanh(0))   # → 0.0",
        "print(round(math.atanh(0.5), 4))   # → 0.5493",
        "print(round(math.atanh(-0.5), 4))   # → -0.5493",
        "print(round(math.atanh(math.tanh(2)), 4))   # → 2.0",
        "print(math.atanh(1))   # → ValueError"
      ],
      "related": [
        "math.tanh",
        "math.atan",
        "math.asinh"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "math.cbrt",
      "title": "math.cbrt",
      "kind": "function",
      "summary": {
        "ru": "Кубический корень числа (Python 3.11+).",
        "en": "The cube root of a number (3.11+)."
      },
      "body": {
        "ru": "Ради чего он нужен вместо возведения в степень: cbrt честно берёт корень из отрицательных чисел, а (-8) ** (1/3) в Python 3 вернёт комплексное число, math.pow(-8, 1/3) же просто упадёт с ValueError. Результат всегда float и для точных кубов может разойтись с целым в последнем разряде, поэтому сравнивайте через math.isclose(), а не ==.",
        "en": "The reason to prefer it over exponentiation: cbrt takes the root of negative numbers directly, while (-8) ** (1/3) yields a complex number in Python 3 and math.pow(-8, 1/3) raises ValueError outright. The result is always a float and may miss an exact integer by a last-digit rounding step, so compare with math.isclose() rather than ==."
      },
      "syntax": "math.cbrt(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.cbrt",
      "version": "3.11",
      "section": "Числа и математика",
      "subcat": "корни/степени",
      "color_group": "module",
      "aliases": [
        "кубический корень",
        "корень третьей степени"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(round(math.cbrt(27), 4))   # → 3.0",
        "print(round(math.cbrt(-8), 4))   # → -2.0",
        "print(round(math.cbrt(0.001), 4))   # → 0.1",
        "print(math.cbrt(0))   # → 0.0",
        "print(math.pow(-8, 1 / 3))   # → ValueError"
      ],
      "related": [
        "math.sqrt",
        "math.pow",
        "math.isqrt"
      ],
      "related_errors": []
    },
    {
      "id": "math.copysign",
      "title": "math.copysign",
      "kind": "function",
      "summary": {
        "ru": "Число с модулем x и знаком y.",
        "en": "A value with the magnitude of x and the sign of y."
      },
      "body": {
        "ru": "Самое неочевидное применение — отличить 0.0 от -0.0: сравнением это не сделать, потому что -0.0 == 0.0 истинно, а math.copysign(1, x) вернёт -1.0 именно для минус-нуля. Знак читается из знакового бита, так что функция работает и с nan, и с бесконечностями, а результат всегда float, даже если оба аргумента целые.",
        "en": "Its least obvious use is telling 0.0 from -0.0: comparison cannot do it, since -0.0 == 0.0 is True, but math.copysign(1, x) returns -1.0 precisely for negative zero. The sign is read straight from the sign bit, so it also works on nan and infinities, and the result is always a float even when both arguments are ints."
      },
      "syntax": "math.copysign(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.copysign",
      "version": "",
      "section": "Числа и математика",
      "subcat": "представление",
      "color_group": "module",
      "aliases": [
        "скопировать знак числа",
        "задать знак числа"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.copysign(3, -1))   # → -3.0",
        "print(math.copysign(-3, 1))   # → 3.0",
        "x = -12.5",
        "print(math.copysign(1, x))   # → -1.0",
        "print(math.copysign(2, -0.0))   # → -2.0"
      ],
      "related": [
        "math.fabs",
        "abs",
        "math.inf"
      ],
      "related_errors": []
    },
    {
      "id": "math.cosh",
      "title": "math.cosh",
      "kind": "function",
      "summary": {
        "ru": "Гиперболический косинус.",
        "en": "Hyperbolic cosine."
      },
      "body": {
        "ru": "Растёт экспоненциально, и примерно с аргумента 710 результат перестаёт помещаться в float — вместо числа прилетает OverflowError (math range error). С обычным косинусом общего мало: cosh чётная, но не колеблется и никогда не бывает меньше 1.",
        "en": "It grows exponentially, so somewhere around an argument of 710 the result no longer fits in a float and you get OverflowError (math range error) instead of a number. It shares little with plain cos: cosh is even too, but it never oscillates and never drops below 1."
      },
      "syntax": "math.cosh(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.cosh",
      "version": "",
      "section": "Числа и математика",
      "subcat": "гиперболические",
      "color_group": "module",
      "aliases": [
        "гиперболический косинус",
        "гиперболические функции"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.cosh(0))   # → 1.0",
        "print(round(math.cosh(1), 4))   # → 1.5431",
        "print(round(math.cosh(-1), 4))   # → 1.5431",
        "print(round(math.cosh(2) ** 2 - math.sinh(2) ** 2, 4))   # → 1.0",
        "print(math.cosh(1000))   # → OverflowError"
      ],
      "related": [
        "math.acosh",
        "math.sinh",
        "math.cos"
      ],
      "related_errors": []
    },
    {
      "id": "math.dist",
      "title": "math.dist",
      "kind": "function",
      "summary": {
        "ru": "Евклидово расстояние между двумя точками (последовательностями координат).",
        "en": "The Euclidean distance between two points."
      },
      "body": {
        "ru": "Появился в 3.8; точки — любые последовательности координат любой размерности, но длины обязаны совпадать, иначе ValueError. Внутри считается так же, как hypot: без переполнения на промежуточных квадратах и с меньшей ошибкой округления, чем у ручного корня из суммы квадратов. Если расстояния только сравниваются между собой, дешевле обойтись суммой квадратов, не извлекая корень.",
        "en": "Added in 3.8; the points are any sequences of coordinates of any dimension, but their lengths must match or you get ValueError. Internally it works like hypot: no overflow on the intermediate squares and less rounding error than a hand-rolled square root of a sum of squares. If you only compare distances with each other, comparing sums of squares without the root is cheaper."
      },
      "syntax": "math.dist(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.dist",
      "version": "3.8",
      "section": "Числа и математика",
      "subcat": "геометрия",
      "color_group": "module",
      "aliases": [
        "расстояние между точками",
        "евклидово расстояние",
        "длина отрезка по координатам"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.dist((0, 0), (3, 4)))   # → 5.0",
        "print(math.dist([1, 2, 3], [4, 6, 3]))   # → 5.0",
        "points = [(1, 1), (5, 5), (2, 3)]",
        "print(min(points, key=lambda p: math.dist((0, 0), p)))   # → (1, 1)",
        "print(math.dist((0, 0), (1, 1, 1)))   # → ValueError"
      ],
      "related": [
        "math.hypot",
        "math.sqrt",
        "math.sumprod"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "math.erf",
      "title": "math.erf",
      "kind": "function",
      "summary": {
        "ru": "Функция ошибок (интеграл Гаусса); полезна в статистике.",
        "en": "The error function (Gauss integral); used in statistics."
      },
      "body": {
        "ru": "Функция нечётная и быстро упирается в ±1: примерно с x = 6 результат округляется ровно в 1.0, поэтому выражение 1 - erf(x) на больших аргументах схлопывается в ноль — там нужен erfc. Если задача про нормальное распределение, короче взять statistics.NormalDist().cdf(), а не собирать формулу из erf вручную.",
        "en": "The function is odd and saturates quickly: from about x = 6 the result rounds to exactly 1.0, so 1 - erf(x) collapses to zero for large arguments and erfc is what you want there. If the task is really about the normal distribution, statistics.NormalDist().cdf() is a shorter road than assembling the formula out of erf by hand."
      },
      "syntax": "math.erf(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.erf",
      "version": "3.2",
      "section": "Числа и математика",
      "subcat": "спецфункции",
      "color_group": "module",
      "aliases": [
        "функция ошибок",
        "интеграл вероятности"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.erf(0))   # → 0.0",
        "print(round(math.erf(1), 4))   # → 0.8427",
        "print(round(math.erf(-1), 4))   # → -0.8427",
        "print(round(math.erf(1 / math.sqrt(2)), 4))   # → 0.6827",
        "print(math.erf(float('inf')))   # → 1.0"
      ],
      "related": [
        "math.erfc",
        "statistics.NormalDist",
        "math.gamma"
      ],
      "related_errors": []
    },
    {
      "id": "math.erfc",
      "title": "math.erfc",
      "kind": "function",
      "summary": {
        "ru": "Дополнительная функция ошибок: 1 − erf(x), точно при больших x.",
        "en": "The complementary error function 1 − erf(x)."
      },
      "body": {
        "ru": "Существует ровно ради хвоста распределения: при x = 10 erf(x) неотличим от 1.0 и 1 - erf(x) даёт ровно ноль, тогда как erfc(10) вернёт около 2e-45. Возле нуля роли меняются — там erfc(x) почти равен единице и значащие цифры съедаются, так что точнее считать через erf.",
        "en": "It exists precisely for the tail: at x = 10 erf(x) is indistinguishable from 1.0 and 1 - erf(x) yields exactly zero, whereas erfc(10) returns roughly 2e-45. Near zero the roles swap — erfc(x) sits just under 1 and significant digits get eaten, so erf is the accurate one there."
      },
      "syntax": "math.erfc(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.erfc",
      "version": "3.2",
      "section": "Числа и математика",
      "subcat": "спецфункции",
      "color_group": "module",
      "aliases": [
        "дополнительная функция ошибок",
        "1 минус функция ошибок",
        "хвост нормального распределения"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.erfc(0))   # → 1.0",
        "print(round(math.erfc(1), 4))   # → 0.1573",
        "print(round(math.erf(1) + math.erfc(1), 10))   # → 1.0",
        "print(1 - math.erf(6))   # → 0.0",
        "print(math.erfc(6) > 0)   # → True"
      ],
      "related": [
        "math.erf",
        "statistics.NormalDist",
        "math.expm1"
      ],
      "related_errors": []
    },
    {
      "id": "math.exp2",
      "title": "math.exp2",
      "kind": "function",
      "summary": {
        "ru": "2 в степени x (Python 3.11+).",
        "en": "2 raised to the power x (3.11+)."
      },
      "body": {
        "ru": "Для целых показателей привычное 2 ** n лучше: оно даёт точный int любой длины, а exp2 всегда возвращает float и падает с OverflowError, как только число перестаёт помещаться в float. Ниша exp2 — дробные показатели и численные расчёты, где нужен один корректно округлённый шаг вместо связки exp и логарифма.",
        "en": "For whole exponents stick with 2 ** n: it gives an exact int of any size, while exp2 always returns a float and raises OverflowError once the value no longer fits. exp2 earns its place with fractional exponents and numeric code that wants a single correctly rounded step instead of exp(x * log(2))."
      },
      "syntax": "math.exp2(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.exp2",
      "version": "3.11",
      "section": "Числа и математика",
      "subcat": "корни/степени",
      "color_group": "module",
      "aliases": [
        "двойка в степени",
        "степень двойки"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.exp2(3))   # → 8.0",
        "print(math.exp2(-1))   # → 0.5",
        "print(round(math.exp2(0.5), 6))   # → 1.414214",
        "print(math.exp2(8))   # → 256.0",
        "print(2 ** 10, math.exp2(10))   # → 1024 1024.0"
      ],
      "related": [
        "math.exp",
        "math.log2",
        "math.pow"
      ],
      "related_errors": []
    },
    {
      "id": "math.expm1",
      "title": "math.expm1",
      "kind": "function",
      "summary": {
        "ru": "exp(x) − 1, точно при малых x (без потери значимости).",
        "en": "exp(x) − 1, accurate for small x."
      },
      "body": {
        "ru": "Проблема, которую он снимает: при крошечном x значение exp(x) почти равно единице, при вычитании старшие разряды взаимно уничтожаются и от ответа остаётся горстка значащих цифр — expm1 считает разность напрямую и сохраняет точность. Парная ему math.log1p(x) вычисляет log(1+x) и служит обратной функцией; на больших x выигрыша нет, и переполнение произойдёт ровно так же, как у exp.",
        "en": "The problem it removes: for tiny x, exp(x) sits so close to 1 that subtracting 1 cancels the leading digits and leaves only a couple of significant ones — expm1 computes the difference directly and keeps full precision. Its counterpart math.log1p(x) computes log(1+x) and inverts it; for large x there is nothing to gain, and it overflows exactly like exp."
      },
      "syntax": "math.expm1(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.expm1",
      "version": "3.2",
      "section": "Числа и математика",
      "subcat": "корни/степени",
      "color_group": "module",
      "aliases": [
        "экспонента минус единица",
        "точная экспонента при малых значениях"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.expm1(0))   # → 0.0",
        "print(round(math.expm1(1), 6))   # → 1.718282",
        "print(round(math.expm1(0.005), 6))   # → 0.005013",
        "print(math.exp(1e-16) - 1)   # → 0.0",
        "print(math.expm1(1e-16))   # → 1e-16"
      ],
      "related": [
        "math.exp",
        "math.log1p",
        "math.exp2"
      ],
      "related_errors": []
    },
    {
      "id": "math.fma",
      "title": "math.fma",
      "kind": "function",
      "summary": {
        "ru": "Слитное умножение-сложение x·y + z за одно округление (Python 3.13+).",
        "en": "Fused multiply-add x·y + z with a single rounding (3.13+)."
      },
      "body": {
        "ru": "Весь смысл в том, что промежуточное произведение не округляется: x*y + z округляет дважды и в длинных суммах копит ошибку, fma — один раз. Аргументы-int приводятся к float, результат всегда float, и в обычной арифметике разница невидима — за ней лезут только в численные алгоритмы вроде скалярного произведения или схемы Горнера.",
        "en": "The whole point is that the intermediate product is never rounded: x*y + z rounds twice and accumulates error across long sums, while fma rounds once. Integer arguments are converted and the result is always a float; in everyday arithmetic the difference is invisible, so reach for it only in numeric code such as dot products or Horner's scheme."
      },
      "syntax": "math.fma(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.fma",
      "version": "3.13",
      "section": "Числа и математика",
      "subcat": "произведение",
      "color_group": "module",
      "aliases": [
        "слитное умножение-сложение",
        "умножить и прибавить за одно округление"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.fma(2, 3, 4))   # → 10.0",
        "print(math.fma(2.5, 4, -1))   # → 9.0",
        "print(math.fma(0.5, 10, 3))   # → 8.0",
        "print(math.fma(0.1, 0.1, -0.01) < 0.1 * 0.1 - 0.01)   # → True",
        "print(type(math.fma(2, 3, 4)))   # → <class 'float'>"
      ],
      "related": [
        "math.sumprod",
        "math.fsum",
        "math.prod"
      ],
      "related_errors": []
    },
    {
      "id": "math.fmod",
      "title": "math.fmod",
      "kind": "function",
      "summary": {
        "ru": "Остаток от деления как в C (fmod): знак совпадает со знаком x (отличается от %).",
        "en": "The C-library fmod remainder: the sign matches x (differs from %)."
      },
      "body": {
        "ru": "Разница с % не косметическая: fmod повторяет C и берёт знак от делимого, а % — от делителя, поэтому math.fmod(-10, 3) это -1.0, а -10 % 3 это 2. Для целых используйте %, для float — fmod: он точен по построению, тогда как % на числах сильно разного порядка накапливает погрешность. Результат всегда float, а нулевой делитель даёт ValueError, а не ZeroDivisionError.",
        "en": "The difference from % is not cosmetic: fmod follows C and takes the sign of the dividend, while % takes the sign of the divisor, so math.fmod(-10, 3) is -1.0 whereas -10 % 3 is 2. Use % for integers and fmod for floats — fmod is exact by construction, while % loses accuracy when the operands differ wildly in magnitude. The result is always a float, and a zero divisor raises ValueError, not ZeroDivisionError."
      },
      "syntax": "math.fmod(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.fmod",
      "version": "",
      "section": "Числа и математика",
      "subcat": "остаток",
      "color_group": "module",
      "aliases": [
        "остаток от деления дробных чисел",
        "знак остатка как у делимого"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.fmod(10, 3))   # → 1.0",
        "print(math.fmod(-10, 3))   # → -1.0",
        "print(-10 % 3)   # → 2",
        "print(math.fmod(10, -3))   # → 1.0",
        "print(math.fmod(7.5, 2))   # → 1.5",
        "print(math.fmod(10, 0))   # → ValueError"
      ],
      "related": [
        "остаток",
        "math.remainder",
        "divmod"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "math.frexp",
      "title": "math.frexp",
      "kind": "function",
      "summary": {
        "ru": "Раскладывает число на мантиссу m и экспоненту e: x = m·2^e, 0.5 ≤ |m| < 1.",
        "en": "Decompose a number into mantissa m and exponent e: x = m·2^e."
      },
      "body": {
        "ru": "Разложение двоичное, а не десятичное: frexp(8) даёт (0.5, 4), потому что 8 = 0.5·2^4, а не (8.0, 0), как подсказывает привычка к научной записи по основанию 10. Операция точная, без округления, и math.ldexp(*math.frexp(x)) вернёт исходное число бит в бит. Мантисса — float, экспонента — int; для нуля результат (0.0, 0).",
        "en": "The split is binary, not decimal: frexp(8) is (0.5, 4) because 8 = 0.5·2^4 — not (8.0, 0) as base-10 scientific notation would suggest. The decomposition is exact, so feeding the pair back through math.ldexp reproduces the original value bit for bit. The mantissa is a float and the exponent an int; zero gives (0.0, 0)."
      },
      "syntax": "math.frexp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.frexp",
      "version": "",
      "section": "Числа и математика",
      "subcat": "представление",
      "color_group": "module",
      "aliases": [
        "мантисса и порядок числа",
        "разложить число на мантиссу и экспоненту"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.frexp(8))   # → (0.5, 4)",
        "print(math.frexp(0.5))   # → (0.5, 0)",
        "print(math.frexp(-12))   # → (-0.75, 4)",
        "print(math.frexp(0))   # → (0.0, 0)",
        "m, e = math.frexp(10); print(m * 2 ** e)   # → 10.0"
      ],
      "related": [
        "math.ldexp",
        "math.modf",
        "float.hex"
      ],
      "related_errors": []
    },
    {
      "id": "math.gamma",
      "title": "math.gamma",
      "kind": "function",
      "summary": {
        "ru": "Гамма-функция: обобщение факториала (gamma(n) = (n−1)!).",
        "en": "The gamma function: a generalization of factorial (gamma(n) = (n−1)!)."
      },
      "body": {
        "ru": "Для целых аргументов лучше брать math.factorial: он даёт точное целое, а gamma всегда возвращает float с погрешностью округления. В нуле и в отрицательных целых у функции полюсы — там ValueError, а растёт она так быстро, что уже около 172 результат не помещается в float и вы получаете OverflowError.",
        "en": "For whole numbers reach for math.factorial instead: it returns an exact integer, while gamma always gives a float with rounding error baked in. The function has poles at zero and at every negative integer (ValueError there), and it grows so fast that somewhere past 172 the result no longer fits in a float and you get OverflowError."
      },
      "syntax": "math.gamma(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.gamma",
      "version": "3.2",
      "section": "Числа и математика",
      "subcat": "спецфункции",
      "color_group": "module",
      "aliases": [
        "гамма-функция",
        "факториал нецелого числа"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.gamma(5))   # → 24.0",
        "print(math.gamma(1))   # → 1.0",
        "print(round(math.gamma(0.5), 6))   # → 1.772454",
        "print(math.gamma(6) == math.factorial(5))   # → True",
        "print(math.gamma(0))   # → ValueError"
      ],
      "related": [
        "math.lgamma",
        "math.factorial",
        "math.erf"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "math.isqrt",
      "title": "math.isqrt",
      "kind": "function",
      "summary": {
        "ru": "Целочисленный квадратный корень: округляет √n вниз до целого (для точной арифметики).",
        "en": "Integer square root: the floor of √n (exact integer arithmetic)."
      },
      "body": {
        "ru": "Привычное int(n ** 0.5) или int(math.sqrt(n)) считает через float и на больших числах (примерно от 2**52) начинает ошибаться на единицу — isqrt работает в целочисленной арифметике и точен для любого n. Аргумент обязан быть целым: float даёт TypeError, отрицательное число — ValueError, а не nan. Функция появилась в Python 3.8.",
        "en": "The usual int(n ** 0.5) or int(math.sqrt(n)) routes through float and starts being off by one for large values (roughly past 2**52); isqrt stays in integer arithmetic and is exact for any n. It takes integers only: a float raises TypeError and a negative argument raises ValueError rather than returning nan. Available since Python 3.8."
      },
      "syntax": "math.isqrt(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.isqrt",
      "version": "3.8",
      "section": "Числа и математика",
      "subcat": "корни/степени",
      "color_group": "module",
      "aliases": [
        "целочисленный квадратный корень",
        "корень с округлением вниз",
        "квадратный корень без погрешности"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.isqrt(17))   # → 4",
        "print(math.isqrt(16))   # → 4",
        "n = 49; print(math.isqrt(n) ** 2 == n)   # → True",
        "n = 10 ** 18 - 1; print(math.isqrt(n))   # → 999999999",
        "print(int(math.sqrt(n)))   # → 1000000000",
        "print(math.isqrt(-1))   # → ValueError"
      ],
      "related": [
        "math.sqrt",
        "math.floor",
        "math.cbrt"
      ],
      "related_errors": [
        "ValueError",
        "TypeError"
      ]
    },
    {
      "id": "math.ldexp",
      "title": "math.ldexp",
      "kind": "function",
      "summary": {
        "ru": "Обратная frexp: x·2^i (собирает число из мантиссы и экспоненты).",
        "en": "The inverse of frexp: x·2^i."
      },
      "body": {
        "ru": "Умножение на степень двойки — точная операция: сдвигается только экспонента, значащие биты не меняются, поэтому ldexp надёжнее, чем x * 2 ** i, где при большом i промежуточное 2 ** i раздувается в гигантское целое. Если результат не влезает в диапазон float, будет OverflowError, а при слишком маленьком — постепенная потеря точности через субнормальные числа и в пределе 0.0.",
        "en": "Scaling by a power of two is exact — only the exponent moves, the significant bits stay put — which makes ldexp safer than x * 2 ** i, where a large i builds a huge intermediate integer first. Overflowing the float range raises OverflowError; going too small degrades gradually through subnormal numbers and finally to 0.0."
      },
      "syntax": "math.ldexp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.ldexp",
      "version": "",
      "section": "Числа и математика",
      "subcat": "представление",
      "color_group": "module",
      "aliases": [
        "собрать число из мантиссы и порядка",
        "умножить на степень двойки"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.ldexp(0.5, 3))   # → 4.0",
        "print(math.ldexp(1, 10))   # → 1024.0",
        "print(math.ldexp(3, -2))   # → 0.75",
        "m, e = math.frexp(10); print(math.ldexp(m, e))   # → 10.0",
        "print(math.ldexp(1, 5000))   # → OverflowError"
      ],
      "related": [
        "math.frexp",
        "math.exp2",
        "math.modf"
      ],
      "related_errors": []
    },
    {
      "id": "math.lgamma",
      "title": "math.lgamma",
      "kind": "function",
      "summary": {
        "ru": "Натуральный логарифм модуля гамма-функции (без переполнения).",
        "en": "The natural log of the absolute value of the gamma function."
      },
      "body": {
        "ru": "Нужен ровно там, где сама gamma переполняется: логарифмы факториалов и биномиальных коэффициентов складывают и вычитают в лог-масштабе, а exp берут в самом конце. Учтите, что это логарифм модуля — знак гамма-функции (на части отрицательной полуоси она отрицательна) теряется, а в нуле и отрицательных целых будет ValueError.",
        "en": "This is the tool for the range where gamma itself overflows: work with log-factorials and log-binomials by adding and subtracting them, and apply exp only at the very end. Note it is the log of the absolute value, so the sign of gamma (negative on parts of the negative axis) is lost, and zero and negative integers raise ValueError."
      },
      "syntax": "math.lgamma(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.lgamma",
      "version": "3.2",
      "section": "Числа и математика",
      "subcat": "спецфункции",
      "color_group": "module",
      "aliases": [
        "логарифм гамма-функции",
        "логарифм факториала без переполнения"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.lgamma(1))   # → 0.0",
        "print(round(math.lgamma(5), 4))   # → 3.1781",
        "print(round(math.exp(math.lgamma(6))))   # → 120",
        "print(round(math.lgamma(1000), 2))   # → 5905.22",
        "print(math.lgamma(0))   # → ValueError"
      ],
      "related": [
        "math.gamma",
        "math.factorial",
        "math.log"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "math.log1p",
      "title": "math.log1p",
      "kind": "function",
      "summary": {
        "ru": "Натуральный логарифм ln(1 + x), точно при малых x.",
        "en": "The natural logarithm ln(1 + x), accurate for small x."
      },
      "body": {
        "ru": "Когда x очень мал, сумма 1 + x округляется до 1.0 ещё до взятия логарифма, и math.log(1 + x) отдаёт ноль вместо ответа — log1p считает ln(1+x) напрямую и сохраняет значащие цифры. Берите её там, где x — небольшое относительное приращение: доходности, вероятности, накопление логарифма правдоподобия. Область определения x > -1, всё остальное даёт ValueError; обратная функция — math.expm1.",
        "en": "For very small x the sum 1 + x rounds to 1.0 before the logarithm is even taken, so math.log(1 + x) collapses to zero; log1p computes ln(1+x) directly and keeps the significant digits. Reach for it whenever x is a small relative change — returns, probabilities, accumulating log-likelihoods. The domain is x > -1 (anything else raises ValueError), and math.expm1 is its inverse."
      },
      "syntax": "math.log1p(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.log1p",
      "version": "",
      "section": "Числа и математика",
      "subcat": "корни/степени",
      "color_group": "module",
      "aliases": [
        "логарифм от 1 плюс x",
        "точный натуральный логарифм при малых значениях"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.log1p(0))   # → 0.0",
        "print(math.log1p(1))   # → 0.6931471805599453",
        "print(math.log(1 + 1e-16))   # → 0.0",
        "print(math.log1p(1e-16))   # → 1e-16",
        "print(math.log1p(-1))   # → ValueError"
      ],
      "related": [
        "math.log",
        "math.expm1",
        "math.log2"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "math.modf",
      "title": "math.modf",
      "kind": "function",
      "summary": {
        "ru": "Возвращает пару (дробная_часть, целая_часть) как float.",
        "en": "Return the (fractional, integer) parts as a pair of floats."
      },
      "body": {
        "ru": "Порядок пары легко перепутать: дробная часть идёт первой, целая — второй. Обе части float и обе несут знак аргумента, поэтому modf(-3.5) это (-0.5, -3.0), а не (0.5, -4.0), которые дал бы divmod(-3.5, 1) с округлением вниз. Если целая часть нужна как int, берите int(x) или math.trunc(x) — modf её так не отдаст.",
        "en": "The order trips people up: the fractional part comes first, the integer part second. Both are floats and both carry the sign of the input, so modf(-3.5) is (-0.5, -3.0) — unlike divmod(-3.5, 1), which floors and yields (-4.0, 0.5). If you need the integer part as an int, use int(x) or math.trunc(x); modf never gives you one."
      },
      "syntax": "math.modf(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.modf",
      "version": "",
      "section": "Числа и математика",
      "subcat": "представление",
      "color_group": "module",
      "aliases": [
        "дробная часть числа",
        "целая и дробная часть числа"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.modf(3.5))   # → (0.5, 3.0)",
        "print(math.modf(-3.5))   # → (-0.5, -3.0)",
        "print(math.modf(5.0))   # → (0.0, 5.0)",
        "print(math.modf(2.7))   # → (0.7000000000000002, 2.0)",
        "print(round(math.modf(19.99)[0] * 100))   # → 99"
      ],
      "related": [
        "math.trunc",
        "math.frexp",
        "divmod"
      ],
      "related_errors": []
    },
    {
      "id": "math.nextafter",
      "title": "math.nextafter",
      "kind": "function",
      "summary": {
        "ru": "Следующее представимое float-число после x в сторону y.",
        "en": "The next representable float after x toward y."
      },
      "body": {
        "ru": "Функция появилась в Python 3.9, а аргумент steps — только в 3.12. Типичное применение — сдвинуть границу интервала на минимально возможную величину (полуоткрытый диапазон, обход строгого сравнения) или проверить, что два float соседние, то есть между ними нет других представимых значений. Если x и y равны, возвращается y; nextafter(0.0, 1.0) даёт наименьшее положительное субнормальное число 5e-324.",
        "en": "Added in Python 3.9; the steps argument only arrived in 3.12. It is the tool for nudging a bound by the smallest possible amount — building a half-open float range, or checking whether two floats are adjacent with nothing representable in between. When x equals y the result is y, and nextafter(0.0, 1.0) returns the smallest positive subnormal, 5e-324."
      },
      "syntax": "math.nextafter(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.nextafter",
      "version": "3.9",
      "section": "Числа и математика",
      "subcat": "представление",
      "color_group": "module",
      "aliases": [
        "следующее число с плавающей точкой",
        "ближайшее представимое число в сторону цели"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.nextafter(1.0, 2.0))   # → 1.0000000000000002",
        "print(math.nextafter(1.0, 0.0))   # → 0.9999999999999999",
        "print(math.nextafter(1.0, 2.0, steps=2))   # → 1.0000000000000004",
        "print(math.nextafter(0.0, 1.0))   # → 5e-324",
        "print(math.nextafter(2.0, 2.0))   # → 2.0"
      ],
      "related": [
        "math.ulp",
        "math.isclose",
        "math.inf"
      ],
      "related_errors": []
    },
    {
      "id": "math.remainder",
      "title": "math.remainder",
      "kind": "function",
      "summary": {
        "ru": "Остаток по IEEE 754: x − round(x/y)·y, результат в [−y/2, y/2].",
        "en": "The IEEE 754 remainder: x − round(x/y)·y."
      },
      "body": {
        "ru": "Частное округляется к ближайшему целому (ровная половина — к чётному), поэтому знак результата не привязан ни к x, ни к y, а модуль никогда не превышает половину y: remainder(11, 3) даёт -1.0, хотя оба числа положительные. Это ровно то, что нужно для приведения углов и фаз в симметричный диапазон, но не «остаток» в бытовом смысле — для него берите % или math.fmod. Нулевой делитель даёт ValueError.",
        "en": "The quotient is rounded to the nearest integer (ties to even), so the sign of the result follows neither x nor y and its magnitude never exceeds half of y: remainder(11, 3) is -1.0 even though both arguments are positive. That is exactly what you want for folding angles or phases into a symmetric range, but it is not the everyday \"remainder\" — use % or math.fmod for that. A zero divisor raises ValueError."
      },
      "syntax": "math.remainder(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.remainder",
      "version": "3.7",
      "section": "Числа и математика",
      "subcat": "остаток",
      "color_group": "module",
      "aliases": [
        "остаток по стандарту IEEE 754",
        "остаток от деления с округлением к ближайшему"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.remainder(10, 3))   # → 1.0",
        "print(math.remainder(11, 3))   # → -1.0",
        "print(11 % 3)   # → 2",
        "print(math.remainder(7, 2))   # → -1.0",
        "print(math.remainder(1, 0))   # → ValueError"
      ],
      "related": [
        "math.fmod",
        "остаток",
        "divmod"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "math.sinh",
      "title": "math.sinh",
      "kind": "function",
      "summary": {
        "ru": "Гиперболический синус.",
        "en": "Hyperbolic sine."
      },
      "body": {
        "ru": "Аргумент здесь — не угол: math.radians и math.degrees к гиперболическим функциям неприменимы, на вход идёт обычное вещественное число. Значение неограниченно и растёт экспоненциально, так что примерно с 710 по модулю вместо результата будет OverflowError.",
        "en": "The argument is not an angle: math.radians and math.degrees have no place here, hyperbolic functions take a plain real number. The result is unbounded and grows exponentially, so past roughly 710 in absolute value you get OverflowError instead of a value."
      },
      "syntax": "math.sinh(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.sinh",
      "version": "",
      "section": "Числа и математика",
      "subcat": "гиперболические",
      "color_group": "module",
      "aliases": [
        "гиперболический синус",
        "гиперболические функции"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.sinh(0))   # → 0.0",
        "print(math.sinh(1))   # → 1.1752011936438014",
        "print(round(math.sinh(-1), 4))   # → -1.1752",
        "print(math.isclose(math.sinh(1), (math.e - 1 / math.e) / 2))   # → True",
        "print(math.sinh(1000))   # → OverflowError"
      ],
      "related": [
        "math.asinh",
        "math.cosh",
        "math.sin"
      ],
      "related_errors": []
    },
    {
      "id": "math.sumprod",
      "title": "math.sumprod",
      "kind": "function",
      "summary": {
        "ru": "Сумма попарных произведений двух последовательностей (скалярное произведение, 3.12+).",
        "en": "The sum of products of two sequences (dot product; 3.12+)."
      },
      "body": {
        "ru": "В отличие от zip, разная длина последовательностей — не молчаливое усечение, а ValueError. Для float результат обычно точнее, чем sum(a * b for a, b in zip(p, q)), потому что накопление ведётся с повышенной точностью; для int арифметика остаётся целочисленной и точной.",
        "en": "Unlike zip, mismatched lengths are a ValueError here, not a silent truncation. For floats it is usually more accurate than sum(a * b for a, b in zip(p, q)) because the accumulation carries extra precision, and for ints the arithmetic stays exact and integral."
      },
      "syntax": "math.sumprod(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.sumprod",
      "version": "3.12",
      "section": "Числа и математика",
      "subcat": "произведение",
      "color_group": "module",
      "aliases": [
        "скалярное произведение векторов",
        "сумма попарных произведений"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.sumprod((1, 2, 3), (4, 5, 6)))   # → 32",
        "print(math.sumprod([100, 250, 30], [2, 1, 5]))   # → 600",
        "print(math.sumprod([], []))   # → 0",
        "print(math.sumprod([1, 2, 3], [4, 5, 6]) == sum(x * y for x, y in zip([1, 2, 3], [4, 5, 6])))   # → True",
        "print(math.sumprod([1, 2, 3], [4, 5]))   # → ValueError"
      ],
      "related": [
        "math.fma",
        "math.fsum",
        "math.prod",
        "zip"
      ],
      "related_errors": [
        "ValueError"
      ]
    },
    {
      "id": "math.tanh",
      "title": "math.tanh",
      "kind": "function",
      "summary": {
        "ru": "Гиперболический тангенс.",
        "en": "Hyperbolic tangent."
      },
      "body": {
        "ru": "Единственная из гиперболических, которая не переполняется: результат всегда внутри (-1, 1), какой бы конечный аргумент ни подали. Зато она быстро насыщается — уже при аргументе около 19 float не отличает ответ от единицы и возвращается ровно 1.0, поэтому обратный ход через math.atanh на таких значениях упадёт с ValueError.",
        "en": "This is the one hyperbolic function that cannot overflow: for any finite argument the result stays inside (-1, 1). It saturates fast, though — by an argument of about 19 the float is indistinguishable from one and you get exactly 1.0 back, so trying to invert it with math.atanh at that point raises ValueError."
      },
      "syntax": "math.tanh(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.tanh",
      "version": "",
      "section": "Числа и математика",
      "subcat": "гиперболические",
      "color_group": "module",
      "aliases": [
        "гиперболический тангенс",
        "гиперболические функции"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.tanh(0))   # → 0.0",
        "print(math.tanh(1))   # → 0.7615941559557649",
        "print(round(math.tanh(-1), 4))   # → -0.7616",
        "print(math.isclose(math.tanh(1), math.sinh(1) / math.cosh(1)))   # → True",
        "print(math.tanh(1000))   # → 1.0"
      ],
      "related": [
        "math.atanh",
        "math.sinh",
        "math.tan"
      ],
      "related_errors": []
    },
    {
      "id": "math.ulp",
      "title": "math.ulp",
      "kind": "function",
      "summary": {
        "ru": "Величина младшего разряда (unit in the last place) числа — шаг между соседними float.",
        "en": "The value of the least significant bit (unit in the last place)."
      },
      "body": {
        "ru": "Расстояние между соседними float не постоянно: оно растёт вместе с величиной числа — около 1.0 это примерно 2.2e-16, а около 1e16 уже 2.0, поэтому прибавление единицы к такому числу вообще ничего не меняет. Отсюда правило сравнения: допуск задают не фиксированной эпсилон, а несколькими ulp от самих сравниваемых значений (или просто берут math.isclose с относительным допуском). Доступна с Python 3.9.",
        "en": "The gap between neighbouring floats is not constant — it scales with magnitude: around 1.0 it is about 2.2e-16, but around 1e16 it is already 2.0, which is why adding 1 to such a value changes nothing. That is the argument against a fixed epsilon: size your tolerance as a few ulp of the values being compared, or just use math.isclose with its relative tolerance. Available since Python 3.9."
      },
      "syntax": "math.ulp(...)",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/math.html#math.ulp",
      "version": "3.9",
      "section": "Числа и математика",
      "subcat": "представление",
      "color_group": "module",
      "aliases": [
        "размер младшего разряда",
        "шаг между соседними вещественными числами"
      ],
      "keywords": [],
      "tags": [
        "math"
      ],
      "examples": [
        "import math",
        "print(math.ulp(1.0))   # → 2.220446049250313e-16",
        "print(math.ulp(0.0))   # → 5e-324",
        "print(math.ulp(1e16))   # → 2.0",
        "print(1.0 + math.ulp(1.0))   # → 1.0000000000000002",
        "print(1.0 + math.ulp(1.0) / 2 == 1.0)   # → True"
      ],
      "related": [
        "math.nextafter",
        "math.isclose",
        "float.hex"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.kde",
      "title": "statistics.kde",
      "kind": "function",
      "summary": {
        "ru": "Оценка плотности распределения ядром (kernel density estimation): возвращает функцию плотности по выборке (Python 3.13+).",
        "en": "Kernel density estimation: returns a density function estimated from data (3.13+)."
      },
      "body": {
        "ru": "Всё решает h: слишком маленькое даёт частокол пиков вокруг каждого наблюдения, слишком большое размазывает данные в один бугор — подбирают его на глаз по графику. Возвращается функция, а не готовые числа: её вызывают в точках сетки, а с cumulative=True получают функцию распределения вместо плотности.",
        "en": "The bandwidth h decides everything: too small and you get a spike over every observation, too large and the data smears into a single hump — it is usually tuned by eye on a plot. What comes back is a function, not numbers: call it at the grid points you want, and pass cumulative=True if you need the distribution function instead of the density."
      },
      "syntax": "statistics.kde(data, h, kernel='normal')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.kde",
      "version": "3.13",
      "section": "Числа и математика",
      "subcat": "статистика",
      "color_group": "module",
      "aliases": [
        "ядерная оценка плотности",
        "оценка плотности распределения"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(not hasattr(statistics, 'kde') or callable(statistics.kde))   # → True",
        "f = statistics.kde([1.0, 2.0, 3.0], h=1.0)",
        "print(round(f(2.0), 4))   # → 0.2943",
        "print(statistics.kde([0.0], h=1.0, kernel='uniform')(0.0))   # → 0.5",
        "print(statistics.kde([], h=1.0))   # → StatisticsError"
      ],
      "related": [
        "statistics.kde_random",
        "statistics.NormalDist",
        "statistics.quantiles"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.kde_random",
      "title": "statistics.kde_random",
      "kind": "function",
      "summary": {
        "ru": "Возвращает генератор случайных чисел из оценки плотности ядром (kde), полученной по выборке (Python 3.13+).",
        "en": "Return a random-number generator drawing from a kernel density estimate (3.13+)."
      },
      "body": {
        "ru": "Отличие от random.choice по той же выборке в сглаживании: генератор выдаёт значения между наблюдениями и за их пределами, а не только те числа, что были в данных. Из-за этого с ядром 'normal' у величины вроде возраста или длительности легко получится отрицательный результат — для ограниченных снизу данных проверяйте выход или берите ядро с конечным носителем. Аргумент seed фиксирует последовательность, без него каждый запуск свой.",
        "en": "Unlike random.choice over the same sample, this smooths: it emits values between the observations and beyond their range, not just the numbers you fed in. With the normal kernel that easily yields a negative draw for a quantity like an age or a duration, so validate the output or pick a kernel with finite support when the data is bounded. Passing seed makes the stream reproducible; without it every run differs."
      },
      "syntax": "statistics.kde_random(data, h, kernel='normal')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.kde_random",
      "version": "3.13",
      "section": "Числа и математика",
      "subcat": "статистика",
      "color_group": "module",
      "aliases": [
        "случайные числа по выборке",
        "генерация значений по ядерной оценке плотности",
        "смоделировать выборку по имеющимся данным"
      ],
      "keywords": [],
      "tags": [
        "statistics"
      ],
      "examples": [
        "import statistics",
        "print(not hasattr(statistics, 'kde_random') or callable(statistics.kde_random))   # → True",
        "rand = statistics.kde_random([0, 10], h=2, kernel='uniform', seed=1)   # прямоугольное ядро, seed делает выборку воспроизводимой",
        "print(type(rand()))   # → <class 'float'>",
        "print(all(-2 <= rand() <= 12 for _ in range(100)))   # → True",
        "print(statistics.kde_random([1, 2, 3], h=0))   # → StatisticsError"
      ],
      "related": [
        "statistics.kde",
        "statistics.NormalDist",
        "random.gauss",
        "random.choices"
      ],
      "related_errors": []
    },
    {
      "id": "statistics.quantiles",
      "title": "statistics.quantiles()",
      "kind": "function",
      "summary": {
        "ru": "Вычисляет квантили набора данных. n=4 → квартили, n=100 → перцентили.",
        "en": "Computes the quantiles of a data set. n=4 → quartiles, n=100 → percentiles."
      },
      "body": {
        "ru": "Возвращается n-1 точек разреза, а не n: при n=4 это три квартиля, а не четыре числа. По умолчанию method='exclusive' считает данные выборкой из большей популяции, поэтому цифры не совпадут с numpy.percentile, чьё поведение по умолчанию ближе к method='inclusive' — его и передавайте, если ваш набор и есть вся совокупность. Меньше двух значений на входе — StatisticsError.",
        "en": "You get n-1 cut points, not n: with n=4 that is three quartiles, not four numbers. The default method='exclusive' treats the data as a sample drawn from a larger population, so results will not match numpy.percentile, whose default behaves closer to method='inclusive' — pass that when your data is the whole population. Fewer than two data points raises StatisticsError."
      },
      "syntax": "statistics.quantiles(data, n=4, method='exclusive')",
      "status": "ready",
      "docs_url": "https://docs.python.org/3/library/statistics.html#statistics.quantiles",
      "version": "3.8",
      "section": "Числа и математика",
      "subcat": "statistics",
      "color_group": "op",
      "aliases": [
        "квартили",
        "перцентили",
        "процентили"
      ],
      "keywords": [],
      "tags": [
        "op"
      ],
      "examples": [
        "import statistics",
        "data = list(range(1, 11))",
        "statistics.quantiles(data, n=4) # → [3.25, 5.5, 7.75]",
        "statistics.quantiles(data, n=2) # → [5.5]  медиана",
        "statistics.quantiles([1,2,3,4,5,6,7,8,9,10], n=10)",
        "# → [1.9, 2.8, 3.7, 4.6, 5.5, 6.4, 7.3, 8.2, 9.1]"
      ],
      "related": [
        "statistics.median",
        "statistics.stdev",
        "statistics.NormalDist"
      ],
      "related_errors": []
    }
  ]
}
