# теория · шаг 1 из 7
Числа, имена и присваивание: чем Python отличается от твоего языка
Коротко
Урок для того, кто уже писал на другом языке и хочет знать, где Python ведёт себя иначе. Целые числа не
переполняются: 2 ** 100 считается точно. Деление / всегда даёт дробное число, а целочисленное
деление // округляет частное вниз, к минус бесконечности, а остаток % согласован с ним: -7 // 2
равно -4, а не -3, как в C и Java. Переменная - это имя, привязанное к значению, а не ячейка с типом. a, b = b, a меняет значения
местами, потому что правая часть вычисляется целиком до присваивания. and и or ленивые и возвращают
один из операндов. Блоки задаются отступом.
Если ты уже пишешь на Python и знаешь, чему равно -7 // 2, листай до «Теперь сам» в конце.
Как думаешь, что напечатает эта строка
print(7 / 2, 7 // 2, -7 // 2, -7 % 2, 2 ** 100)
Прежде чем читать дальше, ответь себе. Если ты пишешь на C, Java или Go, честный ответ звучит примерно так: «3, потом 3, потом -3, потом -1, а последнее переполнится». В Python верно только второе число. Все программы курса написаны на нём, поэтому разницу стоит выучить один раз здесь, а не ловить по одной в каждом разделе.
На что это похоже
Переменная в C похожа на коробку с надписью «целое число»: в неё кладут значение, и коробка определяет, что в ней может лежать. Переменная в Python - это наклейка с именем. Значение живёт само по себе, а наклейку можно переклеить на другое значение, в том числе другого типа. Две наклейки на одном значении - это одно значение с двумя именами, а не две копии. Для чисел и строк это почти незаметно, для списков это главный источник сюрпризов следующего урока.
Механизм: что делает каждая операция
print(7 / 2, 7 // 2, 7 % 2)
print(-7 // 2, -7 % 2)
print(7 // -2, 7 % -2)
print(2 ** 100)
print(0.1 + 0.2, 0.1 + 0.2 == 0.3)
3.5 3 1
-4 1
-4 -1
1267650600228229401496703205376
0.30000000000000004 False
Строка за строкой:
| запись | что делает | чем отличается |
|---|---|---|
/ |
обычное деление, результат всегда дробный | в C 7 / 2 даёт 3 |
// |
частное, округлённое ВНИЗ | в C и Java округление к нулю |
% |
остаток, знак у него как у делителя | в C знак как у делимого |
** |
степень: 2 ** 10 равно 1024 |
^ - не степень: 2 ^ 10 даёт 8 (это побитовая операция) |
| целые | любой длины, без переполнения | цена операции растёт с числом цифр |
| дробные | 64-битные числа с плавающей точкой | как double в C, со всеми его неточностями |
Неточность видна в последней строке вывода. Дробь хранится по степеням двойки, а 0.1 так -
бесконечная дробь, как 1/3 в десятичной: её приходится округлить, и сумма двух округлённых чисел
отличается от округлённого 0.3 в последнем знаке.
// и % связаны: для любых целых a и b (кроме нуля) верно a == (a // b) * b + a % b. Если частное
округлено вниз, остаток обязан быть того же знака, что делитель, - иначе равенство не сойдётся. Пример:
7 / -2 равно -3.5, вниз будет -4, и тогда 7 - (-4) · (-2) = 7 - 8 = -1 - остаток отрицательный,
как делитель. Что
такое остаток с точки зрения математики и почему он такой удобный, разберёт глава «Математика и логика
для курса».
Правило
/ всегда даёт дробное, // округляет частное к минус бесконечности, % берёт знак делителя, и a == (a // b)
* b + a % b верно всегда. Целые не переполняются. Переменная - имя, а не ячейка: присваивание
привязывает имя к значению, а правая часть вычисляется целиком ДО того, как что-то присвоено.
Разбор: обмен и шаг Фибоначчи в одну строку
В прошлой главе обмен двумя присваиваниями a = b; b = a испортил данные. В Python есть запись
a, b = b, a, и она работает. Причина в порядке: сначала правая часть вычисляется полностью и
превращается в пару значений (9, 4), и только потом имена a и b получают по элементу пары. Пока
правая часть считается, ни одно имя не тронуто.
Та же запись спасает шаг чисел Фибоначчи, где каждое следующее - сумма двух предыдущих:
a, b = 0, 1
for _ in range(6):
a, b = b, a + b
print(a, b)
a, b = 0, 1
for _ in range(6):
a = b
b = a + b
print(a, b)
8 13
32 64
Первый цикл даёт верную пару. Во втором b = a + b видит уже новое a, то есть складывает b с самим
собой, и вместо Фибоначчи получаются степени двойки. range(6) выдаёт числа от 0 до 5, то есть цикл
делает шесть кругов (подробно о range - в следующем уроке). Имя _ в for - соглашение: «переменная
цикла мне не нужна».
Разбор: ленивые and и or, сравнение цепочкой
a = []
i = 0
print(i < len(a) and a[i] > 0)
print(0 or "пусто", [] or [7], 3 and 5)
x = None
print(x is None, 0 <= 5 < 10, 1 < 3 > 2)
False
пусто [7] 5
True True True
Первая строка вывода показывает самое полезное свойство: and не вычисляет правую часть, если левая уже
ложна. len(a) - длина списка, здесь 0, так что i < len(a) ложно; a[0] упало бы с ошибкой, но до
него дело не дошло. На этом держится условие
сортировки вставками while i >= 0 and a[i] > klyuch - сначала проверка границы, потом обращение.
Поменяй их местами, и при i = -1 Python молча возьмёт ПОСЛЕДНИЙ элемент списка (отрицательные номера
считаются с конца - о них следующий урок).
Вторая строка: or и and возвращают не True/False, а один из операндов. Пустой список, ноль,
пустая строка и None считаются ложью. Третья: None - значение «ничего», его сравнивают через is;
0 <= 5 < 10 - сравнение цепочкой, оно значит 0 <= 5 and 5 < 10. Поэтому 1 < 3 > 2 истинно, хотя
читается странно.
Что ломается без этого
Программа, перенесённая с C, считает середину отрезка или номер ячейки через // с отрицательным
числом и получает соседнюю ячейку: -7 // 2 даёт -4. Проверка 0.1 + 0.2 == 0.3 ложна, и цикл «пока
сумма не станет равна единице» с шагом 0.1 не остановится никогда - дробные сравнивают с допуском, а
деньги считают в целых копейках. x == None обычно работает, но для объектов со своим сравнением может
соврать; x is None - нет. И i++ в Python нет вовсе: это ошибка синтаксиса, а вот ++i молча
разбирается как «плюс плюс i» и не меняет ничего. Счётчик увеличивают записью i += 1.
Зачем это в работе
На собеседовании задачу часто решают на Python, а сама задача пришла из книги на C или Java. Хеш по
модулю, середина отрезка, номер дня недели «три дня назад» - во всех этих местах // и % с
отрицательным числом дают в Python другой ответ, чем в оригинале. Зато здесь (день - 3) % 7 всегда
попадает в 0..6, и проверка «если меньше нуля, прибавь 7», без которой в C не обойтись, не нужна.
Целые без переполнения означают, что произведение двух больших чисел не нужно проверять на выход за
предел, - но и что оно стоит дороже с каждым разрядом.
Теперь сам
Что напечатает строка print(-1 // 3, -1 % 3, 10 // -3, 3 < 5 < 4)?
Ответ. -1 2 -4 False. -1 / 3 - это -0.33, округление вниз даёт -1. Остаток подбирается так,
чтобы -1 == -1 * 3 + 2, - то есть 2, со знаком делителя. 10 / -3 - это -3.33, вниз будет -4.
Цепочка 3 < 5 < 4 значит 3 < 5 and 5 < 4, а второе ложно.
Главное
В Python // округляет частное к минус бесконечности, а % согласован с ним, целые не переполняются, а переменная - это имя,
привязанное к значению. Самое частое место ошибки при переносе кода из другого языка - отрицательное
число в делении, и оно не падает, а молча даёт соседний ответ.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий