open atlas
↑ К треку
Python для JS/TS-разработчиков PY · 01 · 01

Синтаксис и типы: Python глазами JS/TS-разработчика

Блоки в Python — это отступы, имена — лишь ссылки, int безразмерен, а type hints не работают в рантайме; для JS/TS-разработчика ловушки — == против is и единственный None.

PY Middle ◷ 16 min
Уровень
ОсновыJuniorMiddleSenior
Уже знаешь этот юнит? Пройди быструю проверку за минуту →

JS-разработчик переносит функцию в Python, запускает — и a == b даёт True для двух списков, но a is bFalse, а почти такая же проверка у коллеги возвращает True для двух маленьких чисел и False для двух больших. Type hints ни о чём не предупредили; mypy в CI никто не запускал. Он тянется к ===, не находит его и решает, что == — это строгий вариант. Нет. Каждый из этих сюрпризов сводится к четырём фактам о синтаксисе и модели типов Python, которые тихо отличаются от JavaScript, — и ошибиться в них значит написать код, который проходит сегодня и ломается на входе, который ты не протестировал. К концу этого урока ты будешь знать, почему каждый из них бьёт и что делать иначе.

Блоки — это отступы; переменные — просто имена

В Python нет фигурных скобок и точек с запятой. Блок — это набор строк с одним отступом под строкой-заголовком, оканчивающейся на :. Отступ и есть синтаксис: смешать табы с пробелами или сбиться на один пробел — это IndentationError, а не косметика. Стандарт сообщества (PEP 8) — четыре пробела.

def classify(n: int) -> str:
    if n < 0:
        return "negative"
    elif n == 0:
        return "zero"
    return "positive"          # тот же уровень, что и if → всё ещё в classify

Переменным не нужно ключевое слово. Нет ни let, ни const, ни var: присваивание одновременно объявляет и связывает. А имя — это ссылка на объект, ровно как ссылка на объект в JS: b = a не копирует список, а заставляет b указывать на тот же список, поэтому мутация через одно имя видна через другое. Встроенного const в Python нет; неизменяемость — свойство значения (кортежи, строки, frozenset неизменяемы), а не связывания.

a = [1, 2, 3]
b = a            # b ссылается на ТОТ ЖЕ список, без копии
b.append(4)
print(a)         # [1, 2, 3, 4] — мутировано через b

Числовая башня: int, float и два оператора деления

В JavaScript один числовой тип — IEEE-754 double, — поэтому 9999999999999999 тихо теряет точность. int в Python — произвольной точности: растёт до любого нужного размера, без переполнения, без тихого округления. float — тот самый IEEE-754 double. Ловушка для JS-разработчика — деление: / всегда даёт float, даже на двух int, а // — это деление с округлением вниз (к минус бесконечности).

type(2 ** 100)        # <class 'int'> — 1267650600228229401496703205376, точно
7 / 2                 # 3.5    (float, всегда)
7 // 2                # 3      (деление вниз, int)
-7 // 2               # -4     (округляет к -бесконечности, не к нулю)
7 % 3                 # 1      (остаток)

Строки — неизменяемые последовательности. Интерполяция — это f-string, шаблонный литерал Python, с {} вместо ${}; внутрь можно класть любое выражение: f"{name} has {len(items)} items". Неявного приведения числа к строке нет: "x" + 1 — это TypeError, а не "x1".

Truthiness, None и ловушка == против is

Как и в JS, в Python есть truthiness, но набор «ложных» значений другой: False, None, 0, 0.0, "" и любая пустая коллекция ([], {}, (), set()) ложны; всё остальное истинно. Поэтому if items: — идиоматичная проверка «список непуст?», без отдельной церемонии length > 0.

None — единственное «никакое» значение Python: оно заменяет оба JS-значения null и undefined. undefined нет; отсутствующий атрибут бросает ошибку, отсутствующий ключ словаря бросает, а отсутствующий аргумент берёт значение по умолчанию, которое ты объявил (часто None). Идиоматичная проверка — if x is None:, а не == None, потому что None — синглтон, и идентичность точна и быстра.

Отсюда главная ловушка. == сравнивает значение; is сравнивает идентичность (указывают ли два имени на буквально один и тот же объект в памяти). В JS нет точного аналога is=== для примитивов всё равно сравнение по значению. Спутать их — классический баг, и он коварен из-за кэширования маленьких int: CPython заранее создаёт целые от −5 до 256, поэтому is случайно возвращает True для маленьких int и False для больших.

a = 256;  b = 256
a is b          # True  — оба указывают на ОДИН кэшированный объект

x = 257;  y = 257
x is y          # False — два разных объекта (равны по значению, не по идентичности)
x == y          # True  — сравнение значений, которое тебе и было нужно

[] == []        # True  — равны по значению
[] is []        # False — два отдельных объекта-списка

