Slide 1

Slide 1 text

Андреев Пётр МФТИ: лектор по курсу Advanced Python курирую tech.проекты Финтех Запуск tech.продуктов

Slide 2

Slide 2 text

Agenda 2 Дебри Python. Начало. автор tg: @PyotrAndreev ● Byte-code ● Что происходит, когда вы запускаете CPython-скрипт ● GIL

Slide 3

Slide 3 text

Byte-code 3 Что это? автор tg: @PyotrAndreev def main(): # строка 1 a = 10 # строка 2 b = 20 # строка 3 return a + b # строка 4 Какой-то код “If the implementation is hard to explain, it's a bad idea.” – Zen CPython

Slide 4

Slide 4 text

Byte-code 4 Что это? автор tg: @PyotrAndreev def main(): # строка 1 a = 10 # строка 2 b = 20 # строка 3 return a + b # строка 4 Множество CPU: - инструкции - архитектура OS: - реализации Какой-то код x86-64 ARM RISC-V Linux Windows www.python.org

Slide 5

Slide 5 text

Byte-code 5 Что это? автор tg: @PyotrAndreev def main(): # строка 1 a = 10 # строка 2 b = 20 # строка 3 return a + b # строка 4 Множество CPU: - инструкции - архитектура OS: - реализации Какой-то код x86-64 ARM RISC-V Linux Windows >>> import this The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. …

Slide 6

Slide 6 text

Byte-code 6 Что это? автор tg: @PyotrAndreev def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 Какой-то код x86-64 ARM RISC-V Linux Windows PVM “Simple is better than complex.” – Zen CPython Больше абстракций

Slide 7

Slide 7 text

Byte-code 7 Что это? автор tg: @PyotrAndreev def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 Какой-то код x86-64 ARM RISC-V Linux Windows PVM “Simple is better than complex.” – Zen CPython Больше абстракций Больше абстракций ***.py ***.pyc Byte-code ***.pyc – отображение исходного кода через ограниченный набор opt.codes (operation code) Хранится в __pycache__

Slide 8

Slide 8 text

Byte-code 8 Что это? автор tg: @PyotrAndreev def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 Какой-то код x86-64 ARM RISC-V Linux Windows PVM “Simple is better than complex.” – Zen CPython Больше абстракций Больше абстракций ***.pyc ***.py ***.pyc – отображение исходного кода через ограниченный набор opt.codes (operation code) Хранится в __pycache__ 3.14.0b2 [GCC 13.3.0] >>> import dis >>> len(dis.opnames) 267 Что за множество? Byte-code

Slide 9

Slide 9 text

Byte-code 9 Задача преобразования кода автор tg: @PyotrAndreev def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 ? Какой-то код ***.pyc ***.py Byte-code doc: opt.code GitHub: opt.code

Slide 10

Slide 10 text

Byte-code 10 Задача преобразования кода автор tg: @PyotrAndreev def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 ? Какой-то код ***.pyc ***.py Byte-code Вид ***.pyc 3.14.0b2 [GCC 13.3.0] >>> main.__code__ types.CodeType 3.14.0b2 [GCC 13.3.0] >>> main.__code__.co_code b'\x80\x00^\x05p\x00^\np\x01W\x01,\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00' Последовательность opt.codes с аргументами, за которыми стоит уже С-код doc: opt.code GitHub: opt.code Диспатчинг от PVM: _PyEval_EvalFrameDefault: ● читает byte-code ● по opt.code находит соответствующий C-код ● вызывает его ● управляет стеком, лок.переменными … ● возвращает результат: PyObject* или NULL

Slide 11

Slide 11 text

Byte-code 11 Задача преобразования кода автор tg: @PyotrAndreev def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 ? Какой-то код ***.pyc ***.py Byte-code doc: opt.code GitHub: opt.code В человеко-читабельном виде: >>> from dis import dis >>> dis(main)

Slide 12

Slide 12 text

