
Типы данных
Типы данных. Изменяемость данных
Штана Альберт Игоревич
Типы данных
Существуют разные способы представления данных в программах. Есть строки — наборы символов в кавычках вроде "Hello World!". Есть целые числа — например, 9, -100, 0. Это две разные категории информации — два разных типа данных. Операция умножения имеет смысл для категории «целые числа», но не для категории «строки»: нет смысла умножать одно слово на другое. Тип данных определяет, что можно делать с элементами конкретного множества информации.
Примитивные типы данных
Язык программирования распознает типы данных, поэтому Python не позволит нам умножать строку на строку — нельзя умножать текст на текст. При этом можно умножать целое число на другое целое число. Наличие типов и таких ограничений в языке защищает программы от случайных ошибок. В отличие от строк, числа заключать в кавычки не нужно. Чтобы напечатать число 5, достаточно написать:
print(9) # => 9
Число 9 и строка '9' — совершенно разные вещи, хотя вывод у print() для этих данных идентичный. В Python целые числа (10, 35, -18 и т.д.) и рациональные числа (1.2, 1.0, -12.324 и т.д.) — это два отдельных типа данных. Это разделение существует, несмотря на то, что и целые, и рациональные числа считаются вещественными числами. Такое разделение связано с особенностями устройства компьютеров. Есть и другие типы, о них позже. Вот еще один пример, но уже с рациональным числом:
print(1.234) # => 1.234
Типы данных «строка», «целое число» и «рациональное число» — это примитивные типы, то есть они встроены в сам язык Python. В язык встроены также и некоторые составные типы. Программисты также могут создавать собственные типы данных. По-английски строки в программировании называются strings, а строчки текстовых файлов — lines. Например, в коде выше есть строчка (line), но нет никаких строк (strings). В русском иногда может быть путаница, поэтому будем говорить строка для обозначения типа данных «строка» и строчка для обозначения строчек кода (lines) в файлах. Python — один из языков, который строго относится к типам данных. Поэтому на любую несовместимость типов он ответит ошибкой. Все дело в сильной типизации.
Сильная типизация
Нам известно про два типа данных: числа и строки. Например, мы могли складывать числа, потому что операция сложения — это операция для типа «числа». А что, если применить эту операцию не к двум числам, а к числу и строке?
print(7 + '7') # TypeError: unsupported operand type(s)...
Python не разрешит сложить число 7 и строку '7', потому что это значения разных типов. Нужно сначала либо сделать строку числом, либо число строкой. Такое отношение к совместимости типов называется сильной типизацией. Python — язык с сильной типизацией. Не все языки так делают. Например, JavaScript — это язык со слабой типизацией. Он знает о существовании разных типов, но относится к их использованию не очень строго. JavaScript пытается преобразовывать информацию, когда это кажется разумным. То же самое это относится к некоторым другим языкам, например, к PHP.
// Число 1 + Строка 7 = Строка 17
1 + '7'; // '17'
Есть также языки со строгой типизацией, это когда тип данных нужно указывать при объявлении переменной, к ним относятся: C++, C#, Java и др. С одной стороны, автоматическое неявное преобразование типов и правда кажется удобным. Но на практике это свойство языка создает множество ошибок и проблем, которые трудно найти. Код может иногда работать, а иногда не работать — в зависимости от того, «повезло» ли с автоматическим преобразованием. Программист это заметит не сразу и потратит много времени на отладку.
Неизменяемость примитивных типов
Представим, что нам нужно изменить символ в строке. Вот, что получится:
name = 'Ваня'
name[2] = 'р'
# Ошибка: TypeError: 'str' object does not support item assignment
Такое происходит из-за неизменяемости примитивных типов в Python — язык не дает никакой физической возможности поменять строку. Неизменяемость примитивных типов важна по многим причинам. Ключевая причина — производительность. Но иногда нам нужно изменить строку. Для этого и существуют переменные:
name = 'Ваня'
name = 'Варя'
print(name)
Есть большая разница между изменением значения переменной и изменением самого значения. Примитивные типы в Python поменять нельзя, а составные — можно. Также можно без проблем заменить значение переменной.
Явное преобразование типов
В программировании регулярно встречаются задачи, когда один тип данных нужно преобразовать в другой — например, при работе с формами на сайтах. Данные формы всегда приходят в текстовом виде, даже если значение — число. Вот как его можно преобразовать:
# str в int
number = int('123')
print(number) # => 123
int() — это функция, в которую передается значение, чтобы его преобразовать. Функция ведет себя подобно арифметическим операциям, но выполняет особые действия. Вот еще несколько примеров:
v = '0'
# Внутри скобок можно указывать переменную
v1 = int(v)
print(v1) # => 0
# Или конкретное значение
v2 = int('10')
print(v2) # => 10
v3 = int(False)
print(v3) # => 0
v4 = int(True)
print(v4) # => 1
# Если преобразуется число с плавающей точкой, то отбрасывается вся дробная часть
v5 = int(3.5)
print(v5) # => 3
Точно так же можно преобразовать данные в строки str() и число с плавающей точкой float():
v1 = str(10)
v2 = str(True)
v3 = float(5)
print(v, v2, v3) # '10' True 5.0
Некоторые преобразования Python выполняет автоматически. Например, в операциях, где встречаются одновременно целое число и число с плавающей точкой. Python автоматически все приводит к float — числу с плавающей точкой:
# Неявно выполняется код float(6) + 1.2
v = 5 + 1.2
print(v) # => 6.2
Числа с плавающей точкой
В математике существуют разные виды чисел. Например, натуральные — целые числа от одного и больше или рациональные — числа с точкой, например 0.3. С точки зрения устройства компьютеров, между этими видами чисел — пропасть. Например, нам легко определить, сколько будет 0.1 + 0.2. А теперь посмотрим, что на это скажет Python:
0.1 + 0.2 # 0.30000000000000004
Сложение двух рациональных чисел внезапно привело к неточному вычислению результата. Это объясняется ограничениями вычислительных мощностей. Объем памяти конечен, в отличие от чисел. Бесконечное количество чисел потребовало бы бесконечного количества памяти. Рациональные числа не выстроены в непрерывную цепочку — между 0.1 и 0.2 бесконечное множество чисел. Как тогда хранить рациональные числа? В интернете много статей об организации памяти в таких случаях. Более того, существует стандарт, в котором описано, как это делать правильно. На этот стандарт опирается большинство языков. Разработчикам важно понимать, что операции над числами с плавающей точкой неточны, но эту точность можно регулировать при работе с конкретными задачами.
Неизменяемость и примитивные типы
Иногда разработчикам нужно вернуть положительное значение числа, которое задано. Для этого в Python есть специальная функция. Функция abs() делает число неотрицательным:
balance = -100
amount = abs(balance)
print(amount) # => 100
На экран выведется 100. Но если вызвать print(balance), то на экран выведется старое значение: -100. Функция abs() вернула новые данные, но не изменила переданные в нее. Она не могла это сделать, потому что примитивные типы в Python — неизменяемы. Напомним, что примитивные типы — это простые типы данных, которые встроены в сам язык программирования. Например, число или строка. Никакие функции не смогут изменять данные примитивных типов. Число -100 — это значение переменной balance, и само число нельзя изменить. Но переменная называется переменной, потому что ее значение можно заменить на другое. То есть мы можем написать:
balance = -100
balance = abs(balance)
print(balance)
Сначала в переменную записывается одно значение, а потом в ту же переменную вместо предыдущего значения записывается новое — то, что вернет вызов abs(balance). Строку balance = abs(balance) можно прочитать так: «записать в переменную balance то, что вернет вызов функции abs(), если передать в нее текущее значение переменной balance». Мы не изменили число, но изменили переменную — записали в нее новое число вместо старого. Изменение значения уже существующей переменной может показаться безобидным действием. Но в реальных программах перезаписывание переменной иногда становится источником проблем. Код с изменяемыми переменными сложно понимать и анализировать. Никогда нельзя быть уверенным, какое значение будет у переменной в определенный момент времени. Наверняка, вы регулярно сталкиваетесь с багами и ошибками в приложениях. Многие из них появляются, потому что изменили значения переменных. Такие ошибки сложно найти и исправить. Единственное место, где без изменения значений переменных никак — это циклы. Во всех остальных местах относитесь к переменным как к константам — неизменяемым сущностям. Создавайте переменные, задавайте им значения и больше не меняйте без необходимости.
Попробуйте сами запустить код в окне ниже с интерпретатором Python и повторите примеры из статьи чтобы самим увидеть и понять как всё это работает. Для этого в ячейке с кодом нажмите клавиши на клавиатуре Shift+Enter или запустите код через кнопку Run по значку ▶.