Синтаксичний аналіз (парсинг)

Синтаксичний аналізатор (парсер) — обробляє потік лексем (токенів). На основі їх комбінацій інтерпретатор визначає, які оператори записані в програмі.

Якщо лексер або парсер натрапляють на невідому їм конструкцію, інтерпретатор повідомляє про синтаксичну помилку SyntaxError із зазначенням місця в коді, де її було виявлено.

Якщо синтаксичних помилок немає, у результаті формується бінарний код, готовий до виконання на Python Virtual Machine.

У цьому розділі:

Компіляція в байткод

Байткод (англ. bytecode) — це внутрішнє представлення програми в інтерпретаторі CPython. Внутрішнє представлення програми виконується віртуальною машиною Python.

Розглянемо приклад простої програми, яка виконую аріфметичні дії:

a = 1
b = 2
c = a + b
print(c)

Байткод для цієї програми:

  0           RESUME                   0

  1           LOAD_CONST               0 (1)
              STORE_NAME               0 (a)

  2           LOAD_CONST               1 (2)
              STORE_NAME               1 (b)

  3           LOAD_NAME                0 (a)
              LOAD_NAME                1 (b)
              BINARY_OP                0 (+)
              STORE_NAME               2 (c)

  4           LOAD_NAME                3 (print)
              PUSH_NULL
              LOAD_NAME                2 (c)
              CALL                     1
              POP_TOP
              RETURN_CONST             2 (None)
Python Операція Операнд (значення) Опис
RESUME 0 Відновлює виконання функції або коду.
a = 1 LOAD_CONST 0 (1) Завантажує константу 1 у стек.
STORE_NAME 0 (a) Зберігає значення 1 у змінну a.
b = 2 LOAD_CONST 1 (2) Завантажує константу 2 у стек.
STORE_NAME 1 (b) Зберігає значення 2 у змінну b.
c = a + b LOAD_NAME 0 (a) Завантажує значення змінної a у стек.
LOAD_NAME 1 (b) Завантажує значення змінної b у стек.
BINARY_OP 0 (+) Додає значення a і b (тобто 1 + 2).
STORE_NAME 2 (c) Зберігає результат у змінну c.
print(c) LOAD_NAME 3 (print) Завантажує ім’я print у стек.
PUSH_NULL Додає NULL у стек перед викликом функції.
LOAD_NAME 2 (c) Завантажує ім’я c у стек.
CALL 1 Викликає функцію print із 1 аргументом (c).
POP_TOP Видаляє верхній елемент зі стека. Це потрібно, тому що print(c) повертає None, і цей результат не використовується.
RETURN_CONST 2 (None) Повертає None (типове значення для коду, що не має явногоreturn).

Для складної програми байткод теж буде складнішим. Наприклад, для програми пошуку коренів квадратного рівняння, яка складається з 8 операторів:

def square_root(a: float, b: float, c: float) -> None:
    # Пошук коренів квадратного рівняння виду ax^2 + bx + c = 0
    discriminant = b * b - 4 * a * c

    if discriminant < 0:
        print("Рівняння не має дійсних коренів (дискримінант < 0).")

    root1 = (-b + discriminant**0.5) / (2 * a)
    root2 = (-b - discriminant**0.5) / (2 * a)

    print("Корені квадратного рівняння:", root1, root2)

square_root(10, 5, -17)