Byte-code 12 Задача преобразования кода автор tg: @PyotrAndreev doc: opt.code GitHub: opt.code 1 0 RESUME 0 2 2 LOAD_CONST 1 (5) 4 STORE_FAST 0 (a) 3 6 LOAD_CONST 2 (10) 8 STORE_FAST 1 (b) 4 10 LOAD_FAST 0 (a) 12 LOAD_FAST 1 (b) 14 BINARY_OP 0 (+) 18 RETURN_VALUE Инициализация: (>= Python 3.11) При вызове ф-ии _PyInterpreterFrame /+InternalDocs/ создаёт/переиспользует фрейм, инструкция RESUME 0 помогает инициализировать его для opt.codes Загружает константу (5) из таблицы констант ф-и main.__code__.co_consts и помещает ссылку на неё в стек Из вершины стека извлекается значение (ссылка на объект 5) и сохраняется в локальной переменной a. Локальные переменные хранятся в: main.__code__.co_varnames Индекс 0 -> переменная a в списке локальных переменных ф-ии Загружает значение лок.переменной a (ссылка на 5) на стек Бинарная операция над 2 верхними элементами стека (ссылки на 5 и на 10) Результат: новый объект (число 15) созданный в куче Результатом является новый объект (число 15), созданный в куче, ссылка который помещается обратно в стек. Извлекает значение с вершины стека и возвращает его как результат работы функции. номера строк исходного кода # CPython 3.11.13 [GCC 11.4.0] def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4

Slide 13

Slide 13 text

Byte-code 13 Задача преобразования кода автор tg: @PyotrAndreev doc: opt.code GitHub: opt.code # CPython 3.11.13 [GCC 11.4.0] def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 14 BINARY_OP 0 (+) 18 RETURN_VALUE 1. число 15 не создаётся 2. Что такое 0 (+) ?

Slide 14

Slide 14 text

Byte-code 14 Задача преобразования кода автор tg: @PyotrAndreev doc: opt.code GitHub: opt.code # CPython 3.11.13 [GCC 11.4.0] def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 14 BINARY_OP 0 (+) 18 RETURN_VALUE 1. число 15 не создаётся 2. Что такое 0 (+) ? family(BINARY_OP, INLINE_CACHE_ENTRIES_BINARY_OP) = { BINARY_OP_MULTIPLY_INT, BINARY_OP_ADD_INT, BINARY_OP_SUBTRACT_INT, BINARY_OP_MULTIPLY_FLOAT, BINARY_OP_ADD_FLOAT, ... }; CPython 3.15.0а0 GitHub: версия, семейство BINARY_OP: неспециализированная операция С 3.11 Specializing Adaptive Interpreter: PEP 659 INLINE_CACHE_ENTRIES_BINARY_OP (=5): число нулевых opt.codes (CACHE), которые стоят перед BINARY_OP

Slide 15

Slide 15 text

Byte-code 15 Задача преобразования кода автор tg: @PyotrAndreev doc: opt.code GitHub: opt.code # CPython 3.11.13 [GCC 11.4.0] def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 14 BINARY_OP 0 (+) 18 RETURN_VALUE 1. число 15 не создаётся 2. Что такое 0 (+) ? family(BINARY_OP, INLINE_CACHE_ENTRIES_BINARY_OP) = { BINARY_OP_MULTIPLY_INT, BINARY_OP_ADD_INT, BINARY_OP_SUBTRACT_INT, BINARY_OP_MULTIPLY_FLOAT, BINARY_OP_ADD_FLOAT, ... }; CPython 3.15.0а0 GitHub: версия, семейство BINARY_OP: неспециализированная операция С 3.11 Specializing Adaptive Interpreter: PEP 659 INLINE_CACHE_ENTRIES_BINARY_OP (=5): число нулевых opt.codes (CACHE), которые стоят перед BINARY_OP 12 CACHE # slot0 = –1 (защитный счётчик) 14 CACHE # slot1 = &PyLong_Type 16 CACHE # slot2 = &PyLong_Type 18 CACHE # slot3 = &fast_int_add 20 CACHE # slot4 = резерв (для будущего) 22 BINARY_OP_ADD_INT 5 12 CACHE # slot0 = 8 (счётчик) 14 CACHE # slot1 = None 16 CACHE # slot2 = None 18 CACHE # slot3 = None 20 CACHE # slot4 – резерв 22 BINARY_OP 5 # PyNumber_Add – медленно При промаха счётчик уменьшается; при пробитии порога → откат Byte-code

