{
  "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 взаємодіють одна з одною: неповна копія, яка залишає вкладені дані спільними, коли ви очікували, що вони дубльовані, значення return генератора, яке тихо не стає частиною його виходу, декоратори, розташовані в порядку, який легко перепутати. Це той вид помилок, який старший рецензент помічає за десять секунд, а всім іншим потрібна година, щоб їх відстежити.",
          "recommendation": "Сконцентруйтеся на внутрішніх частинах об'єктів та ітерації: поверхневе копіювання проти глибокого копіювання, що насправді робить генератор зі своїм return, та як порядок розміщення декораторів працює. Потім основи класів, які більшість посібників пропускає — рівність за замовчуванням без __eq__, та чому змінюваний атрибут класу спільний для кожного екземпляра."
        },
        "advanced": {
          "name": "Продвинутий рівень",
          "desc": "Це рівень, який мають на увазі більшість оголошень про роботу під «сильні знання Python». Ви сягаєте потрібного інструменту без його пошуку, ви знаєте, чому змінилася певна частина спільного стану, а не лише те, що вона змінилася, і ви можете прочитати чужу функцію та правильно передбачити її вихід. Те, що відділяє цей рівень від вершини, — це захисна частина роботи: знання того, який із двох правильно виглядаючих підходів буде поводитися неправильно, коли в гру включиться атрибут класу, ключовий аргумент або ієрархія виключень.",
          "recommendation": "Поглиблюйтесь у частини, які захищають кодову базу, яку також торкаються інші люди: ключові аргументи як архітектурний вибір API, а не синтаксична цікавість, ієрархія виключень (щоб пункти except перехоплювали правильну родину помилок, а не лише один клас), та менеджери контексту як стандартний спосіб гарантувати очищення. Глибокі статті Real Python — хороший наступний крок для всіх трьох."
        },
        "expert": {
          "name": "Експерт",
          "desc": "Ви набрали максимальні бали в кожній секції — основний синтаксис, функції та область видимості, структури даних та об'єктно-орієнтовані ідіоми. На практиці це означає, що вам можна дати чужий файл і пояснити, чому він виробляє той вихід, який виробляє, а не лише те, що синтаксис повинен робити — це важча та цінніша навичка. На цьому рівні сама мова рідко є обмежуючим фактором у вашій роботі; обмеження зазвичай залежить від розміру або форми системи навколо коду.",
          "recommendation": "Зараз прибутковість залежить від інструментів і рецензування: статична перевірка типів за допомогою mypy або pyright, профілювання перед оптимізацією, а не вгадування, та читання pull-запитів інших людей саме на цю категорію помилок — ті, що проходять кожен тест і все ще роблять неправильну річ при специфічному введенні даних. Якщо вас перевіряють на роботу, опишіть помилку, подібну до цієї, яку ви знайшли, а не назвіть функції мови — це демонструє рішення, а не лише словниковий запас."
        }
      },
      "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] — кожна lambda ищет i коли викликається, а до того часу цикл закінчується з i, рівним 2"
            },
            {
              "icon": "",
              "label": "[0, 1, 2] — кожна lambda захоплює значення i на момент створення"
            },
            {
              "icon": "",
              "label": "[0, 0, 0] — lambdas завжди захоплюють перше значення, присвоєне змінній"
            },
            {
              "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": "Функція генератора yield 1, потім yield 2, потім return 99. Що містить list(the_generator()), і що відбувається з 99?",
          "options": [
            {
              "icon": "",
              "label": "[1, 2, 99] — return всередину генератора поводиться як ще один yield"
            },
            {
              "icon": "",
              "label": "[99] — return одразу перекриває всі раніше вихідні значення"
            },
            {
              "icon": "",
              "label": "[1, 2] — значення return генератора стає значенням, прикріпленим до 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": "Той із квадратними дужками — списковість у 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 спираються на істинність, і порожній список є хибним"
            }
          ]
        }
      ],
      "optionOrderVersion": "92945565db6c55da"
    }
  }
}
