Python
Как работает Пайтон
Пайтон работает так: вы пишете код в файле, запускаете его через интерпретатор Python, интерпретатор проверяет синтаксис, превращает код во внутренние инструкции и выполняет их шаг за шагом. Для новичка это можно представить как цепочку: текст программы -> Python -> выполнение команд -> результат или ошибка
Чаще всего под словом Python имеют в виду CPython. Это основная реализация языка, написанная на C. Когда вы устанавливаете Python с официального сайта или через менеджер пакетов, почти всегда вы получаете именно CPython. Есть и другие реализации, например PyPy, но для старта важно понять обычную схему CPython
По этой теме полезно отдельно посмотреть Составьте еженедельное расписание занятий, чтобы расширить контекст и сравнить подходы
По этой теме полезно отдельно посмотреть Аннотации типов для декораторов в Python, чтобы расширить контекст и сравнить подходы
Что происходит при запуске python main.py
Допустим, у вас есть файл main.py:
name = "Анна"
print(f"Привет, {name}")
Вы запускаете его в терминале:
python main.py
Дальше происходит несколько шагов. Python открывает файл, читает текст программы, проверяет, можно ли этот текст разобрать как корректный Python-код, готовит внутреннее представление программы и начинает выполнение
Если в файле есть синтаксическая ошибка, программа даже не дойдет до нормального выполнения. Например, если забыть закрывающую скобку:
print("Привет"
Python остановится на этапе разбора кода и покажет SyntaxError. Это ошибка формы записи: интерпретатор еще не выполняет вашу логику, он только пытается понять текст программы
Интерпретатор, байткод и виртуальная машина
Python часто называют интерпретируемым языком, но это не значит, что он просто читает файл как обычный текст и выполняет каждую строку напрямую. В CPython исходный код сначала превращается в байткод. Байткод — это набор более простых инструкций для внутренней виртуальной машины Python
Упрощенная схема такая:
файл .py -> разбор синтаксиса -> байткод -> выполнение виртуальной машиной Python
Байткод не похож на исходный код, который пишет человек. Он ближе к коротким внутренним командам: загрузить значение, сохранить имя, вызвать функцию, вернуть результат. Виртуальная машина Python выполняет эти команды и управляет объектами, функциями, ошибками и импортами
Иногда рядом с Python-файлами появляется папка __pycache__. В ней CPython может хранить скомпилированные файлы байткода с расширением .pyc. Это нужно не для того, чтобы заменить ваш исходный код, а чтобы ускорить повторный импорт модулей. Файл .py все равно остается главным исходником
Почему Python не превращает программу сразу в обычный exe
В языках вроде C или Go часто есть привычная схема: написать исходный код, заранее скомпилировать его в отдельный исполняемый файл, потом запускать этот файл. В Python обычно иначе: исходный файл запускается через установленный интерпретатор
Это удобно для обучения и скриптов: поменяли одну строку, сразу запустили файл снова. Но есть цена: часть работы происходит во время запуска и выполнения программы. Python динамически проверяет типы, ищет имена, вызывает функции и управляет объектами уже в момент работы программы
Поэтому Python обычно проще для старта, но не всегда быстрее языков, которые заранее компилируются в машинный код. При этом скорость зависит не только от языка. Часто узким местом становится база данных, сеть, неправильный алгоритм или лишние операции в цикле
Как Python выполняет код сверху вниз
В простом файле Python выполняет команды сверху вниз:
price = 500
count = 3
total = price * count
print(total)
Порядок здесь важен. Сначала создается имя price, затем count, затем вычисляется total, затем результат выводится в консоль. Если попробовать напечатать total до строки, где он создан, Python покажет ошибку NameError
print(total)
total = 1500
Ошибка возникает потому, что на момент выполнения первой строки имя total еще не существует. Python не просматривает файл заранее в поисках будущих переменных. Он выполняет текущую инструкцию и смотрит, какие имена доступны прямо сейчас
Переменные в Python: имена и объекты
В Python переменная — это не коробка, куда кладут значение. Лучше думать так: переменная это имя, которое ссылается на объект
a = [1, 2, 3]
b = a
b.append(4)
print(a)
Результат:
[1, 2, 3, 4]
Почему изменился a, хотя мы работали с b? Потому что оба имени указывают на один и тот же список. Список является объектом, а a и b всего лишь два имени для этого объекта
С числами и строками такое поведение часто не бросается в глаза, потому что они неизменяемые:
x = 10
y = x
y = y + 5
print(x)
print(y)
Результат:
10
15
Здесь y = y + 5 не меняет старое число 10. Python создает новый объект 15 и связывает имя y уже с ним
Условия и циклы меняют маршрут выполнения
Код не всегда идет строго строка за строкой. Условия выбирают ветку:
age = 17
if age >= 18:
print("Можно войти")
else:
print("Доступ закрыт")
Python вычисляет условие age >= 18. Если результат истинный, выполняется первый блок. Если ложный, выполняется блок else. Отступы здесь не украшение, а часть синтаксиса. Именно по отступам Python понимает, какие строки относятся к блоку условия
Цикл повторяет блок несколько раз:
names = ["Анна", "Олег", "Мария"]
for name in names:
print(f"Привет, {name}")
Python берет первый элемент списка, связывает его с именем name, выполняет тело цикла, затем берет следующий элемент. Когда элементы заканчиваются, выполнение идет дальше после цикла
Функции выполняются только при вызове
Когда Python встречает определение функции, он не выполняет тело функции сразу. Он создает объект функции и связывает его с именем
def add_tax(price):
result = price * 1.2
return result
total = add_tax(1000)
print(total)
В этом примере строки внутри add_tax выполняются только тогда, когда Python доходит до вызова add_tax(1000). При вызове создается отдельная область с локальными именами. Внутри функции появляется имя price, затем result, затем return возвращает значение наружу
Если функция вызывает другую функцию, Python держит цепочку вызовов. Когда появляется ошибка, вы видите traceback. Traceback показывает, через какие функции прошла программа перед падением
def divide(a, b):
return a / b
def run():
return divide(10, 0)
run()
Здесь ошибка возникнет в divide, но traceback покажет и вызов run, потому что именно оттуда программа пришла к делению на ноль
Что происходит при import
import загружает другой модуль, то есть другой Python-файл или пакет. Например:
import math
print(math.sqrt(16))
Когда Python видит import math, он ищет модуль math, загружает его и связывает имя math с объектом модуля. После этого можно обращаться к функциям внутри модуля через точку
С вашими файлами принцип похожий. Если рядом лежит файл helpers.py, его можно импортировать:
import helpers
Важно: при первом импорте Python выполняет код модуля сверху вниз. Поэтому в модулях не стоит оставлять случайные действия на верхнем уровне, если они должны запускаться только вручную. Для этого часто используют проверку:
if __name__ == "__main__":
print("Запущено напрямую")
Такой блок выполняется, когда файл запускают как программу, но не выполняется как основной сценарий при обычном импорте
Как Python работает с памятью
Python создает объекты и следит, нужны ли они программе. Когда на объект больше нет ссылок, CPython может освободить память. В обычных учебных скриптах об этом почти не нужно думать, но полезно понимать общий принцип
items = [1, 2, 3]
items = None
Сначала имя items указывает на список. Потом имя items начинает указывать на None. Если на список больше никто не ссылается, он становится ненужным объектом
Это не значит, что новичку нужно вручную освобождать память после каждой переменной. Обычно Python делает это сам. Но если программа держит большие списки, файлы, соединения или изображения, важно закрывать ресурсы и не хранить лишние ссылки
Как Python сообщает об ошибках
Ошибки в Python можно условно разделить на две группы. Первая группа — ошибки синтаксиса. Они появляются до выполнения программы, когда Python не может разобрать текст
if True
print("ok")
Здесь пропущено двоеточие после if True, поэтому будет SyntaxError
Вторая группа — ошибки во время выполнения. Синтаксис правильный, программа стартует, но на конкретной строке случается проблема
number = int("abc")
Такой код синтаксически корректный, но во время выполнения Python не сможет превратить строку "abc" в число и покажет ValueError
Для пользователя traceback выглядит пугающе, но читать его нужно снизу вверх. Внизу обычно написан тип ошибки и короткое объяснение. Выше находится строка файла, где ошибка проявилась
Как увидеть байткод самому
Python позволяет посмотреть байткод через модуль dis. Это не нужно каждый день, но хорошо показывает, что Python действительно готовит внутренние инструкции
import dis
def add(a, b):
return a + b
dis.dis(add)
Вы увидите набор инструкций. Точный вывод зависит от версии Python, но смысл будет примерно такой: загрузить значение a, загрузить значение b, выполнить сложение, вернуть результат
Не нужно учить байткод наизусть. Для новичка важна сама идея: ваш Python-код проходит промежуточный этап перед выполнением. Поэтому Python не просто «читает строки», а разбирает программу и выполняет внутренние инструкции
Почему Python кажется простым, но иногда ведет себя неожиданно
Python скрывает много технических деталей, чтобы код читался проще. Вы пишете print("Привет"), а внутри происходит поиск имени print, подготовка аргумента, вызов функции, вывод в поток консоли и возврат управления обратно программе
Эта простота хороша для старта. Но она же может удивлять. Например, список может измениться через другое имя, импорт может выполнить код модуля, ошибка может появиться только на редких данных, а тип значения может оказаться строкой вместо числа после input()
Поэтому хороший способ изучать Python — не угадывать, а проверять маленькие примеры. Если непонятно, что происходит, выведите тип через type(), промежуточное значение через print(), а для функций посмотрите traceback при ошибке
Частые заблуждения
Python не выполняет все заранее. Многие ошибки появляются только тогда, когда программа дошла до конкретной строки
Python не хранит переменную как коробку со значением. Имя указывает на объект, и несколько имен могут ссылаться на один объект
Python не всегда медленный сам по себе. Часто проблема в алгоритме, лишних запросах, больших данных или неправильной структуре программы
Python не требует знать байткод для обычной работы. Но понимание байткода помогает не путать исходный текст и внутреннее выполнение
Python не обязан запускаться только в терминале. Его можно запускать из редактора, IDE, notebook, сервера, бота или другого приложения, но внутри все равно есть среда выполнения Python
Что читать дальше
Если нужно повторить базу языка без устройства интерпретатора, откройте Конспект по Python для начинающих
Если интересны переменные, вывод и первые команды, поможет материал Что такое print в Python
Что нужно запомнить
Когда вы запускаете Python-файл, CPython читает исходный код, проверяет синтаксис, готовит байткод и выполняет инструкции виртуальной машиной Python. Код идет сверху вниз, если только условия, циклы, функции и ошибки не меняют этот порядок
Переменные в Python являются именами для объектов. Функции выполняются при вызове. Импорт загружает модуль и может выполнить его верхний уровень. Ошибки бывают до выполнения и во время выполнения
Если держать в голове эту схему, Python становится менее загадочным. Вы уже не просто запоминаете команды, а понимаете, что происходит между строкой кода и результатом в консоли