# Глава 9. Скалярные типы данных
К скалярным (простым, неделимым) встроенным типам данных относятся: числа (целые, дробные, комплексные), логические флаги (`True`, `False`) и тип `NoneType`, обозначающий «отсутствие значения». Рассмотрим каждый из них более подробно.
## Числа
Занимательный факт: размер целых чисел в питоне не ограничен. Точнее, ограничен лишь объемом свободной оперативки. Теперь вы знаете, какой язык выбрать, если попадется олимпиадная задачка на большие числа.
По умолчанию литералы целых чисел записываются в десятичной системе счисления: `month = 12`, `day = 31`. Чтобы явно задать другое основание, используются **префиксы:**
- `0b` — двоичные числа: `0b1101`.
- `0o` — восьмеричные числа: `0o732`.
- `0x` — шестнадцатеричные числа: `0xAFF9`.
Создайте переменную `hex_val`, которая соответствует значению 16 в десятеричной системе счисления, но записано в шестнадцатеричной системе счисления. {.task_text}
```python {.task_source #python_chapter_0090_task_0010}
```
Для представления числа в шестнадцатеричной системе не забудьте воспользоваться соответствующим префиксом. {.task_hint}
```python {.task_answer}
hex_val = 0x10
```
Наиболее популярные **операторы для работы с числами:**
- `a = b` — присваивание.
- `a + b` — сложение.
- `a - b` — вычитание.
- `a += 1` — инкремент. Вместо `1` может быть любое число. Упрощенного варианта инкремента (`a++`) и декремента (`a--`) в питоне **нет.**
- `a -= 1` — декремент.
- `a * b` — умножение.
- `a / b` — деление.
- `a // b` — целочисленное деление.
- `a % b` — остаток от деления.
- `a ** b`, `pow(a, b)` — возведение в степень.
- `-a` — изменение знака. Запись `+a` тоже допустима, по сути ничего не делает, и применяется в редких случаях для улучшения восприятия формул.
Помимо инкремента и декремента, существуют и другие комбинированные операторы присваивания. Например `a *= b`, `a /= b`.
Наиболее популярные встроенные **функции для работы с числами:**
- `abs(a)` — модуль числа.
- `round(a, b)` — округление значения `a`. Если задан второй аргумент — то до заданного в нем количества знаков после запятой. Если аргумент не задан, то до ближайшего целого.
- `divmod(a, b)` — возвращает два числа: частное и остаток от деления.
- `bin(val)`, `oct(val)`, `hex(val)` — перевод числа в заданную систему счисления.
- `int(s, base)` — конвертация строки в целое число в системе счисления с основанием `base` (если `base` не указан, подразумевается десятичная система). Если преобразование не удается, генерируется исключение `ValueError`.
Напишите функцию `to_fahrenheit()`. Она должна принимать единственный аргумент: `celsius` градусов Цельсия и возвращать соответствующее значение по шкале Фаренгейта. {.task_text}
Формула: °F = (°C × 9/5) + 32. {.task_text}
```python {.task_source #python_chapter_0090_task_0020}
```
Функция должна вернуть значение: `celsius * 9.0 / 5.0 + 32.0`. {.task_hint}
```python {.task_answer}
def to_fahrenheit(celsius):
return celsius * 9.0 / 5.0 + 32.0
```
Как и в большинстве других языков, в питоне числа сравниваются между собой через операторы `>`, `>=`, `<`, `<=`, `==`, `!=`.
Математический факт: комплексные числа, в отличие от вещественных, нельзя сравнивать между собой на «больше/меньше». Поэтому операторы `>`, `>=`, `<`, `<=` к комплексным числам (тип `complex`) **не применимы.**
**Цепочки сравнений**
Удобство использования математических операторов в питоне вышло на новый уровень благодаря цепочкам сравнений (comparison operator chaining). Они позволяют связывать множество сравнений в единую последовательность. Внутри нее условия неявно соединяются логическим `and`:
```python
if 0 < val <= 12:
print("...this value may be month")
```
Эта цепочка аналогична явному объединению с помощью `and`:
```python
if 0 < val and val <= 12:
print("...this value may be month")
```
Превратите условие внутри `if` в цепочку сравнений. {.task_text}
```python {.task_source #python_chapter_0090_task_0030}
x = 1999
if x < 2001 and x > 1900:
print("XX century!")
```
Вид цепочки сравнений: `min_val < x < max_val`. {.task_hint}
```python {.task_answer}
x = 1999
if 1900 < x < 2001:
print("XX century!")
```
Как видите, цепочки сравнений делают проверки более компактными и человекочитаемыми:
```python
lower_bound <= a < b < c <= upper_bound
```
Но несмотря на мнимую простоту, цепочки сравнений — настоящая россыпь подводных камней. Будьте внимательны и не попадайтесь в ловушки цепочек сравнения.
Ловушка **усложнение вместо упрощения** срабатывает, если цепочки сравнения запутывают код вместо того, чтобы упрощать.
Например, когда операторы в условии идут не от меньшего большему в формате `a < b <= c < d`, а перемешаны:
```python
if x < y > z:
pass
```
Согласитесь, в данном случае отказ от цепочки сравнений в пользу встроенной функции `max()` делает код понятнее:
```python
if max(x, z) < y:
pass
```
Ловушка **использование в сравнении непостоянного выражения** захлопывается, если в середину цепочки закрадывается выражение с побочными эффектами либо на одинаковых данных возвращающее разный результат. Дело в том, что цепочки сравнений не только делают код короче, но и неявно оптимизируют его.
Ожидаемо, что в этом примере функция `get_val()` вызовется дважды:
```python
if a < get_val() and get_val() <= b:
pass
```
Если же заменить его на цепочку сравнений, то `get_val()` будет вызван только один раз:
```python
if a < get_val() <= b:
pass
```
Помните это, если захотите переписать какое-то условие на цепочку сравнений.
Ловушка **нетранзитивные операторы** грозит при проверке, что три переменные имеют разные значения. Обратная задача (проверить, что переменные одинаковы) действительно идеально укладывается в цепочку сравнений:
```python
if x == y == z:
pass
```
Но сработает ли проверка на неравенство?
```python
if x != y != z:
pass
```
Фактически она является заменой для `x != y and y != z`. Но это условие ничего не говорит о том, как соотносятся между собой `x` и `z`. Поэтому такая проверка работать не будет. А все потому, что с математической точки зрения равенство считается транзитивным отношением, а неравенство — нет.
Перепишите эту функцию, чтобы она возвращала правильный результат: `True`, если все 3 переменные имеют разные значения (среди них нет одинаковых). Иначе `False`. {.task_text}
```python {.task_source #python_chapter_0090_task_0040}
def not_eq(a, b, c):
return a != b != c
```
Чтобы удостовериться, что все три переменные не равны между собой, их следует попарно сравнить: `a` и `b`, `a` и `c`, `b` и `c`. {.task_hint}
```python {.task_answer}
def not_eq(a, b, c):
return a != b and b != c and a != c
```
**Битовые операторы** {#block-bitwise}
К целым числам в питоне применимы битовые операторы:
- `a & b` — битовый «И»: `1 & 4` равен 0, `1 & 5` - соответственно 1.
- `a | b` — битовый «ИЛИ»: `1 | 4` дает 5.
- `~a` — битовый «НЕ». Инверсия битов числа: ~5 равен отрицательному числу -6 из-за особенностей представления целых чисел в памяти.
- `a ^ b` — битовый «исключающий ИЛИ» (XOR). `3 ^ 5` — это 6.
Для битовых операторов существуют комбинированные операторы присваивания: `|=`, `&=` и так далее. Например, так выглядит извлечение из значения `val` 4 и 5 битов по маске:
```python
val &= 0b110000
```
Также в питоне реализованы операторы побитового сдвига:
- `a << b` — сдвиг числа `a` влево на `b` битов. `5 << 1` — это 10.
- `a >> b` — сдвиг вправо. `5 >> 1` — это 2.
Напишите функцию `is_eq_abs(a, b, eps)`, проверяющую два числа с плавающей точкой `a` и `b` на равенство c точностью `eps` включительно. {.task_text}
Например, `is_eq_abs(37.001, 37.002, 0.1)` вернет `True`, а `is_eq_abs(37.001, 37.002, 1e-5)` вернет `False`. {.task_text}
```python {.task_source #python_chapter_0090_task_0050}
```
Модуль разности чисел должен быть меньше или равен эпсилон. {.task_hint}
```python {.task_answer}
def is_eq_abs(a, b, eps):
return abs(a - b) <= eps
```
Что же касается чисел с плавающей точкой, то в модуле `math` содержится масса полезных функций для проведения математических вычислений. Но об этом позже.
## Логические значения
В питоне есть два встроенных логических объекта: `True` и `False`. Это [синглтоны](https://ru.wikipedia.org/wiki/%D0%9E%D0%B4%D0%B8%D0%BD%D0%BE%D1%87%D0%BA%D0%B0_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), то есть во всей программе существует только один объект `True` и один объект `False`.
Результат вычисления любого логического выражения в конечном итоге сводится к `True` либо `False`:
```python
print(10 > 9) # True
print(2 == 3) # False
```
Это относится и к блоку `if/else`. Пример проверки числа на четность:
```python
if val % 2 == 0:
print("val is even")
else:
print("val is odd")
```
К логическим значениям применимы операторы:
- `and` — «И».
- `or` — «ИЛИ».
- `not` — «НЕ».
Например, так выглядит проверка, что дата удовлетворяет условиям, заданным некоей бизнес-логикой:
```python
weekend = is_weekend(date)
if day > 20 and not weekend:
print("Good date for discounts!")
```
Напишите функцию `my_xor()`, которая принимает два флага и возвращает для них логический [XOR.](https://ru.wikipedia.org/wiki/%D0%98%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B0%D1%8E%D1%89%D0%B5%D0%B5_%C2%AB%D0%B8%D0%BB%D0%B8%C2%BB) Например, `my_xor(True, False)` вернет `True`. {.task_text}
```python {.task_source #python_chapter_0090_task_0060}
```
XOR — это взаимоисключающее ИЛИ. Возвращает `True` только для различающихся аргументов. {.task_hint}
```python {.task_answer}
def my_xor(a, b):
return a != b
```
## NoneType
В питоне к типу `NoneType` принадлежит единственный на всю программу объект-синглтон `None`. По смыслу это очередное воплощение `null` из Java, C# и других языков. Обозначает отсутствие значения:
```python
discount = None
```
Пример небольшой функции-обертки над вызовом `int()` для случаев, если на вызывающей стороне вместо исключений хочется обрабатывать проверку на `None`:
```python {.example_for_playground}
def safe_get_int(x):
try:
return int(x)
except ValueError:
return None
print(safe_get_int("20"))
print(safe_get_int("here we get nothing"))
```
Для проверки на `None` используется ключевое слово `is`: {#block-compare}
```python
if x is None:
print("Got None for x")
```
```python
if db_conn is not None:
select_table_rows(db_conn)
```
Почему для сравнения с `None` мы используем `is`, а не `==`? `is` — ключевое слово, проверяющее, являются ли два объекта одним и тем же объектом в памяти. `is` сравнивает не значения, а id объектов. В принципе сравнение с `None` через `==` тоже сработает, но так делать не рекомендуется. Сравнение через `==` вернет предсказуемый результат для всех встроенных типов, но для пользовательских классов этот оператор может быть переопределен. В результате сравнение с `None` вернет не то, что вы ожидали, и вас ждут часы увлекательной отладки. Второй аргумент в пользу `is` — он банально быстрее, чем `==`. Поэтому возьмите за правило сравниваться с `None` через `is`.
Напишите функцию `left_shift(val, n)`, которая сдвигает `val` на `n` битов влево и возвращает результат. Но если `val` или `n` равны `None`, функция сразу возвращает `None`. {.task_text}
```python {.task_source #python_chapter_0090_task_0070}
```
Сдвиг влево реализуется как `val << n`. {.task_hint}
```python {.task_answer}
def left_shift(val, n):
if val is None or n is None:
return None
return val << n
```
## Оператор `is` и режим REPL
Оператор `is` может вести себя по-разному в интерактивной оболочке ([REPL](https://ru.wikipedia.org/wiki/Python#:~:text=%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BA%D0%BE%D0%B4%5D-,%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%B0%D0%BA%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9%20%D1%80%D0%B5%D0%B6%D0%B8%D0%BC,-%5B%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%7C)) и при запуске `.py`-файла.
Чтобы попасть в интерактивный режим, запустите в консоли `python` без указания имени файла и начните вводить команды построчно.
```python
x = 257
y = 257
print(x is y)
```
Если вы таким образом напечатаете этот пример кода, результатом выполнения скорее всего окажется `False`. А если вы сохраните этот код в файл `.py` и запустите его, то получите `True`.
Это не баг, а особенность реализации CPython. В интерактивном режиме каждая строка кода компилируется отдельно, поэтому оптимизатор не видит «картину целиком».
### Различия между `is` и `==` на примерах
Чтобы лучше понять разницу между `is` и `==`, рассмотрим конкретные примеры. Оператор `==` сравнивает _значения_ объектов, а `is` — их _идентификаторы_ (то есть проверяет, один ли это объект в памяти).
Взгляните на пример сравнения переменных с `None`:
```python {.example_for_playground}
a = None
b = None
print(a == b) # True: значения равны
print(a is b) # True: это один и тот же объект-синглтон
print(id(a) == id(b)) # True: id объектов совпадают
```
`None` — это синглтон, поэтому и `is`, и `==` возвращают `True`.
CPython заранее создаёт и кеширует целые числа в диапазоне от -5 до 256. Поэтому переменные, равные числам из этого диапазона, ссылаются на один и тот же объект. Конечно, это деталь реализации CPython, которая может меняться от версии к версии.
```python {.example_for_playground}
x = 256
y = 256
print(x == y) # True: значения равны
print(x is y) # True: один и тот же кешированный объект
print(id(x) == id(y)) # True: id совпадают
a = 257
b = 257
print(a == b) # True: значения равны
print(a is b) # False: за пределами кеша — разные объекты
print(id(a) == id(b)) # False: id разные
```
Однако внутри одного выражения CPython может оптимизировать и переиспользовать объекты даже для чисел вне диапазона кеширования:
```python {.example_for_playground}
# Внутри одного выражения — оптимизация
x = 1000
y = 1000
print(x is y) # Зависит от контекста: в файле в современных версиях True
# А если получить числа из разных выражений — должны быть разные объекты
def get_1000():
return 1000
a = get_1000()
b = get_1000()
print(a is b) # True: из-за оптимизаций, но может быть и False в интерактивно оболочке
print(a == b) # True: значения равны
print(id(a) == id(b)) # True: id одинаковые из-за оптимизаций
```
Итак, числа за пределами диапазона от -5 до 256 ведут себя по-разному в зависимости от контекста. Особенно ярко это проявляется на границах диапазона:
```python {.example_for_playground}
# Граница кеширования: -5 кешируется, -6 — нет
x = -5
y = -5
print(x is y) # True: -5 входит в диапазон кеширования
a = -6
b = -6
print(a is b) # В REPL: False, в файле: True (зависит от версии Python)
# Верхняя граница: 256 кешируется, 257 — нет
c = 256
d = 256
print(c is d) # True: 256 — верхняя граница кеша
e = 257
f = 257
print(e is f) # В REPL: False, в файле: True
```
Эти примеры наглядно показывают: результат сравнения чисел через `is` зависит от версии Python, контекста выполнения (REPL или файл) и конкретных числовых значений.
**Вывод:** используйте `is` только для сравнения с синглтонами (`None`, `True`, `False`). А для сравнения значений выбирайте оператор `==`.
Напишите функцию `safe_get_element(data, index)`, которая возвращает элемент коллекции `data` по индексу `index`. Если `data` или `index` равны `None`, функция должна вернуть `None`. {.task_text}
```python {.task_source #python_chapter_0090_task_0080}
```
Для проверки на `None` используйте оператор `is`. {.task_hint}
```python {.task_answer}
def safe_get_element(data, index):
if data is None or index is None:
return None
return data[index]
```
# Резюмируем
- Инкремент и декремент в питоне выглядят так: `i += 1`, `j -= 1`.
- Если требуется сравнить несколько значений между собой, используйте цепочки сравнений: `2 < x < 6`.
- В питоне нет ограничения на размер целого числа.
- Сравнивать значение с `None` нужно через оператор `is`.
- Оператор `is` сравнивает id объектов в памяти, а не их значения. Для сравнения значений используйте `==`.
- Не полагайтесь на `is` для сравнения неизменяемых типов (кортежей, строк, чисел) — результат зависит от оптимизаций интерпретатора.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!