# Глава 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
```
**Битовые операторы**
К целым числам в питоне применимы битовые операторы:
- `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`. Результат вычисления любого выражения в конечном итоге сводится к одному из них:
```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` — «И». В C++ и некоторых других языках этот оператор имеет вид `&&`.
- `or` — «ИЛИ». В C++ это `||`.
- `not` — «НЕ».
Проверка, что дата удовлетворяет условиям, заданным некоей бизнес-логикой:
```python
weekend = is_weekend(date)
if day > 20 and not weekend:
print("Good date for discounts!")
```
Напишите функцию `my_xor()`, которая принимает два флага и возвращает для них логический XOR. Например, `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
```
# Резюмируем
- Инкремент и декремент в питоне выглядят так: `i += 1`, `j -= 1`.
- Если требуется сравнить несколько значений между собой, используйте цепочки сравнений: `2 < x < 6`.
- В питоне нет ограничения на размер целого числа.
- Сравнивать значение с `None` нужно через оператор `is`.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!