Python: от данных до своей сводки Урок 4 из 56
Переменные и типы: почему «260» и 260 — разные вещи
Четвёртый урок курса по Python. Данные из чужого файла приходят текстом, и пока текст не стал числом, складывать его бессмысленно: «260» + «260» даёт 260260. Измерено и главное денежное правило: 0.1 + 0.2 не равно 0.3, а Decimal — равно. Плюс None, истинность строки «0» и разница между == и is.
Зачем это нужно
В первом уроке мы взяли числа у Всемирного банка, и они пришли готовыми. В жизни так бывает редко: чаще всего цифры приезжают строкой — из CSV, из формы, из чужого отчёта. И тут выясняется, что "260" и 260 для Python — разные вещи, а сложить их одинаково не получится.
Второе, ради чего написан урок: деньги нельзя считать так, как учат в школе. Почему — увидите в третьем блоке вывода.
Сразу целиком
Файл tipter.py. Запуск: python tipter.py из окружения.
Программа длинная — шестьдесят строк. Держать в голове нужно первую половину: строка в число, деньги в Decimal. Остальное (None, истинность, is) — то, что достаточно узнать в лицо и вернуться, когда встретится.
"""Урок 4: переменные и типы — на числах, которые пришли строкой.
Из CSV, из формы и из обычного текстового файла значения чаще всего приходят
текстом. Пока текст не превращён в число, складывать его бессмысленно:
сложение строк даёт не то, что ожидают.
"""
from decimal import Decimal
# Переменная — это имя для значения. Имя пишут маленькими буквами, слова
# разделяют подчёркиванием: так принято во всём Python.
price_text = "260"
count = 2
print("== одно и то же на вид, разное по сути")
print("строка:", price_text, type(price_text))
print("число: ", 260, type(260))
print("строка + строка:", price_text + price_text)
print("число + число: ", 260 + 260)
print()
print("== превращаем текст в число")
price = int(price_text)
print("int(\"260\"):", price, type(price))
print("с пробелами тоже:", int(" 260 "))
print("дробное:", float("260.5"), type(float("260.5")))
print("обратно в текст:", str(price) + " тенге")
print("сумма:", price * count, "тенге")
print()
print("== а вот это не превратится")
try:
int("260,5")
except ValueError as err:
print("int(\"260,5\") →", type(err).__name__, "—", err)
print()
print("== почему деньги не считают в float")
print("0.1 + 0.2 =", 0.1 + 0.2)
print("0.1 + 0.2 == 0.3 →", 0.1 + 0.2 == 0.3)
print("округлили до тиына:", round(0.1 + 0.2, 2))
print("с Decimal:", Decimal("0.1") + Decimal("0.2"))
print("Decimal сравнивается честно →", Decimal("0.1") + Decimal("0.2") == Decimal("0.3"))
print()
print("== да, нет и пусто")
print("bool(0): ", bool(0))
print("bool(260): ", bool(260))
print('bool(""): ', bool(""))
print('bool("0"): ', bool("0"), "← строка непустая, значит истина")
print("bool(None): ", bool(None))
print("None — это не ноль и не пустая строка:", None == 0, None == "")
print()
print("== сравнения и логика")
budget = 1000
print("хватает на две:", price * count <= budget)
print("хватает и не дороже 300 за штуку:", price * count <= budget and price <= 300)
print("или дёшево, или мало:", price <= 100 or count <= 2)
print("не дороже 300:", not price > 300)
print()
print("== == сравнивает значения, is — один ли это объект")
first = [260, 2]
second = [260, 2]
print("first == second:", first == second)
print("first is second:", first is second)
print("first is first: ", first is first)
nothing = None
print("правильная проверка на пусто: nothing is None →", nothing is None)
Вывод:
== одно и то же на вид, разное по сути
строка: 260 <class 'str'>
число: 260 <class 'int'>
строка + строка: 260260
число + число: 520
== превращаем текст в число
int("260"): 260 <class 'int'>
с пробелами тоже: 260
дробное: 260.5 <class 'float'>
обратно в текст: 260 тенге
сумма: 520 тенге
== а вот это не превратится
int("260,5") → ValueError — invalid literal for int() with base 10: '260,5'
== почему деньги не считают в float
0.1 + 0.2 = 0.30000000000000004
0.1 + 0.2 == 0.3 → False
округлили до тиына: 0.3
с Decimal: 0.3
Decimal сравнивается честно → True
== да, нет и пусто
bool(0): False
bool(260): True
bool(""): False
bool("0"): True ← строка непустая, значит истина
bool(None): False
None — это не ноль и не пустая строка: False False
== сравнения и логика
хватает на две: True
хватает и не дороже 300 за штуку: True
или дёшево, или мало: True
не дороже 300: True
== == сравнивает значения, is — один ли это объект
first == second: True
first is second: False
first is first: True
правильная проверка на пусто: nothing is None → True
Разбор
Переменная — имя для значения
price_text = "260"
count = 2
Знак = не «равно», а «присвоить»: справа значение, слева имя, под которым оно теперь известно. Имена пишут маленькими буквами, слова разделяют подчёркиванием — price_text, а не PriceText. Это не каприз: так пишет весь Python, и чужой код будет читаться привычно.
Тип — это то, что значение умеет
type() показывает, чем значение является:
строка: 260 <class 'str'>
число: 260 <class 'int'>
На экране они выглядят одинаково. А ведут себя по-разному: "260" + "260" даёт 260260 — строки склеились; 260 + 260 даёт 520.
Никакой ошибки Python при этом не сделал. Он не угадывает, чего вы хотели: у сложения строк есть смысл, и он этим смыслом воспользовался. Ошибку сделали бы вы, если бы не заметили.
Четыре типа, которых хватит надолго: int — целое, float — с плавающей точкой, str — строка, bool — истина или ложь. Пятое — None, и о нём ниже.
Превращения и их границы
int("260") даёт число. int(" 260 ") — тоже: пробелы по краям он терпит. А вот int("260,5") не сможет:
int("260,5") → ValueError — invalid literal for int() with base 10: '260,5'
Запятая в качестве десятичного разделителя — обычное дело в казахстанских и российских выгрузках, и Python об этом не знает. Такую строку сначала чинят (replace(",", ".")), потом превращают в float.
Обратное превращение — str(260) — понадобится, когда число надо приклеить к тексту. Хотя чаще для этого берут f-строку из прошлого урока.
Образ. Номер телефона, записанный на бумаге, и сам звонок. На бумаге это текст: его можно переписать, склеить с другим. Позвонить по нему нельзя, пока не наберёшь.
Почему деньги не считают в float
Самое важное в уроке:
0.1 + 0.2 = 0.30000000000000004
0.1 + 0.2 == 0.3 → False
Это не ошибка Python и не его особенность. float хранит число в двоичной системе, а 0.1 в двоичной — бесконечная дробь, как 1/3 в десятичной. Приходится округлять, и на третьем знаке после запятой вылезает хвост.
Для роста цен на графике это неважно. Для денег — важно: тиыны накапливаются, и сумма чека не сходится с итогом в кассе.
Два выхода, оба в выводе:
- округлять при показе:
round(0.1 + 0.2, 2)даёт0.3. Годится, когда считаете для человека, а не для бухгалтерии; - считать в
Decimal:Decimal("0.1") + Decimal("0.2")даёт ровно0.3, и сравнение возвращаетTrue. Обратите внимание:Decimalберут из строки, а не изfloat, — иначе хвост попадёт внутрь ещё до сложения.
True, False и None
bool — это два значения: True и False. Но истинность есть у всего, и вот здесь ловушка:
bool(0): False
bool(""): False
bool("0"): True ← строка непустая, значит истина
bool(None): False
Строка "0" — истина, потому что она не пуста. Данные, пришедшие текстом, лгут здесь особенно охотно: "0" из CSV в проверке if value: окажется истиной, хотя означает ноль.
None — отдельное значение, означающее «ничего нет». Это не ноль и не пустая строка: None == 0 даёт False. Оно понадобится, когда в данных пропуск — а пропуски в реальных таблицах есть всегда.
and, or, not
Три слова вместо значков — читается как фраза:
print("хватает и не дороже 300 за штуку:", price * count <= budget and price <= 300)
and — истина, когда истинны оба. or — когда хотя бы одно. not — переворачивает. Скобки ставьте, когда сомневаетесь: они бесплатны, а разбираться потом дорого.
== сравнивает значения, is — объекты
first == second: True
first is second: False
Два разных списка с одинаковым содержимым равны, но это не один и тот же объект. == спрашивает «одинаковое ли внутри», is — «это одна и та же вещь в памяти».
Практическое правило: is используют почти исключительно с None — if value is None:. Для всего остального ==. Если написать if value == None, оно чаще всего сработает, но так не пишут.
Карта урока
Скажите своими словами
Не подглядывая, ответьте вслух или на бумаге. Ответы — в конце урока.
- Почему
"260" + "260"не ошибка, хотя результат — не то, что нужно? - Что не так с
0.1 + 0.2и как считать деньги? - Почему
bool("0")— истина?
Разминка
Три коротких шага перед заданием: предсказать, дописать, починить. Ответы — в конце урока, но сначала ответьте сами.
1. Предскажите. Что напечатает эта строка? Обратите внимание, что + делает в двух случаях разное.
print(int("8") + 1, "8" + "1")
2. Заполните пропуск. Вместо ... подготовьте строку так, чтобы float её принял.
text = " 1,15 "
clean = ...
print(float(clean))
3. Почините. Программа говорит, что значения нет, хотя оно есть и равно нулю. Замените проверку на правильную.
value = 0.0
print("значения нет:", not value)
Задание
Обязательное. Дано:
first = " 1,15 "
second = "0,25"
third = None
Превратите первые два в числа: уберите пробелы, замените запятую на точку. Напечатайте каждое с его типом, потом отдельной строкой — пуст ли третий, потом — является ли ноль пропуском, и в конце сумму с двумя знаками и среднее с тремя.
Ожидаемый вывод:
первый: 1.15 кг, тип float
второй: 0.25 кг, тип float
третий пуст: True
а ноль — это значение: False
всего 1.40 кг, в среднем 0.700 кг
Готово, когда: вывод совпадает построчно; пропуск проверяется через is None, а не через «пусто»; ни одна строка не превращается в число без strip и замены запятой.
На своих данных. Возьмите из чека три веса — их пишут дробными, и на них видно то, ради чего урок написан: "1,15", " 0,25 ", "0,2" (килограммы). Превратите каждый в число (двум потребуется починка), сложите и напечатайте сумму f-строкой с двумя знаками. Затем посчитайте ту же сумму через Decimal и сравните оба результата с помощью ==.
Напечатанные строки совпадут — обе покажут 1.60, — а == вернёт False. Это и есть ответ на вопрос, зачем деньги считают в Decimal.
По желанию.
- Проверьте:
0.1 + 0.2 + 0.3 == 0.6? Аround(0.1 + 0.2 + 0.3, 2) == 0.6? - Напечатайте
type()для5,5.0,"5",True,None— пять разных ответов. - Выясните на своём примере, чему равно
bool(" ")— строка из одного пробела.
Куда это встанет в проекте
Сводка получает первое правило работы с чужими данными: пришло текстом — приведи к числу и проверь, что получилось. Дальше мы соберём эти значения в список, а потом в таблицу.
Долги. Мы починили одну строку руками, но в файле их будут тысячи — это циклы. Мы ничего не сохранили — это списки и словари. И мы пока не умеем сказать «если не получилось превратить — пропусти строку»: это урок про исключения.
Ответы
Показать ответы
На вопросы
- Потому что у сложения строк есть свой смысл — склейка, — и Python им воспользовался. Он не угадывает намерение; следить за типами должен тот, кто пишет.
floatхранит числа в двоичной системе, где0.1— бесконечная дробь, поэтому появляется хвост0.30000000000000004. Для показа человеку хватаетround, для денег берутDecimal, создавая его из строки. И сравниватьfloatсDecimalчерез==— плохой договор: либо округляйте оба до нужного знака, либо считайте вDecimalс первого символа до последнего.- Потому что истинность строки определяется её длиной, а не содержимым:
"0"не пуста, значит истина. Пустая строка""— ложь.
К разминке
9 81. Слеваint("8")даёт число, и+складывает. Справа обе части — строки, и тот же+их склеивает. Знак один, действий два, и разницу задаёт тип.
9 81
text.strip().replace(",", "."). Пробелы по краямfloatещё простит, а запятую — нет:float("1,15")падает сValueError.
text = " 1,15 "
clean = text.strip().replace(",", ".")
print(float(clean))
1.15
not valueспрашивает «пусто ли», а пустыми Python считает и ноль, и пустую строку, и пустой список. Отсутствие значения — этоNone, и спрашивают о нём прямо:
value = 0.0
print("значения нет:", value is None)
значения нет: False
Источники
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.