Slide 16

Slide 16 text

Byte-code 16 Задача преобразования кода автор tg: @PyotrAndreev ? Какой-то код ***.pyc ***.py Byte-code # CPython 3.11.13 [GCC 11.4.0] def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4

Slide 17

Slide 17 text

Byte-code 17 Задача преобразования кода автор tg: @PyotrAndreev ? Какой-то код ***.pyc ***.py Byte-code # CPython 3.11.13 [GCC 11.4.0] def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 1. Lexer токенизирует код: tokenize _PyTokenizer_Get (часть лексера) 2. Parser строит построение AST Abstract Syntax Trees: ast _PyPegen_ASTFromString 3. построение символьной таблицы: PySymtable_BuildObject ○ обходит AST, помечает имена флагами local/global/cell/free ○ записи в co_varnames/co_cellvars/co_freevars

Slide 18

Slide 18 text

Byte-code 18 Задача преобразования кода автор tg: @PyotrAndreev ? Какой-то код ***.pyc ***.py Byte-code # CPython 3.11.13 [GCC 11.4.0] def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 1. Lexer токенизирует код: tokenize 2. Parser строит построение AST Abstract Syntax Trees: ast 3. построение символьной таблицы a = 5 b = 10 c = a + b print(c) Пример

Slide 19

Slide 19 text

Byte-code 19 Задача преобразования кода автор tg: @PyotrAndreev ? Какой-то код ***.pyc ***.py Byte-code # CPython 3.11.13 [GCC 11.4.0] def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 1. Lexer токенизирует код: tokenize 2. Parser строит построение AST Abstract Syntax Trees: ast 3. построение символьной таблицы a (идентификатор), = (оператор присваивания), 5 (целое число), b (идентификатор), = (оператор присваивания), 10 (целое число), c (идентификатор), = (оператор присваивания), a (идентификатор), + (оператор сложения), b (идентификатор), print (функция), ( (открывающая скобка), c (идентификатор), ) (закрывающая скобка) a = 5 b = 10 c = a + b print(c) Пример Токены

Slide 20

Slide 20 text

Byte-code 20 Задача преобразования кода автор tg: @PyotrAndreev ? Какой-то код ***.pyc ***.py Byte-code # CPython 3.11.13 [GCC 11.4.0] def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 1. Lexer токенизирует код: tokenize 2. Parser строит построение AST Abstract Syntax Trees: ast 3. построение символьной таблицы a (идентификатор), = (оператор присваивания), 5 (целое число), b (идентификатор), = (оператор присваивания), 10 (целое число), c (идентификатор), = (оператор присваивания), a (идентификатор), + (оператор сложения), b (идентификатор), print (функция), ( (открывающая скобка), c (идентификатор), ) (закрывающая скобка) Assign (присваивание) ├── Name (a) └── Constant (5) Assign (присваивание) ├── Name (b) └── Constant (10) Assign (присваивание) ├── Name (c) └── BinOp (бинарная операция) ├── Name (a) └── Name (b) Call (вызов функции) ├── Name (print) └── Name (c) a = 5 b = 10 c = a + b print(c) Пример Токены Иерархическое дерево: ● последовательность выполнения ● отношения между объектами

Slide 21

Slide 21 text

Byte-code 21 Задача преобразования кода автор tg: @PyotrAndreev ? Какой-то код ***.pyc ***.py Byte-code # CPython 3.11.13 [GCC 11.4.0] def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4

Slide 22

Slide 22 text

