Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Питон изнутри. Вступление

Anna | 16.06.2014 | нет комментариев
Помимо постижения стандартной библиотеки, неизменно увлекательно, а изредка и благотворно, знать, как язык устроен изнутри. Андрей Светлов, один из разработчиков Python, советует каждому интересующимся серию статей об устройстве CPython. Представляю вам перевод первого эпизода. Если отлично пойдёт, будут переводы остальных постов.

Мой друг некогда сказал мне: «Знаешь, для некоторых людей язык C — это легко комплект макросов, тот, что разворачивается в ассемблерные инструкции». Это было давным-давно (для всезнаек: да, ещё до возникновения LLVM), но эти слова отлично мне запомнились. Может быть, когда Керниган и Ритчи глядят на C-программу, они на самом деле видят ассемблерный код? А Тим Бёрнерс-Ли? Может он сёрфит интернет по-иному, не так, как мы? И что, в конце концов, Киану Ривз видел в том ужасном зелёном месиве? Нет, правда, что, чёрт побери, он там видел?! Эм… вернёмся к программам. Что видит Гвидо ван Россум, когда читает программы на Python?

Данный пост — 1-й в серии статей о внутренностях Питона. Я верю, что трактование темы иным людям — это наилучший метод разобраться в ней. И мне дюже хотелось обучиться видеть и понимать „жуткое зелёное месиво“, которое стоит за Python-кодом. В основном я буду писать о CPython 3-й версии, об исполнении байт-кода (я не фанат этапа компиляции), но, возможно, не обойду вниманием и многое другое, что связано с исполнением всякого Python-кода (включая Unladen Swallow, Jython, Cython и т.п.). Для краткости я пишуPython, подразумевая CPython, если только не говорится другое. Также я подразумеваю POSIX-совместимую ОС либо, если это значимо, Linux, если не утверждается иное. Если вам увлекательно, как работает Питон, то советую дочитать данный пост до конца. Вам тем больше следует это сделать, если вы хотите контрибьютить в CPython. А можете это сделать для того, Дабы обнаружить ошибки, которые я допустил, посмеяться нужно мною и оставить язвительные комментарии, если это ваш исключительный метод проявлять свои чувства и эмоции.

Фактически всё, о чём я буду писать, дозволено обнаружить в начальных кодах Питона либо в некоторых других отличных источниках (документация, исключительно эта и эта страницы, отдельные лекции с PyCon,поиск по python-dev и т.д.). Обнаружить дозволено всё, но я верю, что мои усилия по объединению всех материалов в один, на тот, что дозволено подписаться через RSS, облегчат ваши приключения. Я полагаю, что читатель немножко знаком с языком C; с теорией операционных систем; чуть поменьше, чем никак, с ассемблером всякий архитектуры; чуть огромнее, чем никак, с Питоном и удобно Ощущает себя в UNIX (скажем, легко устанавливает что-либо из исходников). Не переживайте, если вам не хватает навыка во всём этом, но и лёгкого плавания я не обещаю. Если у вас нет настроенного окружения для разработки Питона, предлагаю пройти сюда и исполнить нужные шаги.

Давайте начнём с того, что вам, вероятно, теснее вестимо. Для понимания протекающего мне кажется комфортной метафора механизмов. В случае с Питоном это нетрудно, потому что Питон полагается на виртуальную машину, Дабы делать то, что он делает (как и множество интерпретируемых языков). Тут значимо верно понимать термин «виртуальная машина»: думать следует скорее в сторону JVM, нежели VirtualBox (технически, они, по сути, идентичны, но в настоящем мире их, как правило, разделяют). Понимать данный термин проще, как мне кажется, дословно — это механизм, составленный из программ. Ваш процессор — это каждого лишь трудная электронная машина, которая принимает на вход машинный код и данные, имеет состояние (регистры), и основываясь на вводных данных и нынешнем состоянии она выводит в память либо на шину новую информацию. ?сно, да? А CPython — это механизм, собранный из программных компонентов, тот, что имеет состояние и обрабатывает инструкции (различные реализации могут применять различные инструкции). Механизм данный работает в процессе, где находится интерпретатор Питона. Мне нравится эта метафора с «механизмами», и я теснее описал её в мельчайших подробностях.

