You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
14 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"cells": [
{
"cell_type": "markdown",
"id": "08735fdb",
"metadata": {},
"source": [
"# Байткод #"
]
},
{
"cell_type": "markdown",
"id": "764f3e24",
"metadata": {},
"source": [
"А сейчас давайте поговорим про байт-код.\n",
"\n",
"Помните, когда мы рассматривали организацию кода на Python, мы импортировали функции из модулей и пакетов, и Python автоматически создавал файлики с расширением.pyc. Я сказал, что это файлики, в которых содержится байт-код, то есть скомпилированное представление вашего кода в форме, удобной Python для интерпретирования.\n",
"\n",
"Давайте сейчас подробнее посмотрим, что это значит. Для этого мы создадим модуль, назовем его `mymodule`.\n",
"\n",
"Мы с вами это уже умеем делать, и внутри определим функцию `multiply`, точно такую же, которую мы с вами уже писали. Она принимает два аргумента и возвращает их произведение.\n",
"\n",
"```python\n",
"def multiply(a, b):\n",
" return a * b\n",
"```\n",
"\n",
"Функция определена, теперь мы можем ее импортировать. Давайте воспользуемся конструкцией `from`. `from mymodule import multiply`. `multiply` — это функция."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "03f059a0",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"importing mypackage\n"
]
}
],
"source": [
"from mymodule import multiply"
]
},
{
"cell_type": "markdown",
"id": "0c3e8648",
"metadata": {},
"source": [
"Как я уже сказал, практически всё в Python — это объект, не исключение и функция. Если мы посмотрим с помощью функции `dir` методы, которые содержит функция, мы увидим, что их много, в том числе у функции есть атрибут `__code__` с двумя подчеркиваниями, это `code object`."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "1374e3bc",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']\n"
]
}
],
"source": [
"print(dir(multiply))"
]
},
{
"cell_type": "markdown",
"id": "43989f9d",
"metadata": {},
"source": [
"По сути, это объект, который оборачивает тот код, который вы пишете. У этого объекта есть метод `co_code`, который содержит байт-код. Это байт-код, который Python сгенерировал для этой функции."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "a3373ab6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"b'|\\x00|\\x01\\x14\\x00S\\x00'\n"
]
}
],
"source": [
"print(multiply.__code__.co_code)"
]
},
{
"cell_type": "markdown",
"id": "eb706b6d",
"metadata": {},
"source": [
"Что это значит? Это значит, что наша функция была преобразована в байт-код, который представляет собой набор инструкций. В данном случае это байтовая строка, которая содержит инструкцию одну за другой, которые подаются на вход интерпретатору Python.\n",
"\n",
"Интерпретатор Python, по большому счету, это большая конструкция `switch` кода на C, которая, в зависимости от входящей инструкции байт-кода, предпринимает то или иное действие."
]
},
{
"cell_type": "markdown",
"id": "33872151",
"metadata": {},
"source": [
"Как мы видим, байт-код нечеловекочитаемый Мы можем привести его в человекочитаемый вид, используя модуль `dis` стандартной библиотеки Python.\n",
"\n",
"Давайте это сделаем. Заимпортируем модуль `dis` и с помощью метода `dis` модуля `dis` дисассемблируем нашу функцию в человекочитаемый вид. Что мы видим?"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "e2ccab2f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 0 LOAD_FAST 0 (0)\n",
" 2 LOAD_FAST 1 (1)\n",
" 4 BINARY_MULTIPLY\n",
" 6 RETURN_VALUE\n"
]
}
],
"source": [
"from dis import dis\n",
"\n",
"dis(multiply.__code__.co_code)"
]
},
{
"cell_type": "markdown",
"id": "42c24b47",
"metadata": {},
"source": [
"Мы видим, что наша функция на самом деле преобразовывается в байт-код, который состоит из четырех инструкций. Каждая из этих инструкций делает какое-то действие. Давайте посмотрим на каждую инструкцию подробно.\n",
"\n",
"Первая — это `LOAD_FAST`, это инструкция, которая загружает содержимое переменной `a` в память и помещает его на стек. Стек — это структура данных, последним пришёл - первым вышел (LIFO), которая определена на уровне C и используется Python для того, чтобы манипулировать ходом выполнения программы.\n",
"\n",
"Как только переменная `a` помещена на стек, мы переходим ко второй инструкции, которая также `LOAD_FAST`, она делает то же самое со значением переменной `b`, помещает значение переменной `b` на стек. Далее.\n",
"\n",
"Следующая инструкция — это `BINARY_MULTIPLY`. `BINARY_MULTIPLY` говорит интерпретатору Python, что нужно взять два самых верхних значения на стеке и произвести их умножение. Заметьте, что мы могли передать в функцию значение любого типа. Могли передать целые числа, могли вещественные. Могли бы передать даже строки, например. Но в результате преобразования нашей функции в байт-код байт-код получился тем же самым. Типизация начинает по большому счету работать тогда, когда Python выполняет ту или иную инструкцию.\n",
"\n",
"В данном случае, когда выполняется инструкция `BINARY_MULTIPLY`, Python посмотрит на тип объектов, и, например, если это два числа, произведет умножение двух чисел. Результат умножения он поместит на вершину стека.\n",
"\n",
"И последняя инструкция — это `RETURN_VALUE`. `RETURN_VALUE` берет вершину стека, это результат умножения наших значений `a` и `b`, и возвращает его. По большому счету, это то, как работает Python, как устроен байт-код, и любой код на Python, который вы будете писать, будет превращаться, компилироваться в байт-код. Этот байт-код будет выглядеть посложнее, чем мы с вами видели, потому что функции, которые вы будете писать, могут быть сложнее, содержать всевозможные циклы, условия и так далее. И будут использоваться соответствующие инструкции.\n",
"\n",
"Все эти инструкции задокументированы, их можно посмотреть как в исходном коде Python, так и в документации. Ну, и можно посмотреть на них сейчас, есть модуль в стандартной библиотеке `opcode`, который мы можем заимпортировать и напечатать, например, на экран `opmap`."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "b4d07e58",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'POP_TOP': 1, 'ROT_TWO': 2, 'ROT_THREE': 3, 'DUP_TOP': 4, 'DUP_TOP_TWO': 5, 'ROT_FOUR': 6, 'NOP': 9, 'UNARY_POSITIVE': 10, 'UNARY_NEGATIVE': 11, 'UNARY_NOT': 12, 'UNARY_INVERT': 15, 'BINARY_MATRIX_MULTIPLY': 16, 'INPLACE_MATRIX_MULTIPLY': 17, 'BINARY_POWER': 19, 'BINARY_MULTIPLY': 20, 'BINARY_MODULO': 22, 'BINARY_ADD': 23, 'BINARY_SUBTRACT': 24, 'BINARY_SUBSCR': 25, 'BINARY_FLOOR_DIVIDE': 26, 'BINARY_TRUE_DIVIDE': 27, 'INPLACE_FLOOR_DIVIDE': 28, 'INPLACE_TRUE_DIVIDE': 29, 'GET_AITER': 50, 'GET_ANEXT': 51, 'BEFORE_ASYNC_WITH': 52, 'BEGIN_FINALLY': 53, 'END_ASYNC_FOR': 54, 'INPLACE_ADD': 55, 'INPLACE_SUBTRACT': 56, 'INPLACE_MULTIPLY': 57, 'INPLACE_MODULO': 59, 'STORE_SUBSCR': 60, 'DELETE_SUBSCR': 61, 'BINARY_LSHIFT': 62, 'BINARY_RSHIFT': 63, 'BINARY_AND': 64, 'BINARY_XOR': 65, 'BINARY_OR': 66, 'INPLACE_POWER': 67, 'GET_ITER': 68, 'GET_YIELD_FROM_ITER': 69, 'PRINT_EXPR': 70, 'LOAD_BUILD_CLASS': 71, 'YIELD_FROM': 72, 'GET_AWAITABLE': 73, 'INPLACE_LSHIFT': 75, 'INPLACE_RSHIFT': 76, 'INPLACE_AND': 77, 'INPLACE_XOR': 78, 'INPLACE_OR': 79, 'WITH_CLEANUP_START': 81, 'WITH_CLEANUP_FINISH': 82, 'RETURN_VALUE': 83, 'IMPORT_STAR': 84, 'SETUP_ANNOTATIONS': 85, 'YIELD_VALUE': 86, 'POP_BLOCK': 87, 'END_FINALLY': 88, 'POP_EXCEPT': 89, 'STORE_NAME': 90, 'DELETE_NAME': 91, 'UNPACK_SEQUENCE': 92, 'FOR_ITER': 93, 'UNPACK_EX': 94, 'STORE_ATTR': 95, 'DELETE_ATTR': 96, 'STORE_GLOBAL': 97, 'DELETE_GLOBAL': 98, 'LOAD_CONST': 100, 'LOAD_NAME': 101, 'BUILD_TUPLE': 102, 'BUILD_LIST': 103, 'BUILD_SET': 104, 'BUILD_MAP': 105, 'LOAD_ATTR': 106, 'COMPARE_OP': 107, 'IMPORT_NAME': 108, 'IMPORT_FROM': 109, 'JUMP_FORWARD': 110, 'JUMP_IF_FALSE_OR_POP': 111, 'JUMP_IF_TRUE_OR_POP': 112, 'JUMP_ABSOLUTE': 113, 'POP_JUMP_IF_FALSE': 114, 'POP_JUMP_IF_TRUE': 115, 'LOAD_GLOBAL': 116, 'SETUP_FINALLY': 122, 'LOAD_FAST': 124, 'STORE_FAST': 125, 'DELETE_FAST': 126, 'RAISE_VARARGS': 130, 'CALL_FUNCTION': 131, 'MAKE_FUNCTION': 132, 'BUILD_SLICE': 133, 'LOAD_CLOSURE': 135, 'LOAD_DEREF': 136, 'STORE_DEREF': 137, 'DELETE_DEREF': 138, 'CALL_FUNCTION_KW': 141, 'CALL_FUNCTION_EX': 142, 'SETUP_WITH': 143, 'LIST_APPEND': 145, 'SET_ADD': 146, 'MAP_ADD': 147, 'LOAD_CLASSDEREF': 148, 'EXTENDED_ARG': 144, 'BUILD_LIST_UNPACK': 149, 'BUILD_MAP_UNPACK': 150, 'BUILD_MAP_UNPACK_WITH_CALL': 151, 'BUILD_TUPLE_UNPACK': 152, 'BUILD_SET_UNPACK': 153, 'SETUP_ASYNC_WITH': 154, 'FORMAT_VALUE': 155, 'BUILD_CONST_KEY_MAP': 156, 'BUILD_STRING': 157, 'BUILD_TUPLE_UNPACK_WITH_CALL': 158, 'LOAD_METHOD': 160, 'CALL_METHOD': 161, 'CALL_FINALLY': 162, 'POP_FINALLY': 163}\n"
]
}
],
"source": [
"from opcode import opmap\n",
"\n",
"print(opmap)"
]
},
{
"cell_type": "markdown",
"id": "a86677b4",
"metadata": {},
"source": [
"Мы видим, что на самом деле инструкций существует очень много. И, так или иначе, они в определенный момент будут задействованы. \n",
"\n",
"Что хотелось бы отметить? Знание о том, что ваш код компилируется в байт-код, может оказаться в определенный момент очень полезным. И, как минимум, вы не будете бояться того, что Python автоматически создает скомпилированные байт-код файлики в ваших директориях.\n",
"\n",
"Мы посмотрели на то, как Python преобразовывает код в байт-код, и теперь вы знаете, как это устроено."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}