Byte-code 22 Задача преобразования кода автор tg: @PyotrAndreev ✔ Какой-то код ***.pyc ***.py Byte-code # CPython 3.11.13 [GCC 11.4.0] def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4

Slide 23

Slide 23 text

Byte-code 23 Задача преобразования кода автор tg: @PyotrAndreev def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 Какой-то код x86-64 ARM RISC-V Linux Windows PVM “Simple is better than complex.” – Zen CPython Больше абстракций Больше абстракций ***.py ***.pyc Byte-code

Slide 24

Slide 24 text

Запуск программы 24 Немного исходников автор tg: @PyotrAndreev def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 Какой-то код x86-64 ARM RISC-V Linux Windows PVM “Simple is better than complex.” – Zen CPython Больше абстракций Больше абстракций ***.py ***.pyc Byte-code Этапы запуска CPython

Slide 25

Slide 25 text

Запуск программы 25 Немного исходников автор tg: @PyotrAndreev def main(): # строка 1 a = 5 # строка 2 b = 10 # строка 3 return a + b # строка 4 Какой-то код x86-64 ARM RISC-V Linux Windows PVM “Simple is better than complex.” – Zen CPython Больше абстракций Больше абстракций ***.py ***.pyc Byte-code Этапы запуска CPython user@comp:~$ python3 foo.py ... ● Unix находит исполняемый файл (ИФ): /usr/bin/python3, который содержит ф-ю main из Programs/python.c ... ● ИФ вызывает main, что вызывает цепочку вызовов из Modules/main.c: Py_BytesMain → pymain_main → Py_RunMain → запуск главного цикла выполнения Python‑кода

Slide 26

Slide 26 text

Запуск программы 26 Немного исходников автор tg: @PyotrAndreev main (Programs/python.c) │ └> Py_BytesMain(argc, argv) (Modules/main.c) │ └> pymain_main(&args) │ ├> status = pymain_init(args) # Инициализация интерпретатора │ └── (настройка PyConfig, путей, сигналов и т.д.) │ ├> Проверка статуса (если требуется завершение или ошибка) │ └> Py_RunMain() │ ├> pymain_run_python(&exitcode) │ ├> Обновление конфигурации и настройка путей │ ├> Выбор режима выполнения: │ │ • Если задана команда → pymain_run_command() │ │ • Если задан модуль → pymain_run_module() │ │ • Если задан файл → pymain_run_file() │ │ • Иначе чтение из stdin → pymain_run_stdin() │ └> pymain_repl(config, exitcode) # Запуск REPL (интерактивного режима) │ ├> Py_FinalizeEx() # Завершение интерпретатора │ └> pymain_free() # Освобождение глобальных ресурсов Этапы запуска CPython

Slide 27

Slide 27 text

Byte-code 27 Задача преобразования кода автор tg: @PyotrAndreev run.py byte-code исполнение PVM Компьютер процессы -> потоки Запуск отдельного процесса под PVM исполнение файла Lexer -> токенизация Parser -> AST ... a = 5 b = 10 c = a + b print(c) a (идентификатор), = (оператор присваивания), 5 (целое число), b (идентификатор), = (оператор присваивания), 10 (целое число), c (идентификатор), = (оператор присваивания), a (идентификатор), + (оператор сложения), b (идентификатор), print (функция), ( (открывающая скобка), c (идентификатор),) (закрывающая скобка) Assign (присваивание) ├── Name (a) └── Constant (5) Assign (присваивание) ├── Name (b) └── Constant (10) Assign (присваивание) ├── Name (c) └── BinOp (бинарная операция) ├── Name (a) └── Name (b) Call (вызов функции) ├── Name (print) └── Name (c) b'\x97\x00d\x01}\x00d\x02}\x01|\x00|\x01z\x00\x00 \x00}\x02t\x01\x00\x00\x00\x00\x00\x00\x00\x00|\x 02\xab\x01\x00\x00\x00\x00\x00\x00\x01\x00y\x00' 0 RESUME 2 LOAD_CONST 4 STORE_FAST 6 LOAD_CONST 8 STORE_FAST 10 LOAD_FAST 12 LOAD_FAST 14 BINARY_OP 18 STORE_FAST 20 LOAD_GLOBAL 30 LOAD_FAST 32 CALL 40 POP_TOP 42 RETURN_CONST AST byte-code (.pyc) byte-code foo.py ‘дёргает’ Python C API

