Все статьи по Python
25 янв. 2025 г. - 14 мин. чтения
Типы данных

Типы данных

Типы данных. Изменяемость данных

@ashtana

Штана Альберт Игоревич

Типы данных

Существуют разные способы представления данных в программах. Есть строки — наборы символов в кавычках вроде "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 по значку ▶.