Skip to content

Latest commit

 

History

History
418 lines (297 loc) · 19.7 KB

File metadata and controls

418 lines (297 loc) · 19.7 KB

← Оглавление

Python

«Питон — язык, который позволяет думать о задаче, а не о языке» — Гвидо ван Россум


Что такое Python

Python — интерпретируемый язык программирования общего назначения с акцентом на читаемость кода.

Создан: Гвидо ван Россумом (Guido van Rossum), нидерландским программистом. Первая версия: 1991 год. Назван в честь британского комедийного шоу Monty Python's Flying Circus — не в честь змеи.

Какую задачу решал

В конце 1980-х Гвидо работал над языком ABC — учебным языком с чистым синтаксисом. ABC был хорош для обучения, но плохо интегрировался с реальными системами: не умел работать с файловой системой, не расширялся модулями, не был практичен для реальных задач.

Python создавался как практичный наследник ABC: такой же читаемый, но пригодный для настоящей работы — скриптов, автоматизации, работы с файлами и системой.

Особенности Python

  • Читаемость как главный принцип — отступы вместо скобок, код читается почти как текст
  • Батарейки включены (batteries included) — огромная стандартная библиотека, не нужны сторонние пакеты для базовых задач
  • Динамическая типизация — не нужно объявлять типы переменных
  • Интерактивность — можно запускать код строка за строкой в REPL
  • Кроссплатформенность — один код работает на Windows, macOS, Linux
  • Огромная экосистема — pip, PyPI, миллионы библиотек

Где применяется

  • Веб — Django, FastAPI, Flask
  • Data Science и ML — NumPy, Pandas, TensorFlow, PyTorch
  • Автоматизация и скрипты — замена bash для сложных задач
  • DevOps — Ansible написан на Python
  • Научные вычисления — астрофизика, биоинформатика, физика
  • Обучение — самый популярный первый язык

На чём написан Python

CPython (стандартная реализация Python) написан на языке C.

Почему C

  • C — системный язык близкий к железу. Компилируется прямо в машинный код — максимальная скорость выполнения
  • Переносимость — C-компиляторы существуют для всех платформ. Написал интерпретатор на C — он работает везде, где есть C
  • Интеграция — Python-код может напрямую вызывать C-библиотеки. Так работают NumPy, OpenCV и другие быстрые библиотеки: интерфейс на Python, вычисления на C
  • Зрелость — C существует с 1972 года, крайне стабилен и предсказуем