Учтя вышесказанное, давайте оценим с высоты птичьего полёта, что происходит, когда мы запускаем такую команду:

$ python -c 'print("Hello, world!")'. 

Запускается бинарник Питона, инициализируется стандартная библиотека C (это происходит при запуске фактически всякого процесса), вызывается main-функция (глядите исходники ./Modules/python.cmain, из которой вызывается ./Modules/main.cPy_Main). Позже некоторых подготовительных шагов (разбор доводов, учёт переменных окружения, оценка обстановки со стандартными потоками и т.д.), вызывается./Python/pythonrun.cPy_Initialize. По огромному счёту, в этой функции «создаются» и собираются части, нужные для запуска CPython-машины, и легко «процесс» превращается в «процесс с интерпретатором Питона внутри». Помимо этого, создаются две дюже значимые конструкции: состояния интерпретатора исостояния потока. Также создаётся встроенный модуль sys и модуль, в котором содержатся все встроенные функции и переменные. В следующих эпизодах эти шаги будут описаны во всех подробностях.

Имея всё это, Питон ползёт одним из нескольких путей в зависимости от того, что ему скормили: выполнится строка, если передана опция -c), выполнится модуль (опция -m), выполнится файл (очевидно переданный в командной строке либо переданный ядром, если Питон применяется как интерпретатор скрипта) либо запустится REPL (это специальный случай исполнения файла, являющегося интерактивным устройством). В нашем случае, будет исполнена строка, т.к. мы передали опцию -c. Дабы исполнить эту строку, вызывается./Python/pythonrun.cPyRun_SimpleStringFlags. Эта функция создаёт неймспейс __main__, в котором будет исполнена наша строчка кода (где будет храниться a, если исполнить $ python -c 'a=1; print(a)'? Верно, в этом неймспейсе). Позже создания неймспейса, строка выполняется в нём (вернее, интерпретируется). Дабы это случилось, для начала необходимо преобразовать строку во что-нибудь внятное для машины.

Как и говорил, я не буду акцентировать внимание на парсере и компиляторе Питона. Я не знаток этих областей, меня это не крепко волнует, и, насколько я знаю, в компиляторе Питона нет какой-то специальной магии, выходящей за пределы университетского курса по компиляторам. Лишь немножко пройдёмся по верхам этих тем и, может быть, вернёмся чуть позднее, Дабы разглядеть некоторые особенности поведения CPython (скажем, оператор global, тот, что влияет на парсер). В всеобщем, стадии парсинга/компиляции вPyRun_SimpleStringFlags проходят дальнейшим образом: лексический обзор и создание дерева разбора, его реформирование в абстрактное синтаксическое дерево (AST), компиляция AST в объект кода с поддержкой./Python/ast.cPyAST_FromNode. Теперь можете думать об объекте кода, как о бинарной строке, с которыми могут трудиться механизмы виртуальной машины Питона — сейчас мы готовы к интерпретации.

У нас есть фактически пустой __main__, у нас есть объект кода, и мы хотим его выполнить. Что дальше? Всё делает строчка из ./Python/pythonrun.crun_mod:

v = PyEval_EvalCode((PyObject*)co, globals, locals);

Функция принимает объект кода и неймспейсы globals и locals (в нашем случае, они являются опять сделанным неймспейсом __main__), создаёт объект фрейма и исполняет его. Вернёмся к Py_Initialize, тот, что определяет состояние потока. Всякий питонячий поток представлен отдельной конструкцией состояния, которая (помимо каждого прочего) указывает на стек исполняемых в нынешний момент фреймов. Позже того, как объект фрейма сделан и помещён наверх стека состояния потока, он (вернее, байт-код, на тот, что он указывает) выполняется, операция за операцией, средствами достаточно длинной функции./Python/ceval.cPyEval_EvalFrameEx.

