Инстроспекция и байткод
Интроспекция — возможность запросить тип и структуру объекта во время выполнения программы.
В Python — из коробки, в силу открытости внутренней структуры.
__dir__() / .__dict__, hasattr(), callable(), .__class__ (и классы как объекты), …; а также аннотации
Доступ к исходным текстам и стеку вызовов
Чтобы было ещё удобнее — inspect:
getmembers() / is*()
TODO пример
TODO пример
Работает, только если исходники есть, конечно
- В т. ч. работа с блоком параметров
1 >>> import inspect 2 >>> def fun(a, b=1, /, c=2, *, d=3): 3 ... return d, c, b, a 4 >>> inspect.signature(fun) 5 <Signature (a, b=1, /, c=2, *, d=3)> 6 >>> s = inspect.signature(fun) 7 >>> s.parameters 8 mappingproxy(OrderedDict([('a', <Parameter "a">), ('b', <Parameter "b=1">), ('c', <Parameter "c=2">), ('d', <Parameter "d=3">)])) 9 >>> for key, val in s.parameters.items(): 10 ... print(key, val, val.kind, val.kind.description) 11 a a POSITIONAL_ONLY positional-only 12 b b=1 POSITIONAL_ONLY positional-only 13 c c=2 POSITIONAL_OR_KEYWORD positional or keyword 14 d d=3 KEYWORD_ONLY keyword-only 15
Поддерживаются аннотации
Частичный разбор: getfullargspec()
- Разбор замыкания:
До кучи — getclasstree()
- В т. ч. работа с блоком параметров
- Вызовы — это стек
TODO разбор примера
- Вызовы — это стек
Интерпретация исходного текста
Python:
- Ситаксический анализатор
- Транслятор Python → байткод
- Интерпретатор байткода
Ситаксический анализатор
Написан на Си.
Поддерживается 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
Если очень грубо: типы данных, имена, Call(), Store()/Load()
- Проверка синтаксиса
- Структурная модель программы
В частности, FrBrGeorge/PythonCopypasteProof ☺
- Работа с деревом
1 >>> for obj in ast.walk(tree): 2 ... print(obj) 3 <ast.Module object at 0x7fdafe04bac0> 4 <ast.Assign object at 0x7fdafe04a3b0> 5 <ast.For object at 0x7fdafe04bd90> 6 <ast.Name object at 0x7fdafe049a80> 7 <ast.Constant object at 0x7fdafe049bd0> 8 <ast.Name object at 0x7fdafe04bdc0> 9 <ast.Call object at 0x7fdafe04acb0> 10 <ast.Expr object at 0x7fdafe049d80> 11 <ast.Store object at 0x7fdafef152d0> 12 <ast.Store object at 0x7fdafef152d0> 13 <ast.Name object at 0x7fdafe0498d0> 14 <ast.Constant object at 0x7fdafe0499f0> 15 <ast.Call object at 0x7fdafe049c30> 16 <ast.Load object at 0x7fdafef15270> 17 <ast.Name object at 0x7fdafe049ae0> 18 <ast.Name object at 0x7fdafe04ada0> 19 <ast.Name object at 0x7fdafe049720> 20 <ast.Load object at 0x7fdafef15270> 21 <ast.Load object at 0x7fdafef15270> 22 <ast.Load object at 0x7fdafef15270> 23 >>> print(ast.unparse(tree)) 24 k = 1 25 for i in range(10): 26 print(i, k) 27 >>> for obj in ast.walk(tree): 28 ... if isinstance(obj, ast.Name) and obj.id == "k": 29 ... obj.id = "QKRQ" 30 >>> print(ast.unparse(tree)) 31 QKRQ = 1 32 for i in range(10): 33 print(i, QKRQ) 34
Пример формирования AST для дальнейшей трансляции Python-ом: язык Hy
Транслятор
- Из AST тоже!
Исполнитель 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.
- Классическое фон-неймановское последовательное выполнение инструкций с GOTO
- Объекты — это их ID
- Имена — это ссылки на ID в пространствах имён (то есть опять-таки в объектах Python, потому что это словари)
- Функция — это тоже объект, а её вызов — тоже инструкция
- …
Модуль dis («дизассемблер»):
1 >>> dis.dis(compile("1 + 2", "<пример>", "eval")) 2 0 0 RESUME 0 3 4 1 2 LOAD_CONST 0 (3) 5 4 RETURN_VALUE 6 >>> # Ой, это компилятор сам вычислил… 7 >>> dis.dis(compile("a + 2", "<пример>", "eval")) 8 0 0 RESUME 0 9 10 1 2 LOAD_NAME 0 (a) 11 4 LOAD_CONST 0 (2) 12 6 BINARY_OP 0 (+) 13 10 RETURN_VALUE 14 >>> dis.dis(compile("for i in range(5):\n print(i)", "<пример>", "exec")) 15 0 0 RESUME 0 16 17 1 2 PUSH_NULL 18 4 LOAD_NAME 0 (range) 19 6 LOAD_CONST 0 (5) 20 8 PRECALL 1 21 12 CALL 1 22 22 GET_ITER 23 >> 24 FOR_ITER 13 (to 52) 24 26 STORE_NAME 1 (i) 25 26 2 28 PUSH_NULL 27 30 LOAD_NAME 2 (print) 28 32 LOAD_NAME 1 (i) 29 34 PRECALL 1 30 38 CALL 1 31 48 POP_TOP 32 50 JUMP_BACKWARD 14 (to 24) 33 34 1 >> 52 LOAD_CONST 1 (None) 35 54 RETURN_VALUE 36 * Иное: 37 {{{#!highlight pycon 38 >>> code = compile("for i in range(5):\n print(i)", "<пример>", "exec") 39 >>> print(dis.code_info(code)) 40 Name: <module> 41 Filename: <пример> 42 Argument count: 0 43 Positional-only arguments: 0 44 Kw-only arguments: 0 45 Number of locals: 0 46 Stack size: 4 47 Flags: 0x1000000 48 Constants: 49 0: 5 50 1: None 51 Names: 52 0: range 53 1: i 54 2: print 55 >>> >>> for instr in dis.get_instructions(code): 56 ... print(instr) 57 … 58
Python 3.11+: CACHE — место для хранения вспомогательных данных вместо стека
Д/З
- Прочитать:
(необязательно) dis
EJudge: DumpClass 'Что внутри?'
Написать декоратор класса dumper(cls), который будет добавлять (или подменять) строковое представление экземпляра класса так:
Строка содержит информацию о всех атрибутах класса, имя которых не начинается на «"_"»
Если поле числовое или строковое, в строку добавляется имя=значение (без кавычек)
Если атрибут — это метод (и только метод), добавляется имя(параметры), где параметры — это имена формальных параметров через запятую с пробелом (см. пример).
В остальных случаях добавляется имя: имя_типа
a=1; b=QQ; c: list; foo(d)
EJudge: IfIf 'Что если?'
Написать программу, которая получает на вход некоторый текст и определяет, является ли он синтаксически верным кодом на Python, в котором присутствует оператор if. Программа выводит True или False соответственно.
False
EJudge: CopyPaste 'А разница?'
Написать функцию copypaste(one, two), которая будет проверять, являются ли строки one и two синтаксически верными программами на Python, которые отличаются только используемыми именами. Сравнение распространяется и на имена атрибутов. Для простоты сравнение распространяется и на заведомо различные объекты (например, подмена bin() на hex() также считается простой подменой имён). Функция возвращает True, если условие выполнено, иначе — False.
True
EJudge: WhoCall 'Кто звал меня?'
Написать функцию whocall(depth=1), которая возвращает имя и названия параметров (строкой через пробел) одной из функций, вызов которой привёл к вызову whocall(). При depth=1 это функция, непосредственно вызвавшая whocall(), при depth=2 — функция, которая вызвала эту функцию и т. д. При depth=0 это сама whocall(). Если вызывающий объект не является функцией (например, это модуль) или depth превышает глубину стека вызовов, названия параметров — пустая строка; в последнем случае в качестве имени возвращается <UNIVERSE>.
В этом задании первый параметр метода — self — также надо указывать. Это упрощает решение, см. рассуждения в полной формулировке задачи
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))