Правило: используй is только для None, True, False и объектов-маркеров (sentinel); == — для всего остального. Сами bool — подкласс int: True == 1 и False == 0 дают True, — ещё один острый угол при нестрогих сравнениях.

Викторина

x = 257; y = 257. Что вернут x == y и x is y и почему?

Type hints описывают, но не принуждают

Если ты пришёл из TypeScript с ожиданием, что аннотация параметра как int реально остановит str в рантайме, — этот раздел сэкономит тебе инцидент в проде.

Это самое глубокое отличие от TypeScript. Type hints в Python (PEP 484) выглядят как аннотации TS — def f(x: int) -> str:, count: int = 0, items: list[str], — но интерпретатор игнорирует их в рантайме. Передать str туда, где аннотирован int, проходит нормально, пока что-нибудь реально не сломается на значении. Hints проверяет статически отдельный инструмент — mypy или pyright, который ты запускаешь в CI; по сути это совещательные метаданные в __annotations__.

def repeat(s: str, n: int) -> str:
    return s * n

repeat("ab", 3)       # 'ababab' — корректно
repeat(3, "x")        # hint сказал int/str; Python не проверяет — узнаешь через баг

Контраст с TS тонкий. Типы TS тоже стираются при компиляции — в рантайме их тоже нет. Разница в экосистеме и принуждении: в TS код, не прошедший проверку типов, обычно не отгружается (сборка падает); в Python программа запускается независимо от того, запускал ли ты mypy. Считай hints документацией, которую может проверить чекер, а не рантайм-защитой. Нужна валидация в рантайме — это библиотека (Pydantic, dataclasses с проверками), а не система типов.

JS / TSPythonЗаметка
let x = 1; const y = 2;x = 1; y = 2Без ключевого слова; встроенного const нет (неизменяемо само значение)
a === b (значение, без приведения)a == bНет ===; == — сравнение значений
Object.is(a, b) / идентичность ссылокa is bИдентичность; только для None/True/False/маркеров
null и undefinedNoneОдно «никакое»; проверяй через is None
$&#123;name&#125;: $&#123;n&#125;f”{name}: {n}“f-string; держит любое выражение
number (один тип IEEE-754)int / floatint произвольной точности; / всегда float, // вниз
typeof xtype(x) / isinstance(x, T)type() возвращает объект-класс
Почему это работает

Почему b = a не копирует? Потому что у Python (как у JS для объектов) ссылочная семантика: каждая переменная — это имя, связанное с объектом, и присваивание перепривязывает имя, не трогая объект. Изменяемые объекты (list, dict, set) разделяются; неизменяемые (int, str, tuple, frozenset) нельзя поменять на месте, поэтому их совместное использование незаметно. Поэтому же изменяемый аргумент по умолчанию — def f(items=[]) — знаменитая ловушка: список создаётся один раз при определении и разделяется между всеми вызовами.

Выбери лучший вариант

Нужно проверить, отсутствует ли значение в переменной с опциональным значением. Выбери корректную, идиоматичную проверку.

Вспомните перед уходом
  1. 01
    Объясни разницу между == и is в Python и почему `257 is 257` может быть False, а `256 is 256` — True.
  2. 02
    Коллега из TypeScript расставляет type hints повсюду и ждёт, что неверные вызовы упадут в рантайме, как в собранной TS-сборке. Что не так с этим ожиданием и что на самом деле принуждает типы?
Итог

Для пришедшего из JS/TS синтаксис и модель типов Python переставляют несколько основ. Блоки задаются отступами под строкой-заголовком, оканчивающейся на :, — пробелы и есть синтаксис, скобок и точек с запятой нет. Переменным не нужно ключевое слово; присваивание объявляет и связывает, а имя — ссылка на объект, поэтому b = a разделяет, а не копирует, — встроенного const нет, есть лишь неизменяемые значения. Числовая модель расщепляет единственное число JS на int произвольной точности и IEEE-754 float, где / всегда даёт float, а // делит вниз. Строки интерполируются через f-string и никогда не приводят числа неявно. Truthiness считает ложными 0, "", None и любую пустую коллекцию, а None — единственное «никакое» значение, заменяющее и JS null, и undefined и проверяемое через is None. Самая острая ловушка — == (значение) против is (идентичность): они расходятся на равных, но разных объектах, а кэш маленьких int обманчиво даёт is == True для целых в −5..256. Наконец, type hints (аннотации типов) выглядят как аннотации TS, но никогда не принуждаются в рантайме; их статически проверяет mypy или pyright, и в отличие от TS-сборки, которая падает на ошибках типов, Python-программа запускается всё равно — поэтому относись к hints как к проверяемой документации, а не как к защите. Теперь, когда видишь if x is None там, где написал бы == None, или x=None там, где хотел поставить x=[], — ты знаешь, почему это сделано именно так, и поступаешь так же.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.