что такое интерпретатор python
Погружение в пучину интерпретатора Python. Ч1
От переводчика: Наверно всем интересно, что внутри у инструмента, который используешь, этот интерес овладел и мной, но главное не утопать в нём и не закопаться так что не вылезти. Найдя для себя интересный материал, я решил заботливо перевести его и представить хабросообществу (моя первая публикация, прошу ногами сильно не пинать). Тем, кому интересен как Python работает на самом деле, прошу проследовать под кат.
Последние три месяца я потратил много времени на byterun, интерпретатор питоновского байткода, написанного на питоне. Работа над этим проектом была для меня захватывающе весёлой и познавательной. Я был бы рад, если бы вы тоже его потыкали. Но прежде нам надо немного остепенится, понять как работает python, так, чтобы мы знали, что такое интерпретатор на самом деле и с чем его едят.
Я подразумеваю, что вы сейчас в том же положении, что и я три месяца назад. Вы понимаете python, но понятия не имеете как он работает.
Небольшая заметка: Я работаю с версией 2.7 в этом посте. Третья версия почти схожа со второй, есть небольшие различия в синтаксисе и наименованиях, но в целом всё тоже самое.
Как работает python?
Мы начнём с очень (очень очень) высокого уровня внутренней работы. Что происходит когда вы выполняете код в вашем интерпретаторе?
Годы идут, ледянки тают, Линус Торвальдс пилит очередное ядро, а 64 битый процессор без устали трудится, тем временем происходит четыре шага: лексической анализ, парсинг, компиляция и наконец таки интерпретация. Парсер забирает скормленные ему инструкции и генерирует структуру которая объясняет их связь формируя AST( Абстрактное Синтаксическое Дерево). Компилятор затем преобразует AST в одни (или несколько) объектов кода (байткод + обвязка). Потом интерпретатор выполняет каждый объект.
Я не собираюсь говорить об лексическом анализе, парсинге или компиляции сегодня, наверно потому что я сам про эти вещи ни чего не знаю, но не унывайте: вы всегда сможете изучить это, потратив часов этак пятьдесят. Мы предположим, что эти шаги прошли хорошо и успешно, и у нас есть на руках объекты python кода.
Перед тем как приступить к делу, я хочу сделать небольшую ремарку: в данном топике мы будем говорить об объектах функциях, объектах кода, и байткоде. Это всё разные вещи. Давайте начнём с функций. Нам не обязательно вникать глубоко в них, чтобы добраться до интерпретатора, но я просто хочу прояснить, что объекты функции и объекты кода — это две большие разницы, а объекты функции — самые интересные.
Объекты функции
Вы наверно могли слышать про «объекты функции». Это вещи которые люди подразумевают когда говорят: «Функции — это объекты первого класса». Давайте изучим их подробнее:
«Функции это объекты первого класса» означает что функции — это объекты также как список это объект или экземпляры MyObject это объекты. Раз foo это объект, мы можем исследователь его не выполняя его (в этом и есть разница между foo() и foo). Мы можем предать foo как параметр в другую функцию или можем присвоить его переменной.
Давайте немного посмотрим на foo подробней:
Как вы можете видеть в выше приведённом коде, объект кода это атрибут объекта функции. Объект кода генерируется питоновским компилятором и интерпретатором, он содержит информацию необходимую для работы интерпретатора. Давайте посмотрим на атрибуты объекта кода:
Здесь целая куча ништяков, большинство из которых нм сейчас не нужно. Давайте подробнее рассмотрим три атрибута объекта foo.
Вот что здесь есть: имена переменных и констант которые используются в нашей функции и количество принимаемых аргументов. Но мы всё ещё не видим ни чего что было бы похоже на инструкции. Инструкции называют байткодом ссылка, кстати это атрибут объекта кода:
Напоминаю что байткод и объекты кода это не одно и тоже. Байткод это атрибут объекта кода помимо многих других атрибутов. Так что же такое байткод? Ну это просто набор байт. Они выглядят странно когда мы их печатаем потому что некоторым байтом сопоставимы символы а другим нет, давайте выведем их как числа.
Вот байты которые творят всю магию. Интерпретатор будет последовательно и безустанно выбирать байты, смотреть какие они операции выполняют и с какими аргументами и исполнять команды. Для того чтобы пойти ещё дальше можно просмотреть исходный код Cpython а конкретно ceval.c что мы сделаем позднее.
Дизассемблирование байткода
Дизассемблирование означает взять все эти байты и преобразовать их во что-нибудь, что мы человеки способны понять. Это не выполняется в стандартном цикле питона. Сегодня для этой задачи есть отличный инструмент — модуль dis. Мы воспользуемся функцией dis.dis чтобы проанализировать что делает наша foo.
Первый номер это строка исходного python кода, второй номер это смещение внутри байткода: LOAD_CONST находится на позиции 0, а STORE_FAST на позиции 3 и так далее. Средняя колонка это название самой инструкции, последние две колонки дают понятие об аргументах инструкции (ели они есть), четвертая колонка показывает сам аргумент, который представляет собой индекс в других атрибутов объекта кода. В этом примере аргумент для LOAD_CONST это индекс в списке co_consts, а аргумент для STORE_FAST это индекс в co_varnames, в пятой колонке выводятся имена переменных или значение констант. Мы можем с легкостью это проверить:
Это также объясняет вторую инструкцию STORE_FAST которая находится по позиции 3 в байткоде. Если инструкция имеет аргумент следующие два байта и есть этот аргумент. Работа интерпретатора как раз таки в том чтобы не запутается и продолжать сеять разумное, доброе, вечное. (вы могли заметить что BINARY_ADD не имеет аргументов, не волнуйтесь мы ещё вернемся к этому)
Была одна вешь которая удивляла меня когда я начел разбираться в том как работает python, как python может быть динамическим, если он ещё и «компилируется»? Обычно эти два слова «антонимы», есть динамические языки такие как Python, Ruby, и Javascript, а есть компилируемые таки как C, Java, и Haskell.
Когда люди говорят об компилируемых языках они имеют ввиду компиляцию в нативные x86/ARM/etc инструкции. Интерпретируемый язык не имеет компиляции вообще, разве что только «компилируется» на лету в байткод. Интерпретатор питона разбирает байткод и выполняет его внутри виртуальной машины, что кстати достаточно много работы, но мы поговорим об этом позднее.
Для того чтобы быть динамическим надо быть абстрактным, давайте посмотрим что это значит:
Эта дизассемблированая функция в байткоде. К тому времени как мы получаем приглашение функция modus была скомпилирована и объект корда был сгенерирован. Достаточно внезапно, но операция остатка от деления % (операция modulus) преобразуется в BINARY_MODULO. Похоже этой функцией можно воспользоваться для чисел:
Неплохо, а что если мы передадим что то другое, например строку.
Опана, что это тут? Вы наверно уже видели это раньше:
Когда операция BINARY_MODULO выполняется для двух строк она выполняет подстановку строк вместо остатка от деления. Эта ситуация отличный пример динамической типизации. Когда компилятор генерирует объект кода для modulus он не имеет понятия что такое x и y, строки ли они или числа или что-то ещё. Он просто выполняет инструкции: загрузить одну перемененную, загрузить другую, выполнять препарацию бинарного модуля, вернуть результат. Работа интерпретатора в том чтобы понимать что BINARY_MODULO значит в текущем контексте. Наша функция modulus может считать остаток, подставлять строки… может что-то ещё? Если мы определим класс с методом __mod__ то мы сможем сделать что угодно.
Одна и та же функция с одним и тем же байткодом может выполнять разные операции в зависимости от типа контекста. Также функция modulus может возбудить исключение для примера TypeError если мы вызовем его для объектов, которые не реализованы.
Это является одной из причин того, почему трудно оптимизировать python. Вы не знаете, когда вы генерируете код объекта и байт-код, что за объекты будут в конечном итоге. Russell Power и Alex Rubinsteyn написали статью «как быстр может быть python», это статья достаточного содержательная.
На сегодня пока все. Оригинал статьи тут. Прошу прошения за возможные ошибки т.к. от природы обладаю врождённой безграмотностью и вынужден пользоваться машинным способом проверки текста.
Интерпретатор Python: о чём думает змея? (часть I-III)
Данная серия статей рассчитана на тех, кто умеет писать на python в целом, но плохо представляет как этот язык устроен изнутри. Собственно, как и я три месяца назад.
Небольшой дисклеймер: свой рассказ я буду вести на примере интерпретатора python 2.7. Всё, о чем пойдёт речь далее, можно повторить и на python 3.x с поправкой на некоторые различия в синтаксисе и именование некоторых функций.
Часть I. Слушай Питон, а что у тебя внутри?
Начнём с немного (на самом деле, с сильно) высокоуровневого взгляда на то, что же из себя представляет наша любимая змея. Что происходит, когда вы набираете строку подобную этой в интерактивном интерпретаторе?
Ваш палец падает на enter и питон инициирует 4 следующих процесса: лексический анализ, парсинг, компиляцию и непосредственно интерпретацию. Лексический анализ – это процесс разбора набранной вами строки кода в определенную последовательность символов, называемых токенами. Далее парсер на основе этих токенов генерирует структуру, которая отображает взаимоотношения между входящими в неё элементами (в данном случае, структура это абстрактное синтаксическое древо или АСД). Далее, используя АСД, компилятор создаёт один или несколько объектных модулей и передаёт их в интерпретатор для непосредственного выполнения.
Я не буду углубляться в темы лексического анализа, парсинга и компиляции, в основном потому, что сам не имею о них ни малейшего представления. Вместо этого, давайте лучше представим, что умные люди сделали всё как надо и данные этапы в питоновском интерпретаторе отрабатывают без ошибок. Представили? Двигаем дальше.
Прежде чем перейти к объектным модулям (или объектам кода, или объектным файлам), следует кое-что прояснить. В данной серии статей мы будем говорить об объектах функций, объектных модулях и байткоде – всё это совершенно разные, хоть и некоторым образом связанные между собой понятия. Хотя нам и необязательно знать, что такое объекты функций для понимания интерпретатора, но я всё же хотел бы остановить на них ваше внимание. Не говоря уже о том, что они попросту крутые.
Объекты функций или функции, как объекты
Если это не первая ваша статья о программировании на питоне, вы должны быть наслышаны о неких «объектах функций». Это именно о них люди с умным видом рассуждают в контексте разговоров о «функциях, как объектах первого класса» и «наличии функций первого класса в питоне». Рассмотрим следующий пример:
Выражение «функции – это объекты первого класса» означает, что функции – это объекты первого класса, в том смысле, в коем и списки – это объекты, и экземпляр класса MyObject – объект. И так как foo это объект, он имеет значимость сам по себе, безотносительно вызова его, как функции (то есть, foo и foo() — это разные вещи). Мы можем передать foo в другую функцию в качестве аргумента, можем переназначить её на новое имя ( other_function = foo ). С функциями первого класса можно делать, что угодно и они всё стерпят.
Часть II. Объектные модули
На данном этапе we need to go deeper, чтобы узнать, что объект функции в свою очередь содержит объект кода:
Как видно из приведённого листинга, объектный модуль является атрибутом объекта функции (у которого есть и множество других атрибутов, но в данном случае особого интереса они не представляют в силу простоты foo ).
Объектный модуль генерируется питоновским компилятором и далее передаётся интерпретатору. Модуль содержит всю необходимую для выполнения информацию. Давайте посмотрим на его атрибуты:
Их, как видите, немало, поэтому все рассматривать не будем, для примера остановимся на трёх наиболее понятных:
Атрибуты выглядят довольно интуитивно:
co_varnames – имена переменных
co_consts – значения, о которых знает функция
co_argcount – количество аргументов, которые функция принимает
Всё это весьма познавательно, но выглядит несколько черезчур высокоуровнево для нашей темы, не правда ли? Где же инструкции интерпретатору для непосредственного выполнения нашего модуля? А такие инструкции есть и представлены они байткодом. Последний также является атрибутом объектного модуля:
Что за неведомая байтовая фигня, спросите вы?
Часть III. Байткод
Вы наверное и сами понимаете, но я, на всякий случай, озвучу – «байткод» и «объект кода» это разные вещи: первый является атрибутом второго, среди многих других (см. часть 2). Атрибут называется co_code и содержит все необходимые инструкции для выполнения интерпретатором.
Что же из себя представляет этот байткод? Как следует из названия, это просто последовательность байтов. При выводе в консоль выглядит она достаточно бредово, поэтому давайте приведём её к числовой последовательности, пропустив через ord :
Таким образом мы получили числовое представление питоновского байткода. Интерпретатор пройдётся по каждому байту в последовательности и выполнит связанные с ним инструкции. Обратите внимание, что байткод сам по себе не содержит питоновских объектов, ссылок на объекты и т.п.
Байткод можно попытаться понять открыв файл интерпретатора CPython (ceval.c), но мы этого делать не будем. Точнее будем, но позже. Сейчас же пойдём простым путём и воспользуемся модулем dis из стандартной библиотеки.
Дизассемблируй это
Итак, давайте применим dis и снимем паранжу с нашего объектного модуля. Для этого воспользуемся функцией dis.dis :
Числа в первой колонке – это номера строк анализируемых исходников. Вторая колонка отражает смещение команд в байткоде: LOAD_CONST находится в позиции «0», STORE_FAST в позиции «3» и т.д. Третья колонка даёт байтовым инструкциям человекопонятные названия. Названия эти нужны только жалким людишкам нам, в интерпретаторе они не используются.
Это также объясняет, почему инструкция STORE_FAST находится на третьей позиции в байткоде: если где-то в байткоде есть аргумент, следующие два байта будут представлять этот аргумент. Корректная обработка таких ситуаций также ложится на плечи интерпретатора.
Как dis переводит байты (например, 100) в осмысленные имена (например, LOAD_CONST ) и наоборот? Подумайте, как бы вы сами организовали подобную систему? Если у вас появились мысли, вроде «ну, может там есть какой-то список с последовательным определением байтов» или «по-любому словарь с названиями инструкций в качестве ключей и байтами как значениями», поздравляю – вы абсолютно правы. Именно так всё и устроено. Сами определения происходят в файле opcode.py (можно также посмотреть заголовочный файл opcode.h), где вы сможете увидеть
полторы сотни подобных строк:
(Какой-то любитель комментариев заботливо оставил нам пояснения к инструкциям.)
Теперь мы имеем некоторое представление о том, чем является (и чем не является) байткод и как использовать dis для его анализа. В следующих частях мы рассмотрим, как питон может компилироваться в байткод, оставаясь при этом динамическим ЯП.
Интерпретатор Python
Будучи самым быстрорастущим языком программирования в 2019 году, нет недостатка в интерпретаторах для Python. Но поскольку каждый из них отвечает определенным требованиям, вам необходимо сначала выяснить, какой из них вам подходит.
Прежде чем мы перейдем к объяснению 6 самых популярных интерпретаторов Python, давайте сначала получим краткое понимание интерпретатора.
Что такое интерпретатор?
По определению, интерпретатор – это тип компьютерной программы, которая непосредственно выполняет инструкции, написанные на каком-то языке программирования или языке сценариев. Под прямым исполнением мы подразумеваем, что для этого не требуется, чтобы инструкции сначала компилировались в программу на машинном языке.
Интерпретатор Python
Итак, со всем этим давайте перейдем к краткому обзору 6 самых популярных интерпретаторов Python:
CPython
Поддержка – до Python 3.7
Это стандартная и наиболее широко используемая реализация языка программирования Python. Написанный на C и Python, CPython является интерпретатором, который предлагает интерфейс сторонней функции с C и другими языками программирования.
CPython также можно классифицировать как компилятор, потому что он преобразует код Python в байт-код перед его интерпретацией. Он использует GIL, Global Interpreter Lock, которая может представлять ограничение, поскольку отключает параллельные потоки Python для процесса.
Как эталонная реализация Python, CPython предлагает наибольшую совместимость с пакетами Python и модулями расширения C. Таким образом, все версии языка программирования Python реализованы на языке C.
CPython является единственным вариантом для использования пакетов Python, которые полагаются на расширения C для правильной работы. Ориентация на CPython необходима, если вы хотите охватить максимально широкую аудиторию для программы, разработанной на языке программирования Python.
IronPython
Поддержка – до Python 2.7
Jython
Поддержка – до Python 2.7
Ранее известный как JPython, Jython является реализацией Python, работающей на платформе Java. Написанный на Java и Python, Jython преобразует код Python в байт-код Java и, следовательно, позволяет запускать код Python на любой машине, имеющей JVM.
Jython обеспечивает поддержку как статической, так и динамической компиляции. Важной особенностью популярного интерпретатора Python является то, что он позволяет импортировать, а также использовать любой класс Java, например модуль Python.
Если вам нужно взаимодействовать с существующей кодовой базой Java или написать код Python для JVM, вы можете сделать ставку на Jython.
Поддержка – до Python 2.7, Python 3.5 и Python 3.6
Обладая JIT-компилятором, PyPy поддерживает C, CLI и JVM. Основная цель PyPy – предложить максимальную совместимость с эталонной реализацией CPython, одновременно повышая производительность.
PythonNet
Поддерживает – Python 2.6 до Python 3.5
Stackless Python
Поддержка – до Python 3.7
CPython и другие популярные интерпретаторы Python зависят от вызова C для своего стека. Однако это не относится к интерпретатору Stackless Python.
Хотя Stackless Python использует стек C, он очищается между вызовами функций. Следовательно, интерпретатор Python не зависит от вызова C для своего стека. Как и CPython, Stackless Python написан с использованием C и Python.
Помимо поддержки потоков, Stackless Python предлагает поддержку каналов связи, сопрограмм, предварительно скомпилированных двоичных файлов, циклического планирования, сериализации задач и тасклетов.
Возможно, самая важная особенность Stackless Python – это микропотоки. Эта функция помогает избежать значительной части издержек, связанных с типичными потоками операционной системы.
Python 2 или Python 3? Какой выбрать?
С появлением Python 3 в 2008 году, всегда был важный вопрос, чтобы спросить, стоит ли придерживаться более старого Python 2 или перейти на борт самого последнего Python 3.
Ответ может быть легким для новичков в изучении Python; Начните с более нового и лучшего Python 3. Однако выбор не так прост для организаций или профессионалов, которые сильно полагаются на Python для своего бизнеса и имеют огромные базы кода Python.
Большинство приложений Python на сегодняшний день использует Python 2.7. Однако переход на Python 3 со временем увеличивается. Отчасти это связано с тем, что Python 2.7 будет получать обновления безопасности только до 2020 года.
Для создания новых приложений Python вы должны использовать Python 3. Если вы работаете с новой библиотекой Python с открытым исходным кодом, вы можете написать ее как для Python 2, так и для Python 3. Это потому, что значительная часть разработчиков Python по-прежнему расставляет приоритеты с использованием Python 2.
Рекомендуется использовать новейший интерпретатор Python 3.x, поскольку каждая новая версия предлагает улучшенные исправления ошибок, безопасность и стандартные модули библиотеки.
Вы должны придерживаться Python 2, только если у вас есть уже существующая кодовая база в Python 2 или эксклюзивная библиотека для нее.
Если вы искренне любите Python 2 и не хотите переходить на Python 3, это нормально. Однако следует понимать, что после 2020 года Python 2 не будет таким же прибыльным вариантом, как сегодня. Итак, начать работу с Python 3 сегодня, наряду с Python 2, может быть хорошей идеей.
Это завершает список 6 самых популярных интерпретаторов Python, доступных на данный момент. Вы можете работать с любым из них. Однако у каждого из них есть свои льготы. Таким образом, вы должны выбирать мудрее, особенно когда работаете профессионально.
Хорошим знанием может быть знание нескольких интерпретаторов Python. Таким образом, чем больше вы пытаетесь, тем лучше.
Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.
Как работает Python?
Всем еще раз привет, сейчас расскажу о том, как работает Python, что такое интерпретатор, как работает компилятор и что такое байт-код, далее расскажу о виртуальной машине (PVM) и о производительности Python. Также о альтернативных реализациях интерпретатора.
После того, как вы установили себе Python, перейдем к теоретически-практической части и начнем с того что из себя представляет интерпретатор.
Интерпретатор
В зависимости от используемой версии Python сам интерпретатор может быть реализован как программа на языке C, как набор классов Java и в каком-либо другом виде, но об этом позже.
Запуск сценария в консоли
Давайте запустите в консоле интерпретатор:
Теперь он ожидает ввода комманд, введите туда следующую инструкцию:
ура, наша первая программа! 😀
Запуск сценария из файла
Создайте файл «test.py», с содержимым:
и выполните этот файл:
Вы увидите в консоли результат, поехали дальше!
Динамическая компиляция и байт-код
В следующий раз, когда вы запустите свою программу интерпретатор минует этап компиляции и отдаст на выполнение откомпилированный файл с расширением «.pyc». Однако, если вы изменили исходные тексты вашей программы, то снова произойдет этап компиляции в байт-код, так как Python автоматически следит за датой изменения файла с исходным кодом.
Если Python окажется не в состоянии записать файл с байт-кодом, например из-за отсутствия прав на запись на диск, то программа не пострадает, просто байт-код будет собран в памяти и при завершении программы оттуда удален.
Виртуальная машина Python (PVM)
Производительность
По этим причинам программы на Python не могут выполняться также быстро как на C/C++. Обход инструкций выполняет виртуальная система, а не микропроцессор, и чтобы выполнить байт-код, необходима дополнительная интерпретация, инструкции которой требуют большего времени, чем машинные инструкции микропроцессора.
В итоге, Python по производительности находится между традиционными компилирующими и традиционными интерпретирующими языками программирования.
Альтернативные реализации Python
То что было сказано выше о компиляторе и виртуальной машине, характерно для стандартной реализации Python, так называемой CPython (реализации на ANSI C). Однако также существует альтернативные реализации, такие как Jython и IronPython, о которых пойдет сейчас речь.
CPython
Это стандартная и оригинальная реализация Python, названа так, потому что написана на ANSI C. Именно ее мы установили, когда выбрали пакет ActivePython или установили из FreeBSD портов. Поскольку это эталонная реализация, она как правило работает быстрее, устойчивее и лучше, чем альтернативные реализации.
Jython
Цель Jython состоит в том, чтобы позволить программам на языке Python управлять Java-приложениями, точно также как CPython может управлять компонентами на языках C/C++. Эта реализация имеет беcшовную интеграцию с Java. Поскольку программный код на Python транслируется в байт-код Java, во время выполнения он ведет себя точно также, как настоящая программа на языке Java. Программы на Jython могут выступать в качестве апплетов и сервлетов, создавать графический интерфейс с использованием механизмов Java и т.д. Более того, Jython обеспечивает поддержку возможности импортировать и использовать Java-классы в программном коде Python.
Тем не менее, поскольку реализация Jython обеспечивает более низкую скорость выполнения и менее устойчива по сравнению с CPython, она представляет интерес скорее для разработчиков программ на языке Java, которым необходим язык сценариев в качестве интерфейса к Java-коду.
IronPython
Средства оптимизации скорости выполнения
Существуют и другие реализации, включая динамический компилятор Psyco и транслятор Shedskin C++, которые пытаются оптимизировать основную модель выполнения.
Динамический компилятор Psyco
Во время выполнения программы, Psyco собирает информацию о типах объектов, и затем эта информация используется для генерации высокоэффективного машинного кода, оптимизированного для объектов этого типа. После этого произведенный машинный код заменяет соответствующие участки байт-кода, тем самым увеличивается скорость выполнения.
В идеале некоторые участки программного кода под управление Psyco могут выполняться также быстро, как скомпилированный код на языке Си.
Psyco обеспечивает увеличение скорости от 2 до 100 раз, но обычно в 4 раза, при использовании немодифицированного интерпретатора Python. Единственный минус у Psyco, это то обстоятельство, что в настоящее время он способен генерировать машинный код только для архитектуры Intel x86.
Psyco не идет в стандартной поставке, его надо скачать и установить отдельно. Еще есть проект PyPy, который представляет собой попытку переписать PVM с целью оптимизации кода как в Psyco, проект PyPy собирается поглотить в большей мере проект Psyco.
Транслятор Shedskin C++
Фиксированные двоичные файлы (frozen binaries)
Иногда необходимо из своих программ на Python создавать самостоятельные исполняемые файлы. Это необходимо скорее для упаковки и распространения программ.
Фиксированные двоичные файлы объединяют в единый файл пакета байт-код программ, PVM и файлы поддержки, необходимые программам. В результате получается единственный исполняемый файл, например файл с расширение «.exe» для Windows.
На сегодняшний день существует три основных инструмента создания «frozen binaries»:
Вам надо загружать эти инструменты отдельно от Python, они распространяются бесплатно.
Фиксированные двоичные файлы имеют немалый размер, ибо они содержат в себе PVM, но по современным меркам из все же нельзя назвать необычно большими. Так как интерпретатор Python встроен непосредственно в фиксированные двоичные файлы, его установка не является обязательным требованием для запуска программ на принимающей стороне.
Резюме
На сегодня всё, в следующей статье расскажу о стандартных типах данные в Python, ну и в последующих статьях рассмотрим каждый тип в отдельности, а также функции и операторы для работы с этими типами.
создал файл, запустил его через пайтон, но пишет, что ошибка кодировки (файл сохранен в UTF-8) 🙁
SyntaxError: Non-ASCII character ‘\xd0’