CPython — не единственная реализация. Есть PyPy (Python на Python, с JIT-компилятором — быстрее CPython), Jython (на Java), IronPython (на C# для .NET). Но CPython — стандарт.


Парадигмы программирования

Python — мультипарадигменный язык. Не заставляет следовать одному стилю.

  • Процедурное — последовательность функций и инструкций
  • Объектно-ориентированное (ООП) — классы, объекты, наследование
  • Функциональное — функции как объекты первого класса, map, filter, lambda, замыкания
# Процедурное
result = 0
for i in range(10):
    result += i

# ООП
class Counter:
    def __init__(self):
        self.value = 0
    def increment(self):
        self.value += 1

# Функциональное
result = sum(range(10))
squares = list(map(lambda x: x**2, range(10)))

Python не заставляет выбирать — можно смешивать стили внутри одного проекта.


Компилируемый vs Интерпретируемый

Прежде чем понять как работает Python — нужно понять разницу между двумя подходами исполнения кода.

Компилируемые языки

Компиляция — перевод всего исходного кода в машинный код до запуска. Результат — исполняемый файл (.exe, бинарник), который процессор выполняет напрямую.

исходный код (.c, .cpp, .go)
        ↓
    компилятор (один раз, заранее)
        ↓
  машинный код (.exe / бинарник)
        ↓
  процессор выполняет напрямую — без посредников

Примеры: C, C++, Go, Rust.

Плюсы:

  • Максимальная скорость — процессор работает с готовым машинным кодом без посредников
  • Ошибки типов и синтаксиса обнаруживаются ещё до запуска

Минусы:

  • Бинарник привязан к платформе: windows.exe не запустится на Linux
  • Цикл «изменил → скомпилировал → запустил» замедляет разработку

Интерпретируемые языки

Интерпретация — исходный код выполняется строка за строкой прямо во время работы программы. Посредник — интерпретатор — читает строку и сразу её выполняет.

исходный код (.py)
        ↓
  интерпретатор читает строку
        ↓
  выполняет строку
        ↓
  читает следующую строку
        ↓
     ...и так до конца файла

Примеры: Python, Ruby, PHP.

Плюсы:

  • Один код запускается везде, где установлен интерпретатор
  • Быстрая разработка — сохранил, запустил, увидел результат
  • Интерактивность — REPL, Jupyter Notebook

Минусы:

  • Медленнее компилируемых: каждая строка обрабатывается интерпретатором во время работы
  • Ошибки обнаруживаются только в момент выполнения конкретной строки

Сравнение

Компилируемый Интерпретируемый
Когда переводится в машинный код До запуска Во время выполнения
Скорость Быстрее Медленнее
Переносимость Бинарник под каждую ОС Один файл — везде
Ошибки На этапе компиляции В момент выполнения строки
Скорость разработки Медленнее Быстрее
Примеры C, Go, Rust, C++ Python, Ruby, PHP

Python — интерпретируемый, но с нюансом: перед интерпретацией он компилирует код в байткод (.pyc). Это ускоряет повторные запуски, но байткод всё равно выполняется виртуальной машиной, а не процессором напрямую.


Как работает Python

Запуск python script.py — это не просто «читать и выполнять строки». Внутри происходит четыре этапа.

Полный путь от кода до результата

script.py  (исходный текст)
     ↓
1. Лексический анализ  →  токены
     ↓
2. Синтаксический анализ  →  AST (дерево)
     ↓
3. Компиляция  →  байткод (.pyc в __pycache__)
     ↓
4. CPython VM  →  выполнение  →  результат

Этап 1 — Лексический анализ (Lexer)

Исходный текст разбивается на токены — минимальные смысловые единицы: ключевые слова, имена переменных, числа, операторы.

x = 42 + 1
[NAME 'x']  [OP '=']  [NUMBER '42']  [OP '+']  [NUMBER '1']

Здесь проверяется, что символы вообще допустимы в языке.


Этап 2 — Синтаксический анализ (Parser)

Токены превращаются в AST (Abstract Syntax Tree) — абстрактное синтаксическое дерево. Иерархическое представление структуры программы.

x = 42 + 1
Assign
├── target: Name('x')
└── value: BinOp
    ├── left:  Constant(42)
    ├── op:    Add
    └── right: Constant(1)

Здесь проверяется грамматика. x = = 42 — ошибка синтаксиса обнаруживается именно здесь.


Этап 3 — Компиляция в байткод

AST компилируется в байткод — набор простых инструкций для виртуальной машины Python. Сохраняется в файлы .pyc в папке __pycache__ — при следующем запуске, если файл не изменился, этапы 1–3 пропускаются.

import dis
dis.dis("x = 42 + 1")
LOAD_CONST   43     # Python вычислил 42+1=43 заранее (оптимизация)
STORE_NAME   x      # сохранить результат в переменную x

Байткод — не машинный код. Это инструкции для виртуальной машины CPython.


Этап 4 — Выполнение на виртуальной машине (CPython VM)

CPython VM — это C-программа, которая читает байткод и выполняет каждую инструкцию. Реализована как стековая машина: операции берут аргументы из стека и кладут результат обратно.

байткод-инструкция
        ↓
  C-код интерпретатора
        ↓
  машинный код (уже от C-компилятора)
        ↓
     процессор

Именно здесь происходит реальная работа: создаются объекты в памяти, вызываются функции, выполняются вычисления.


До каких значений работает Python

На самом нижнем уровне Python оперирует объектами — в Python всё является объектом, включая числа, строки и даже True.

Базовые (атомарные) типы:

42           # int    — целое число (без ограничения размера!)
3.14         # float  — число с плавающей точкой (64-битный double из C)
True / False # bool   — логическое (подкласс int: True == 1, False == 0)
"текст"      # str    — строка Unicode (не байты)
b"байты"     # bytes  — байтовая строка
None         # NoneType — отсутствие значения, аналог null

Каждое значение в памяти — это C-структура (PyObject), содержащая:

  • счётчик ссылок — сколько переменных ссылается на объект (для сборщика мусора)
  • указатель на тип объекта
  • само значение
// упрощённо, как число 42 живёт в памяти CPython (C-код)
typedef struct {
    int   ob_refcnt;          // счётчик ссылок
    PyTypeObject *ob_type;    // тип: int, str, float...
} PyObject;

typedef struct {
    PyObject ob_base;
    long ob_ival;             // само число: 42
} PyLongObject;

x = 42 в Python — это не 4 байта в памяти как в C. Это полноценный объект с метаданными. Поэтому Python потребляет больше памяти, чем C, но даёт безопасность и гибкость.

Целые числа в Python не переполняются — в отличие от C (int там максимум ~2 млрд). Python автоматически расширяет размер числа.

2 ** 1000   # работает, выведет огромное число — в C переполнилось бы

Сборщик мусора (garbage collector)

Объекты создаются в куче, но кто освобождает память, когда объект больше не нужен? В CPython — два механизма.

1. Подсчёт ссылок (основной). У каждого объекта есть счётчик ссылок (ob_refcnt): сколько имён/контейнеров на него указывают. Ссылку добавили — счётчик +1, удалили — −1. Как только счётчик дошёл до 0 — объект немедленно уничтожается, память освобождается.

import sys
a = []
sys.getrefcount(a)   # → 2  (сама переменная a + временный аргумент функции)
b = a
sys.getrefcount(a)   # → 3  — теперь на список ссылаются a и b
del b                # −1 → снова 2

2. Циклический сборщик (дополнительный). Подсчёт ссылок не справляется с циклами: если a ссылается на b, а b на a, их счётчики никогда не дойдут до 0, даже когда извне на них никто не ссылается — утечка памяти. Для этого есть отдельный сборщик (модуль gc), который периодически находит недостижимые циклы и удаляет их.

import gc
class Node: pass
x = Node(); y = Node()
x.ref = y; y.ref = x     # цикл: x → y → x
del x, y                 # извне ссылок нет, но счётчики ещё по 1 (друг на друга)
gc.collect()             # → 2  — циклический сборщик нашёл и удалил оба

Простыми словами: обычный мусор (счётчик → 0) убирается сразу, зацикленный — периодической уборкой gc. Поэтому в Python обычно не думают о памяти вручную, но знать про циклы полезно (например, разрывать ссылки или использовать weakref).


Python 2 vs Python 3

В 2008 году вышел Python 3 — обратно несовместимый с Python 2. Код на Python 2 не запускается на интерпретаторе Python 3 без изменений.

Python 2 официально прекратил поддержку 1 января 2020 года. Сейчас используется только Python 3.

Ключевые отличия

print — функция vs оператор

# Python 2
print "Hello"          # оператор, скобки не нужны

# Python 3
print("Hello")         # функция, скобки обязательны

Деление целых чисел

# Python 2
5 / 2    # → 2    (целочисленное деление по умолчанию — неожиданно!)
5 / 2.0  # → 2.5

# Python 3
5 / 2    # → 2.5  (всегда float)
5 // 2   # → 2    (явное целочисленное деление — оператор //)

Строки — байты vs Unicode

# Python 2
"текст"    # байтовая строка (bytes) — кодировка зависит от системы
u"текст"   # юникодная строка (нужен префикс u)

# Python 3
"текст"    # всегда Unicode  ← проблем с кодировками стало меньше
b"текст"   # байтовая строка (нужен префикс b)

range — список vs генератор

# Python 2
range(1000000)    # создаёт список из миллиона чисел в памяти сразу — тяжело

# Python 3
range(1000000)    # генератор, числа вычисляются по одному — экономит память

input — поведение

# Python 2
raw_input("Введи: ")   # возвращает строку — безопасно
input("Введи: ")       # вычисляет введённое как Python-выражение — опасно!

# Python 3
input("Введи: ")       # всегда возвращает строку (как raw_input в Python 2)

Аннотации типов — появились в Python 3, в Python 2 нет:

# Python 3
def greet(name: str) -> str:
    return f"Привет, {name}"

f-строки — появились в Python 3.6:

name = "Андрей"

# Python 2 / ранний Python 3
print("Привет, %s" % name)
print("Привет, {}".format(name))

# Python 3.6+
print(f"Привет, {name}")     # f-строка — удобно и читаемо

Сводная таблица

Различие Python 2 Python 3
print оператор print "x" функция print("x")
Деление int 5/2 → 2 5/2 → 2.5
Строки bytes по умолчанию Unicode по умолчанию
range список в памяти генератор
input вычисляет выражение возвращает строку
f-строки нет есть (3.6+)
Аннотации типов нет есть (3.5+)
Поддержка прекращена в 2020 активна