байткод буде складатись з 69 операцій:

  1           RESUME                   0

  3           LOAD_FAST_LOAD_FAST     17 (b, b)
              BINARY_OP                5 (*)
              LOAD_CONST               1 (4)
              LOAD_FAST                0 (a)
              BINARY_OP                5 (*)
              LOAD_FAST                2 (c)
              BINARY_OP                5 (*)
              BINARY_OP               10 (-)
              STORE_FAST               3 (discriminant)

  5           LOAD_FAST                3 (discriminant)
              LOAD_CONST               2 (0)
              COMPARE_OP              18 (bool(<))
              POP_JUMP_IF_FALSE       11 (to L1)

  6           LOAD_GLOBAL              1 (print + NULL)
              LOAD_CONST               3 ('Рівняння не має дійсних коренів (дискримінант < 0).')
              CALL                     1
              POP_TOP

  8   L1:     LOAD_FAST                1 (b)
              UNARY_NEGATIVE
              LOAD_FAST                3 (discriminant)
              LOAD_CONST               4 (0.5)
              BINARY_OP                8 (**)
              BINARY_OP                0 (+)
              LOAD_CONST               5 (2)
              LOAD_FAST                0 (a)
              BINARY_OP                5 (*)
              BINARY_OP               11 (/)
              STORE_FAST               4 (root1)

  9           LOAD_FAST                1 (b)
              UNARY_NEGATIVE
              LOAD_FAST                3 (discriminant)
              LOAD_CONST               4 (0.5)
              BINARY_OP                8 (**)
              BINARY_OP               10 (-)
              LOAD_CONST               5 (2)
              LOAD_FAST                0 (a)
              BINARY_OP                5 (*)
              BINARY_OP               11 (/)
              STORE_FAST               5 (root2)

 11           LOAD_GLOBAL              1 (print + NULL)
              LOAD_CONST               6 ('Корені квадратного рівняння:')
              LOAD_FAST_LOAD_FAST     69 (root1, root2)
              CALL                     3
              POP_TOP
              RETURN_CONST             0 (None)

Різні реалізації інтерпретатора Python

Існує декілька реалізацій інтерпретатора мови Python, короткий перелік:

  • CPython — офіційна реалізація, використовується для більшості задач;
  • PyPy — використовує JIT-компіляцію (Just-In-Time), що дозволяє значно прискорити виконання Python-коду в порівнянні з CPython;
  • Jython — працює на платформі Java Virtual Machine (JVM);
  • IronPython — реалізація для .NET Framework та Mono. Дозволяє інтегрувати Python-код із мовами C#, F# та іншими .NET-екосистеми;
  • MicroPython — полегшена реалізація, призначена для вбудованих систем та мікроконтролерів. Працює на пристроях з обмеженими ресурсами, таких як ESP8266, ESP32, Raspberry Pi Pico, STM32;

CPython — це стандартна та найпоширеніша реалізація мови програмування Python, написана мовою програмування C. Вона забезпечує інтерпретацію та виконання Python-коду, компілюючи його у байткод для віртуальної машини Python (Python Virtual Machine, PVM), яка потім виконує цей байткод.

CPython підтримує динамічну типізацію, автоматичне керування пам'яттю через збирач сміття та має багату стандартну бібліотеку, аналіз даних тощо. Вона є референсною реалізацією Python, тобто нові функції мови спочатку додаються саме до CPython.

CPython є офіційною реалізацією Python і використовується у більшості застосувань мови, включаючи веб-розробку, автоматизацію, аналіз даних. У більшості випадків, коли кажуть "інтерпретатор Python", то мають на увазі саме реалізацію CPython.

Окремо треба зазначити, що PyCharm, VS Code, Idle, Notebook++, Sublime Text не є ані інтерпретаторами, ані компіляторами мови Python. Це інтегровані середовища розробки (Integrated Development Environment, IDE) або редактори коду, але ніяк не реалізації мови Python.

Розширений перелік реалізацій мови Python: https://wiki.python.org/moin/PythonImplementations.

Яку реалізацію інтерпретатора обрати для вивчення Python?

Для вивчення мови треба обрати стандартну реалізацію CPython, яка доступна з адреси https://www.python.org/downloads/

У нашому курсі вивчається мова Python, яка повинна працювати однаково на будь-якій реалізації. Також будуть надані деякі особливості реалізації CPython. Якщо будуть показані особливості інших реалізацій, то це буде явно зазначене.

Джерела