Slide 28

Slide 28 text

Processes: ● отдельное адресное пространство (виртуальная память) ● изменения в памяти одного процесса не влияют на другие ● обход GIL: модуль multiprocessing 28 Исполнение кода автор tg: @PyotrAndreev Threads: threading – интерфейс для работы ● внутри процесса делят общее адресное пространство, но не стек ● создание и переключение быстрее, чем между процессами ● GIL threads & processes

Slide 29

Slide 29 text

29 автор tg: @PyotrAndreev threads & processes Что хотим? ● Получить максимальную эффективность от CPU-bound задачах передаваемые данные необходимо сериализовать: дорого Processes: ● отдельное адресное пространство (виртуальная память) ● изменения в памяти одного процесса не влияют на другие ● обход GIL: модуль multiprocessing Коммуникация между ● Queues, Pipes, Managers(общие объекты) ● Shared Memory – необходимы инструменты синхронизации, чтобы избежать race condition Threads: threading – интерфейс для работы ● внутри процесса делят общее адресное пространство, но не стек ● создание и переключение быстрее, чем между процессами ● GIL Коммуникация между ● Lock, RLock: блокировки для защиты критических секций ● Condition, Semaphore, Event, Barrier ... pickle Исполнение кода

Slide 30

Slide 30 text

30 автор tg: @PyotrAndreev GIL (CPython) “Для … вычислительных задач … недостаток параллелизма … является большей проблемой, чем скорость выполнения кода Python…” “The GIL … a global bottleneck” типы параллелизма: arxiv timemodule.c: repo

Slide 31

Slide 31 text

автор tg: @PyotrAndreev GIL (CPython) “Для … вычислительных задач … недостаток параллелизма … является большей проблемой, чем скорость выполнения кода Python…” “The GIL … a global bottleneck” 31 типы параллелизма: arxiv ● гарантирует, что одновременно выполняется PVM только один поток байт-кода (-> Python C API) -> что обеспечивает потокобезопасность ● Защищает race conditions на Python уровне, но не глубже: ○ C-расширения могут предоставлять параллелизм в процессе timemodule.c: repo

Slide 32

Slide 32 text

автор tg: @PyotrAndreev GIL (CPython) Как передаётся GIL? макросами ● Py_BEGIN_ALLOW_THREADS освобождает GIL Py_END_ALLOW_THREADS захватывает GIL ● GIL не будет захвачен другими потоками, если им это не требуется “Для … вычислительных задач … недостаток параллелизма … является большей проблемой, чем скорость выполнения кода Python…” “The GIL … a global bottleneck” 32 типы параллелизма: arxiv ● гарантирует, что одновременно выполняется PVM только один поток байт-кода (-> Python C API) -> что обеспечивает потокобезопасность ● Защищает race conditions на Python уровне, но не глубже: ○ C-расширения могут предоставлять параллелизм в процессе time.sleep(10) timemodule.c: repo

Slide 33

Slide 33 text

автор tg: @PyotrAndreev Пример: ● исполняется внутренняя С-функция: time_sleep ● ... ● вычисляется время до, которого спим ● используем Py_BEGIN_ALLOW_THREADS -> сохраняет состояние треда: PyEval_SaveThread -> освобождает GIL ● вызывается OS блокирующая операция для усыпления потока на заданное время ● OS пробуждает поток по истечению времени ● другие потоки могут выполнять Python байт-код ● используем Py_END_ALLOW_THREADS -> восстанавливаем состояние потока: PyEval_RestoreThread -> захват GIL потоком GIL (CPython) Как передаётся GIL? макросами ● Py_BEGIN_ALLOW_THREADS освобождает GIL Py_END_ALLOW_THREADS захватывает GIL ● GIL не будет захвачен другими потоками, если им это не требуется “Для … вычислительных задач … недостаток параллелизма … является большей проблемой, чем скорость выполнения кода Python…” “The GIL … a global bottleneck” 33 типы параллелизма: arxiv ● гарантирует, что одновременно выполняется PVM только один поток байт-кода (-> Python C API) -> что обеспечивает потокобезопасность ● Защищает race conditions на Python уровне, но не глубже: ○ C-расширения могут предоставлять параллелизм в процессе time.sleep(10) timemodule.c: repo

