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

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

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

Чтобы было ещё удобнее — 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=[])
  37 

Транслятор

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

   1 >>> compile("1 + 2", "<пример>", "eval").co_code
   2 b'\x97\x00d\x00S\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\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00D\x00]\x0eZ\x02\x02\x00e\x03e\x02e\x00\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x8c\x0fd\x02S\x00'
   7 

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

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

Д/З

  1. Прочитать:
  2. EJudge: DumpClass 'Что внутри?'

    Написать декоратор класса dumper(cls), который будет добавлять (или подменять) строковое представление экземпляра класса так:

    • Строка содержит информацию о всех атрибутах класса, имя которых не начинается на «"_"»

    • Если поле числовое или строковое, в строку добавляется имя=значение (без кавычек)

    • Если атрибут — это метод (и только метод), добавляется имя(параметры), где параметры — это имена формальных параметров через запятую с пробелом (см. пример).

    • В остальных случаях добавляется имя: имя_типа

    Input:

       1 @dumper
       2 class C:
       3     a = 1
       4     def __init__(self):
       5         self.b = "QQ"
       6         self.c = [1,2,3]
       7     def foo(self, d):
       8         pass
       9 print(C())
    
    Output:

    a=1; b=QQ; c: list; foo(d)
  3. EJudge: IfIf 'Что если?'

    Написать программу, которая получает на вход некоторый текст и определяет, является ли он синтаксически верным кодом на Python, в котором присутствует оператор if. Программа выводит True или False соответственно.

    Input:

       1 # if a: print("qkrq")
       2 print(qq)
       3 while a:
       4      print("PP")
    
    Output:

    False
  4. EJudge: CopyPaste 'А разница?'

    Написать функцию copypaste(one, two), которая будет проверять, являются ли строки one и two синтаксически верными программами на Python, которые отличаются только используемыми именами. Сравнение распространяется и на имена атрибутов. Для простоты сравнение распространяется и на заведомо различные объекты (например, подмена bin() на hex() также считается простой подменой имён). Функция возвращает True, если условие выполнено, иначе — False.

    Input:

       1 A = "a=3\nfor i in range(a): print(i*a)"
       2 B = """# The program
       3 var = 3  # The variable
       4 
       5 # The comment
       6 for idx in range(var):
       7     print(var * idx)"""
       8 print(copypaste(A, B)) 
    
    Output:

    True
  5. EJudge: WhoCall 'Кто звал меня?'

    Написать функцию whocall(depth=1), которая возвращает имя и названия параметров (строкой через пробел) одной из функций, вызов которой привёл к вызову whocall(). При depth=1 это функция, непосредственно вызвавшая whocall(), при depth=2 — функция, которая вызвала эту функцию и т. д. При depth=0 это сама whocall(). Если вызывающий объект не является функцией (например, это модуль) или depth превышает глубину стека вызовов, названия параметров — пустая строка; в последнем случае в качестве имени возвращается <UNIVERSE>.

    • В этом задании первый параметр метода — self — также надо указывать. Это упрощает решение, см. рассуждения в полной формулировке задачи

    Input:

       1 class C:
       2     def fun(self, arg, *args, kw="KW", **kwargs):
       3         return whocall(arg)
       4     lfun = lambda self, arg: whocall(arg)
       5 
       6 def fun():
       7     return whocall(1) 
       8 
       9 print(C().fun(0), C().lfun(0), fun())
      10 print(C().fun(1), C().lfun(1))
      11 print(C().fun(2, 3, 4, kw=123), C().lfun(2))
      12 print(C().fun(3), C().lfun(42))
    
    Output:

       1 ('whocall', 'depth') ('whocall', 'depth') ('fun', '')
       2 ('fun', 'self arg args kw kwargs') ('<lambda>', 'self arg')
       3 ('<module>', '') ('<module>', '')
       4 ('<module>', '') ('<UNIVERSE>', '')
    

LecturesCMC/PythonIntro2023/30_DisInspect (последним исправлял пользователь FrBrGeorge 2023-11-22 16:24:45)