{
  "assessmentTests": {
    "python_test": {
      "name": "Тест на знание Python",
      "desc": "30 практических вопросов о синтаксисе, функциях, структурах данных и поведении объектов — узнайте, соответствует ли ваше знание Python тому, что работодатель имеет в виду под компетентностью в Python.",
      "recommendation": "Ваш профиль навыков Python",
      "results": {
        "beginner": {
          "name": "Начинающий",
          "desc": "Вы можете следить за коротким скриптом и менять значения внутри него, но вопросы, которые вы пропустили, группируются вокруг специфического поведения самого языка, а не вокруг общей логики — аргумент по умолчанию, который создаётся один раз и переиспользуется; is и == на двух похожих списках; переменная, которая становится локальной в момент присваивания ей значения где-то в функции. Это не о плохом программировании; это правила, специфичные для Python, которые запутывают людей, переходящих из других языков, не менее часто, чем начинающих. Они важны на работе, потому что каждое из них — место, где может скрываться баг в коде, который на первый взгляд выглядит правильно.",
          "recommendation": "Начните с трёх вещей в таком порядке: опасность изменяемых аргументов по умолчанию (почему basket=[] опасно), разница между is и ==, и как работает область видимости переменных внутри функции. Официальное руководство Python на docs.python.org рассматривает все три напрямую, а термин «Python gotchas» выдаст вам проработанные примеры каждого."
        },
        "intermediate": {
          "name": "Средний уровень",
          "desc": "Вы комфортно справляетесь с повседневным написанием скриптов — структуры данных, управление потоком, простые функции — и не будете затруднены рутинной поддержкой или небольшой фичей в существующей кодовой базе. Разница между здесь и Продвинутым уровнем заключается в основном в том, что происходит, когда удобные возможности Python взаимодействуют друг с другом: поверхностное копирование, оставляющее вложенные данные общими, когда вы ожидали дублирования; возвращаемое значение генератора, которое незаметно не становится частью его выхода; декораторы, складывающиеся в порядке, который легко перепутать. Это те баги, которые старший ревьюер заметит за десять секунд, а все остальные будут ловить час.",
          "recommendation": "Сосредоточьтесь на внутренностях объектов и итерации: поверхностное копирование против глубокого, что генератор действительно делает с return, и как работает порядок складывания декораторов. Затем основы классов, которые большинство туториалов пропускают — равенство по умолчанию без __eq__ и почему изменяемый атрибут класса общий для каждого экземпляра."
        },
        "advanced": {
          "name": "Продвинутый",
          "desc": "Это уровень, который имеют в виду большинство вакансий, когда пишут «сильный Python». Вы берёте нужный инструмент, не охотясь за ним, вы знаете, почему общее состояние изменилось, а не только что оно изменилось, и вы можете прочитать функцию другого человека и правильно предсказать её выход. То, что отделяет этот диапазон от вершины, — это защитная сторона работы: знание того, какой из двух выглядящих правильно подходов будет вести себя неправильно, когда участвует атрибут класса, аргумент только по ключевому слову или иерархия исключений.",
          "recommendation": "Углубитесь в части, которые защищают кодовую базу, которую трогают и другие люди: аргументы только по ключевому слову как выбор дизайна API, а не синтаксическая любопытство; иерархия исключений (чтобы предложения except ловили правильное семейство ошибок, а не только один класс); и контекстные менеджеры как стандартный способ гарантировать очистку. Статьи Real Python с глубокими погружениями — хорошая следующая остановка для всех трёх."
        },
        "expert": {
          "name": "Эксперт",
          "desc": "Вы набрали максимум во всех разделах — основной синтаксис, функции и область видимости, структуры данных и объектно-ориентированные идиомы. Практически это означает, что вы можете взять чужой файл и объяснить, почему он производит выход, который производит, не только что синтаксис говорит, что он должен производить — это сложнее и более ценное умение. На этом уровне сам язык редко является ограничивающим фактором в вашей работе; обычно ограничение — это размер или форма системы вокруг кода.",
          "recommendation": "Возвраты теперь в инструментарии и ревью: статическая проверка типов с mypy или pyright, профилирование перед оптимизацией вместо гадания, и чтение pull request других людей именно на эту категорию багов — те, что проходят все тесты и всё ещё делают неправильное при конкретном входе. Если вас отбирают на роль, опишите баг, похожий на этот, который вы нашли, вместо называния свойств языка — это демонстрирует рассуждение, не только словарь."
        }
      },
      "questions": [
        {
          "question": "Вы пишете def add_item(item, basket=[]): basket.append(item); return basket, затем вызываете add_item('a') и add_item('b'), оба раза оставляя basket. Что возвращает второй вызов?",
          "options": [
            {
              "icon": "",
              "label": "['b'] — каждый вызов получает свежий пустой список по умолчанию"
            },
            {
              "icon": "",
              "label": "TypeError — вы должны передать аргумент basket"
            },
            {
              "icon": "",
              "label": "['a', 'b'] — список по умолчанию создаётся один раз и переиспользуется между вызовами"
            },
            {
              "icon": "",
              "label": "['a'] затем ['b'] как два независимых однопредметных списка"
            }
          ]
        },
        {
          "question": "a = [1, 2, 3] и b = [1, 2, 3]. Что возвращает a == b, и что возвращает a is b?",
          "options": [
            {
              "icon": "",
              "label": "True для обоих — идентичные списки всегда один и тот же объект"
            },
            {
              "icon": "",
              "label": "True для ==, False для is — равное содержимое, но разные объекты в памяти"
            },
            {
              "icon": "",
              "label": "False для обоих — отдельные литералы никогда не равны в Python"
            },
            {
              "icon": "",
              "label": "True для is, False для == — is проверяет равенство значений для списков"
            }
          ]
        },
        {
          "question": "В Python 3, что вычисляется 7 / 2, и что вычисляется 7 // 2?",
          "options": [
            {
              "icon": "",
              "label": "3.5, затем 3 — / всегда возвращает float, // округляет результат вниз"
            },
            {
              "icon": "",
              "label": "3, затем 3.5 — / обрезает и // возвращает float"
            },
            {
              "icon": "",
              "label": "3.5, затем 3.5 — оба оператора возвращают float в Python 3"
            },
            {
              "icon": "",
              "label": "3, затем 3 — оба оператора округляют вниз в Python 3"
            }
          ]
        },
        {
          "question": "s = 'hello', затем вы запускаете s[0] = 'H'. Что происходит?",
          "options": [
            {
              "icon": "",
              "label": "s становится 'Hello'"
            },
            {
              "icon": "",
              "label": "s становится 'Hhello', потому что символ вставляется, а не заменяется"
            },
            {
              "icon": "",
              "label": "TypeError — строки неизменяемы и не поддерживают присваивание элемента"
            },
            {
              "icon": "",
              "label": "Ничего — присваивание молча игнорируется и s остаётся 'hello'"
            }
          ]
        },
        {
          "question": "x = [10, 20, 30, 40, 50]. Что возвращает x[1:4]?",
          "options": [
            {
              "icon": "",
              "label": "[20, 30, 40, 50]"
            },
            {
              "icon": "",
              "label": "[10, 20, 30, 40]"
            },
            {
              "icon": "",
              "label": "[20, 30, 40]"
            },
            {
              "icon": "",
              "label": "[30, 40]"
            }
          ]
        },
        {
          "question": "name = 'Ana' и age = 30. Что производит f'{name} is {age}'?",
          "options": [
            {
              "icon": "",
              "label": "{name} is {age}"
            },
            {
              "icon": "",
              "label": "Ana is 30"
            },
            {
              "icon": "",
              "label": "name is age"
            },
            {
              "icon": "",
              "label": "TypeError, потому что f-строки требуют метода format()"
            }
          ]
        },
        {
          "question": "Что вычисляется 1 < 2 < 3?",
          "options": [
            {
              "icon": "",
              "label": "True — Python объединяет сравнения как (1 < 2) and (2 < 3)"
            },
            {
              "icon": "",
              "label": "False — объединённые сравнения вычисляются слева направо без короткого замыкания, давая 1 < 2 < 3 -> 1 < True -> False"
            },
            {
              "icon": "",
              "label": "SyntaxError — сравнения не могут быть объединены"
            },
            {
              "icon": "",
              "label": "3 — Python возвращает последнее значение в цепи"
            }
          ]
        },
        {
          "question": "Что возвращает bool([]), и что возвращает bool([0])?",
          "options": [
            {
              "icon": "",
              "label": "False, затем False — любой список, содержащий только ложные значения, ложен"
            },
            {
              "icon": "",
              "label": "False, затем True — пустой список ложен, список с одним элементом (даже 0) истинен"
            },
            {
              "icon": "",
              "label": "True, затем True — объекты, не являющиеся None, всегда истинны"
            },
            {
              "icon": "",
              "label": "True, затем False — список истинен только когда пуст"
            }
          ]
        },
        {
          "question": "Существует count = 0 на уровне модуля. Внутри def increment(): count += 1; return count вы вызываете increment(). Что происходит?",
          "options": [
            {
              "icon": "",
              "label": "UnboundLocalError — присваивание count где-то в функции делает его локальным, поэтому count += 1 пытается прочитать локальную переменную до её присваивания"
            },
            {
              "icon": "",
              "label": "Возвращает 1 и увеличивает count на уровне модуля до 1"
            },
            {
              "icon": "",
              "label": "Возвращает 1, но оставляет count на уровне модуля на 0"
            },
            {
              "icon": "",
              "label": "Вызывает NameError, потому что count никогда не объявлялась внутри функции"
            }
          ]
        },
        {
          "question": "def f(*args, **kwargs): return args, kwargs. Что возвращает f(1, 2, x=3)?",
          "options": [
            {
              "icon": "",
              "label": "([1, 2], {'x': 3})"
            },
            {
              "icon": "",
              "label": "((1, 2), {'x': 3})"
            },
            {
              "icon": "",
              "label": "(1, 2, {'x': 3})"
            },
            {
              "icon": "",
              "label": "TypeError — позиционные и именованные аргументы не могут быть смешаны в одном вызове"
            }
          ]
        },
        {
          "question": "Вы строите funcs = [lambda: i for i in range(3)] в цикле. Что возвращает [f() for f in funcs]?",
          "options": [
            {
              "icon": "",
              "label": "[2, 2, 2] — каждая лямбда ищет i при вызове, и к тому времени цикл завершился с i равным 2"
            },
            {
              "icon": "",
              "label": "[0, 1, 2] — каждая лямбда захватывает значение i в момент её создания"
            },
            {
              "icon": "",
              "label": "[0, 0, 0] — лямбды всегда захватывают первое значение, присвоенное переменной"
            },
            {
              "icon": "",
              "label": "NameError, потому что i больше не существует после окончания цикла"
            }
          ]
        },
        {
          "question": "Функция определена с @a прямо над @b, которая сидит прямо над def f(): pass. В каком порядке декораторы применяются к f?",
          "options": [
            {
              "icon": "",
              "label": "b оборачивает f сначала, затем a оборачивает результат b — декоратор, ближайший к функции, работает первым"
            },
            {
              "icon": "",
              "label": "a оборачивает f сначала, затем b оборачивает результат — декораторы применяются сверху вниз"
            },
            {
              "icon": "",
              "label": "Оба декоратора оборачивают f независимо и только последний определённый вступает в силу"
            },
            {
              "icon": "",
              "label": "Это зависит от порядка импорта декораторов, а не их положения в коде"
            }
          ]
        },
        {
          "question": "def f(a, *, b): return a + b. Какой из этих вызовов работает?",
          "options": [
            {
              "icon": "",
              "label": "f(1, b=2) — b должна быть передана по ключевому слову, потому что она идёт после голой * в сигнатуре"
            },
            {
              "icon": "",
              "label": "f(1, 2) — позиционные аргументы заполняют параметры слева направо независимо от *"
            },
            {
              "icon": "",
              "label": "f(a=1, 2) — именованные аргументы должны идти первыми"
            },
            {
              "icon": "",
              "label": "f(1) — b необязательна, потому что идёт после *"
            }
          ]
        },
        {
          "question": "Функция-генератор yields 1, затем yields 2, затем делает return 99. Что содержит list(the_generator()), и что происходит с 99?",
          "options": [
            {
              "icon": "",
              "label": "[1, 2, 99] — return внутри генератора ведёт себя как один более yield"
            },
            {
              "icon": "",
              "label": "[99] — return немедленно переопределяет любые уже выданные значения"
            },
            {
              "icon": "",
              "label": "[1, 2] — возвращаемое значение генератора становится значением, присоединённым к StopIteration, который он вызывает; оно не выдаётся"
            },
            {
              "icon": "",
              "label": "RuntimeError, потому что генераторы не могут содержать return"
            }
          ]
        },
        {
          "question": "Во внешней функции outer() устанавливается x = 1, затем вложенная inner() делает nonlocal x; x += 1, и outer() вызывает inner() перед возвращением x. Что возвращает outer()?",
          "options": [
            {
              "icon": "",
              "label": "1 — inner() работает с собственной локальной копией x, поэтому изменение не сохраняется"
            },
            {
              "icon": "",
              "label": "UnboundLocalError, потому что x не объявлена внутри inner() перед заявлением nonlocal"
            },
            {
              "icon": "",
              "label": "SyntaxError — nonlocal может использоваться только внутри классов"
            },
            {
              "icon": "",
              "label": "2 — nonlocal позволяет inner() изменять x внешней функции вместо создания новой локальной переменной"
            }
          ]
        },
        {
          "question": "d = {'a': 1, 'b': 2}. Что возвращает d.get('c', 0), и добавляет ли оно 'c' в словарь?",
          "options": [
            {
              "icon": "",
              "label": "0, и нет — get() возвращает значение по умолчанию без вставки ключа"
            },
            {
              "icon": "",
              "label": "0, и да — get() вставляет ключ со значением по умолчанию"
            },
            {
              "icon": "",
              "label": "KeyError, потому что 'c' не существует"
            },
            {
              "icon": "",
              "label": "None, и да — get() всегда добавляет отсутствующие ключи со значением None"
            }
          ]
        },
        {
          "question": "t = (1, 2, [3, 4]). Можете ли вы запустить t[2].append(5), и можете ли вы запустить t[0] = 10?",
          "options": [
            {
              "icon": "",
              "label": "Оба работают — вложение списка внутри кортежа делает весь кортеж изменяемым"
            },
            {
              "icon": "",
              "label": "Оба вызывают TypeError — ничто внутри кортежа никогда не может быть изменено"
            },
            {
              "icon": "",
              "label": "t[2].append(5) вызывает TypeError, но t[0] = 10 работает, потому что кортежи допускают переназначение их первого элемента"
            },
            {
              "icon": "",
              "label": "t[2].append(5) работает, потому что список внутри изменяем; t[0] = 10 вызывает TypeError, потому что сам кортеж неизменяем"
            }
          ]
        },
        {
          "question": "Какой из них возвращает итератор, который вычисляет значения лениво, а не строит всю последовательность в памяти сразу: [x*x for x in range(1000000)] или (x*x for x in range(1000000))?",
          "options": [
            {
              "icon": "",
              "label": "Тот, что с скобками, (x*x for x in range(1000000)) — это выражение генератора"
            },
            {
              "icon": "",
              "label": "Тот, что с квадратными скобками — list comprehensions всегда ленивы в Python 3"
            },
            {
              "icon": "",
              "label": "Оба ленивы — единственное различие в типе, возвращаемом в конце"
            },
            {
              "icon": "",
              "label": "Ни один — оба строят полную последовательность в памяти сразу же"
            }
          ]
        },
        {
          "question": "Блок try делит на ноль, предложение except ZeroDivisionError печатает 'a', и finally печатает 'b'. Что печатается и в каком порядке?",
          "options": [
            {
              "icon": "",
              "label": "b затем a — finally всегда запускается перед любым except"
            },
            {
              "icon": "",
              "label": "a затем b — подходящий except запускается первым, и finally всегда запускается после независимо"
            },
            {
              "icon": "",
              "label": "Только b — исключение не захватывается, поэтому 'a' никогда не печатается"
            },
            {
              "icon": "",
              "label": "Только a — finally пропускается, как только исключение уже обработано"
            }
          ]
        },
        {
          "question": "import copy; a = [[1, 2], [3, 4]]; b = copy.copy(a); b[0].append(99). Изменится ли a[0] тоже?",
          "options": [
            {
              "icon": "",
              "label": "Нет — copy.copy() всегда дублирует вложенные объекты тоже"
            },
            {
              "icon": "",
              "label": "Нет — добавление к b[0] создаёт совершенно новый список только для b[0]"
            },
            {
              "icon": "",
              "label": "Да, но только потому что copy.copy() был вызван без аргумента deepcopy"
            },
            {
              "icon": "",
              "label": "Да — copy.copy() делает поверхностную копию, поэтому вложенные списки всё ещё общие между a и b"
            }
          ]
        },
        {
          "question": "d = {'x': 1, 'y': 2}. Что проверяет 'x' in d — ключи, значения или оба?",
          "options": [
            {
              "icon": "",
              "label": "Значения — `in` проверяет, равны ли 1 или 2 'x'"
            },
            {
              "icon": "",
              "label": "Проверяются и ключи, и значения"
            },
            {
              "icon": "",
              "label": "Ни то, ни другое — `in` на словаре всегда вызывает TypeError"
            },
            {
              "icon": "",
              "label": "Ключи — `in` на словаре проверяет членство среди его ключей, а не значений"
            }
          ]
        },
        {
          "question": "names = ['Al', 'Bo'] и scores = [10, 20]. Что возвращает list(zip(names, scores))?",
          "options": [
            {
              "icon": "",
              "label": "[('Al', 'Bo'), (10, 20)]"
            },
            {
              "icon": "",
              "label": "['Al', 10, 'Bo', 20]"
            },
            {
              "icon": "",
              "label": "[('Al', 10), ('Bo', 20)]"
            },
            {
              "icon": "",
              "label": "ValueError, потому что zip требует три или более аргументов"
            }
          ]
        },
        {
          "question": "class Dog: tricks = [] держит список на уровне класса, и add_trick добавляет к self.tricks. d1 = Dog() и d2 = Dog() оба создаются, затем запускается d1.add_trick('sit'). Что содержит d2.tricks?",
          "options": [
            {
              "icon": "",
              "label": "[] — каждый экземпляр получает собственную независимую копию tricks"
            },
            {
              "icon": "",
              "label": "['sit'] — tricks — это список на уровне класса, общий для каждого экземпляра, поэтому изменение d1 видно на d2 тоже"
            },
            {
              "icon": "",
              "label": "TypeError — экземпляры не могут добавлять к атрибуту класса"
            },
            {
              "icon": "",
              "label": "['sit'] только после переюобого d2 = Dog()"
            }
          ]
        },
        {
          "question": "class A имеет def greet(self): return 'A'. class B(A) имеет def greet(self): return 'B' + super().greet(). Что возвращает B().greet()?",
          "options": [
            {
              "icon": "",
              "label": "'AB' — super() всегда запускается перед собственным кодом подкласса"
            },
            {
              "icon": "",
              "label": "'B' — super().greet() игнорируется, потому что B уже переопределяет greet"
            },
            {
              "icon": "",
              "label": "'BA' — super().greet() вызывает метод A и его результат добавляется к 'B'"
            },
            {
              "icon": "",
              "label": "AttributeError, потому что A не имеет видимого greet для B"
            }
          ]
        },
        {
          "question": "class Point хранит self.x в __init__ и не определяет других методов. p1 = Point(1) и p2 = Point(1). Что возвращает p1 == p2 по умолчанию?",
          "options": [
            {
              "icon": "",
              "label": "True — Python сравнивает все совпадающие атрибуты по умолчанию"
            },
            {
              "icon": "",
              "label": "False — без пользовательского __eq__, == откатывается на сравнение идентичности, и p1 и p2 — разные объекты"
            },
            {
              "icon": "",
              "label": "True, но только потому что оба объекта были созданы с одним и тем же аргументом"
            },
            {
              "icon": "",
              "label": "TypeError, потому что Point не определяет __eq__"
            }
          ]
        },
        {
          "question": "Какой except вещь ниже тоже будет ловить ZeroDivisionError: except ArithmeticError, except ValueError, или except KeyError?",
          "options": [
            {
              "icon": "",
              "label": "except ValueError — ошибки деления рассматриваются как недействительные значения"
            },
            {
              "icon": "",
              "label": "except KeyError — любая ошибка времени выполнения, не привязанная к конкретному типу, откатывается на KeyError"
            },
            {
              "icon": "",
              "label": "Ни один из них — только except ZeroDivisionError сам может это ловить"
            },
            {
              "icon": "",
              "label": "except ArithmeticError — ZeroDivisionError — подкласс ArithmeticError"
            }
          ]
        },
        {
          "question": "Вы открываете файл с open('data.txt') as f: и исключение вызывается при чтении внутри блока. Закрывается ли файл всё ещё?",
          "options": [
            {
              "icon": "",
              "label": "Нет — файл остаётся открытым, потому что блок не закончился нормально"
            },
            {
              "icon": "",
              "label": "Только если вы также добавите finally вокруг заявления with"
            },
            {
              "icon": "",
              "label": "Да — заявление with вызывает метод __exit__ файла, который закрывает его, даже если внутри блока происходит исключение"
            },
            {
              "icon": "",
              "label": "Это зависит от типа исключения — только исключения, перехваченные где-то ещё, инициируют очистку"
            }
          ]
        },
        {
          "question": "x = [3, 1, 2] и y = x.sort(). Что содержит y?",
          "options": [
            {
              "icon": "",
              "label": "[1, 2, 3] — sort() возвращает новый отсортированный список"
            },
            {
              "icon": "",
              "label": "None — list.sort() сортирует на месте и возвращает None; сам x становится [1, 2, 3]"
            },
            {
              "icon": "",
              "label": "[3, 1, 2] — sort() возвращает исходный неотсортированный список и только меняет внутренний порядок x"
            },
            {
              "icon": "",
              "label": "TypeError, потому что sort() не может быть назначена переменной"
            }
          ]
        },
        {
          "question": "Что происходит при запуске 'Score: ' + 42?",
          "options": [
            {
              "icon": "",
              "label": "'Score: 42' — Python автоматически конвертирует число внутри выражения +"
            },
            {
              "icon": "",
              "label": "'Score: ' — число молча опускается"
            },
            {
              "icon": "",
              "label": "42 — строка приводится к числу и добавляется"
            },
            {
              "icon": "",
              "label": "TypeError — вы не можете непосредственно объединить str и int; 42 должна быть конвертирована с str() сначала"
            }
          ]
        },
        {
          "question": "Вам нужно проверить, пусто ли список, называемый results. Какой способ идиоматичен для Python: if results == []: , if len(results) == 0: , или if not results: ?",
          "options": [
            {
              "icon": "",
              "label": "if results == []: — это единственный надёжный способ обнаружить пустой список"
            },
            {
              "icon": "",
              "label": "if len(results) == 0: — это требуется, потому что пустые списки истинны в Python"
            },
            {
              "icon": "",
              "label": "Все три одинаково никогда не используются на практике; стандартный подход — if results is None:"
            },
            {
              "icon": "",
              "label": "if not results: — идиома Python полагается на truthiness, и пустой список ложен"
            }
          ]
        }
      ],
      "optionOrderVersion": "92945565db6c55da"
    }
  }
}