PyEval_EvalFrameEx принимает фрейм, извлекает коды операций (и операнды, если они имеются; мы ещё побеседуем об этом) и исполняет ломтики C-кода, соответствующие кодам операций. Давайте дизассемблируем отрывок Python-кода и посмотрим, как выглядят эти «коды операций»:

>>> from dis import dis # о! комфортная функция для дизассемблирования!
>>> co = compile("spam = eggs - 1", "<string>", "exec")
>>> dis(co)
 1           0 LOAD_NAME                0 (eggs)
             3 LOAD_CONST               0 (1)
             6 BINARY_SUBTRACT     
             7 STORE_NAME               1 (spam)
            10 LOAD_CONST               1 (None)
            13 RETURN_VALUE
>>>

… даже без специальных познаний, байт-код оказывается довольно читаемым. «Загружаем» что-то с именемeggs (откуда загружаем? откуда мы это загружаем?) и загружаем константное значение (1), после этого выполняется «бинарное вычитание» (что тут подразумевается под словом «бинарный»? что является операндами?), и так дальше.

Как вы могли додуматься, переменные «загружаются» из глобального и локального неймспейсов, которые мы видели ранее, на стек операндов (не путайте со стеком исполняющихся фреймов), как раз туда, откуда бинарное вычитание их вытащит, вычтет одну из иной и положит итог обратно на стек. «Бинарное вычитание» — это вычитание одного операнда из иного (отсель и «бинарное», т.е. тут нет никакой связи с двоичными числами).

Вы можете сами исследовать функцию PyEval_EvalFrameEx в файле ./Python/ceval.c. Она довольно огромная и по явственным причинам я не буду описывать её тут целиком, но покажу код, тот, что исполняется при обработке операции BINARY_SUBTRACT:

TARGET(BINARY_SUBTRACT) {
    PyObject *right = POP();
    PyObject *left = TOP();
    PyObject *diff = PyNumber_Subtract(left, right);
    Py_DECREF(right);
    Py_DECREF(left);
    SET_TOP(diff);
    if (diff == NULL)
        goto error;
    DISPATCH();
}

… вытолкнуть 1-й операнд, взять со стека 2-й операнд, передать оба операнда C-функцииPyNumber_Subtract, сделать малопонятный (мы потом разберёмся с этим) Py_DECREF обоим операнда, переписать верхнее значение стека итогом вычитания и сделать какой-то DISPATCH, если diff не равен NULL. Выходит. Правда мы пока и не понимаем некоторых пророческой, я думаю, что вычитание 2-х чисел в Питоне на самом низком ярусе, в целом, ясно. Но Дабы дойти до этой точки у нас ушло приблизительно полторы тысячи слов!

Позже того, как фрейм исполнен, PyRun_SimpleStringFlags возвращает код заключения, основная функция проводит чистку (специальное внимание мы уделим Py_Finalize), деинициализируется libc (atexit и другое), и процесс завершается.

Верю, данный пост получился довольно информативным, и мы впоследствие будем пользоваться им как фундаментом при обсуждении различных частей Питона. У нас ещё много терминов, к которым необходимо возвратиться: интерпретатор, состояние потока, неймспейс, модули, встроенные функции и переменные, объекты кода и фреймов и те непонятные слова DECREF и DISPATCH из обработчика BINARY_SUBTRACT. Также у нас есть ключевой «фантомный» термин, вокруг которого мы странствовали в этой статье, но тот, что не называли по имени — объект. Объектная система CPython главна для понимания того, как оно всё работает, и я верю, мы обстоятельно обсудим её в дальнейшем посте.

Оставайтесь на связи.

При переводе наверно кто-то пострадал: смыслы, термины, пресмыкающиеся. Давайте совместно делать мир отменнее, пишите об ошибках в комментарии, так надёжнее.

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB