Главная / Курсы / Python / Скалярные типы данных
# Глава 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. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!