Интроспекция и байткод

Интроспекция — возможность запросить тип и структуру объекта во время выполнения программы.

Доступ к исходным текстам и стеку вызовов

Чтобы было ещё удобнее — inspect:

Бонус: python3 -m inspect [--detail] inspect

Интерпретация исходного текста

Python:

  1. Синтаксический анализатор
  2. Транслятор Python → байткод
  3. Интерпретатор байткода

Ситаксический анализатор

Написан на Си.

Поддерживается 1:1 прикладной модуль ast

   1 >>> import ast
   2 >>> tree = ast.parse("""
   3 ... k = 1
   4 ... for i in range(10):
   5 ...     print(i, k)
   6 ... """)
   7 >>> print(tree)
   8 <ast.Module object at 0x7f8132980dc0>
   9 >>> print(tree.body)
  10 [<ast.Assign object at 0x7f8132981060>, <ast.For object at 0x7f8138be2c50>]
  11 >>> ast.dump(tree)
  12 "Module(body=[Assign(targets=[Name(id='k', ctx=Store())], value=Constant(value=1)), For(target=Name(id='i', ctx=Store()), iter=Call(func=Name(id='range', ctx=Load()), args=[Constant(value=10)], keywords=[]), body=[Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Name(id='i', ctx=Load()), Name(id='k', ctx=Load())], keywords=[]))], orelse=[])], type_ignores=[])"
  13 >>> print(ast.dump(tree, indent=2))
  14 Module(
  15   body=[
  16     Assign(
  17       targets=[
  18         Name(id='k', ctx=Store())],
  19       value=Constant(value=1)),
  20     For(
  21       target=Name(id='i', ctx=Store()),
  22       iter=Call(
  23         func=Name(id='range', ctx=Load()),
  24         args=[
  25           Constant(value=10)],
  26         keywords=[]),
  27       body=[
  28         Expr(
  29           value=Call(
  30             func=Name(id='print', ctx=Load()),
  31             args=[
  32               Name(id='i', ctx=Load()),
  33               Name(id='k', ctx=Load())],
  34             keywords=[]))],
  35       orelse=[])],
  36   type_ignores=[])

Зачем нужно ИРЛ:

Работа с деревом

Транслятор

Исполнитель Python

   1 >>> compile("1 + 2", "<пример>", "eval").co_code
   2 b'\x97\x00y\x00'
   3 >>> compile("a + 2", "<пример>", "eval").co_code
   4 b'\x97\x00e\x00d\x00z\x00\x00\x00S\x00'
   5 >>> compile(tree, "<AST>", "exec").co_code
   6 b'\x97\x00d\x00Z\x00\x02\x00e\x01d\x01\xab\x01\x00\x00\x00\x00\x00\x00D\x00]\x0b\x00\x00Z\x02\x02\x00e\x03e\x02e\x00\xab\x02\x00\x00\x00\x00\x00\x00\x01\x00\x8c\r\x04\x00y\x02'
   7 

Исполнитель — это стековая машина, данные которой — объекты Python.

Модуль dis («дизассемблер»):

«Дизассемблер» можно использовать для оценки быстродействия (стоит помнить, что инструкции имеют различное время выполнение, особенно — связанные созданием пространств имён)

Байт-код Python не предназначен для взаимодействия c приложениями на Python: он не имеет внешнего API, а внетреннее API постоянно меняется. Не используйте доступ к байт-коду для решения прикладных задач, а если используете — будьте готовы переписывать свои решения с каждым минорным релизом Python.

Например, начиная с Python 3.11 часть внутренних данных, необходимых для работы операторов, хранится прямо в байт-коде, между командами:

   1 >>> dis.dis(compile("for i in range(5):\n  print(i)", "<пример>", "exec"),show_caches=True)
   2   0           0 RESUME                   0
   3 
   4   1           2 PUSH_NULL
   5               4 LOAD_NAME                0 (range)
   6               6 LOAD_CONST               0 (5)
   7               8 CALL                     1
   8              10 CACHE                    0 (counter: 0)
   9              12 CACHE                    0 (func_version: 0)
  10              14 CACHE                    0
  11              16 GET_ITER
  12         >>   18 FOR_ITER                10 (to 42)
  13              20 CACHE                    0 (counter: 0)
  14              22 STORE_NAME               1 (i)
  15 
  16   2          24 PUSH_NULL
  17              26 LOAD_NAME                2 (print)
  18              28 LOAD_NAME                1 (i)
  19              30 CALL                     1
  20              32 CACHE                    0 (counter: 0)
  21              34 CACHE                    0 (func_version: 0)
  22              36 CACHE                    0
  23              38 POP_TOP
  24              40 JUMP_BACKWARD           12 (to 18)
  25 
  26   1     >>   42 END_FOR
  27              44 RETURN_CONST             1 (None)
  28 

TODO Python3.13+ — изменения в формате вывода dis()

(если успеем) Бонус: минимальный REPL

Как запустить интерпретатор и немного его подправить:

   1 import code
   2 import readline
   3 import subprocess
   4 
   5 class BangConsole(code.InteractiveConsole):
   6     def raw_input(self, *args, **kwargs):
   7         res = super().raw_input(*args, **kwargs)
   8         if res.startswith("!"):
   9             try:
  10                 res = subprocess.run(res[1:].strip().format(**self.locals).split())
  11             except Exception as E:
  12                 print(f"Error: {E}")
  13             return ""
  14         return res
  15 
  16 if __name__ == "__main__":
  17     BangConsole().interact()

Д/З

  1. Прочитать:

TODO

LecturesCMC/PythonIntro2024/30_DisInspect (последним исправлял пользователь FrBrGeorge 2024-11-21 18:02:26)