Slide 34

Slide 34 text

автор tg: @PyotrAndreev Пример: ● исполняется внутренняя С-функция: time_sleep ● ... ● вычисляется время до, которого спим ● используем Py_BEGIN_ALLOW_THREADS -> сохраняет состояние треда: PyEval_SaveThread -> освобождает GIL ● вызывается OS блокирующая операция для усыпления потока на заданное время ● OS пробуждает поток по истечению времени ● другие потоки могут выполнять Python байт-код ● используем Py_END_ALLOW_THREADS -> восстанавливаем состояние потока: PyEval_RestoreThread -> захват GIL потоком GIL (CPython) Как передаётся GIL? макросами ● Py_BEGIN_ALLOW_THREADS освобождает GIL Py_END_ALLOW_THREADS захватывает GIL ● GIL не будет захвачен другими потоками, если им это не требуется “Для … вычислительных задач … недостаток параллелизма … является большей проблемой, чем скорость выполнения кода Python…” “The GIL … a global bottleneck” 34 типы параллелизма: arxiv ● гарантирует, что одновременно выполняется PVM только один поток байт-кода (-> Python C API) -> что обеспечивает потокобезопасность ● Защищает race conditions на Python уровне, но не глубже: ○ C-расширения могут предоставлять параллелизм в процессе time.sleep(10) timemodule.c: repo { Py_BEGIN_ALLOW_THREADS /* your code here */ Py_END_ALLOW_THREADS } Разворачивается в: { PyThreadState *_save; _save = PyEval_SaveThread(); /* your code here */ PyEval_RestoreThread(_save); } что есть:

Slide 35

Slide 35 text

автор tg: @PyotrAndreev Пример: ● исполняется внутренняя С-функция: time_sleep ● ... ● вычисляется время до, которого спим ● используем Py_BEGIN_ALLOW_THREADS -> сохраняет состояние треда: PyEval_SaveThread -> освобождает GIL ● вызывается OS блокирующая операция для усыпления потока на заданное время ● OS пробуждает поток по истечению времени ● другие потоки могут выполнять Python байт-код ● используем Py_END_ALLOW_THREADS -> восстанавливаем состояние потока: PyEval_RestoreThread -> захват GIL потоком GIL (CPython) Как передаётся GIL? макросами ● Py_BEGIN_ALLOW_THREADS освобождает GIL Py_END_ALLOW_THREADS захватывает GIL ● GIL не будет захвачен другими потоками, если им это не требуется “Для … вычислительных задач … недостаток параллелизма … является большей проблемой, чем скорость выполнения кода Python…” “The GIL … a global bottleneck” 35 типы параллелизма: arxiv Преимущества GIL: ● Более быстрая разработка ● Простой подсчёт ссылок через: Py_IncRef, Py_DecRef ● ... ● гарантирует, что одновременно выполняется PVM только один поток байт-кода (-> Python C API) -> что обеспечивает потокобезопасность ● Защищает race conditions на Python уровне, но не глубже: ○ C-расширения могут предоставлять параллелизм в процессе time.sleep(10) timemodule.c: repo { Py_BEGIN_ALLOW_THREADS /* your code here */ Py_END_ALLOW_THREADS } Разворачивается в: { PyThreadState *_save; _save = PyEval_SaveThread(); /* your code here */ PyEval_RestoreThread(_save); } что есть:

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

PY.UP tg: py_up автор tg: @PyotrAndreev