Сайт вебмастера

Типы данных в Python

06-03-2023Время чтения ~ 10 мин.Python 1790

Понятие типов данных — основополагающее в программировании. Тип данных нужен для того, чтобы понять как именно нужно работать с ними. Например если это числа, то это будет арифметическое сложение. Но если это строки, то это уже будет конкатенация — объединение.

Также язык программирования должен уметь одновременно оперировать с разными типами данных. Например как поступать, если нужно сложить число и строку? В Python подобные вещи сделаны достаточно своеобразно, что отличает его от других языков программирования.

Понятие переменной

Условно можно сказать, что переменная — это некий ящик, на котором написано название. В этот ящик можно положить разные вещи. А чтобы вещь получить, достаточно обратиться к конкретному ящику по его имени.

На практике это выглядит очень просто:

a = 42 # переменная «a» хранит число 42
s = 'spam' # переменная «s» хранит строку «spam»
lst = ['mars', 'venus', 150] # список хранит массив срок и чисел

Читается так «имя переменной = данные», где знак «=» означает «присвоить», а не «равно», как в условиях.

Понятие переменной изначально означало некую область памяти в компьютере, где программа размещала свои данные. Для этого используется адрес начала области памяти и длину данных (либо он автоматом определялся). Таким образом присваивание переменной какого-то значения по сути означает заполнение области в памяти указанными данными (байтиками). Получение обратный процесс — по имени переменной получался адрес памяти, а потом эти байтики считывались до определенной длины.

Поэтому в программировании обычно считается, что переменная — это область памяти, где хранятся данные.

Абстрактная память

Во многих традиционных языках, например в C или Pascal, данные хранились в памяти и для них можно было получить реальный физический адрес. С этим связано много проблем. Помните как Мосс говорил об этом в Компьтерщиках?

В разных языках проблема решалась по разному, но в целом идея состояла в том, чтобы запретить программе напрямую обращаться к физической памяти, а компилятор/транслятор будет сам решать что и где размещать. Соответственно в языках появились менеджеры памяти и сборщики «мусора». Таким образом, сейчас мы имеем в виду память в виде абстракции — хранилища произвольных данных.

Типируем

Для хранения данных всё-равно требуется знать тип данных. В первую очередь для того, чтобы понимать как именно хранить данные в памяти. Во вторую — какие операции могут быть разрешены с этими данными.

Указание типа данных может быть явным, как например в C, Pascal, Java и многие другие, а также может быть динамическим, то есть компилятор сам определит тип данных из контекста кода. К таким языкам относится Python, PHP, JavaScript и другие.

То есть когда мы присваиваем переменной какое-то значение, то Python в первую очередь будет определять тип данных и только потом будет решать как её хранить. Такой подход называется динамической типизацией. Для строк мы используем кавычки, а это признак типа string. Если это только числа, то это тип integer. Если это квадратные скобки, то скорее всего это список list.

В языках со статической типизацией требуется явно указать тип переменной, перед тем как её использовать. Если объявить один тип, а попытаться присвоить переменной значение другого типа, то компилятор просто не позволит этого сделать.

Динамическая типизация очень удобная вещь, но с ней связано несколько трудностей. Как например поступить, если мы попытаемся сложить число и строку?

Выполните:

a = 42 
s = 'spam'
r = a + s

Python вывалится с ошибкой «TypeError: unsupported operand type(s) for +: 'int' and 'str'». То есть когда происходят операции с несовместимыми типами, Python будет генерировать ошибку. Такое поведение называется сильной/строгой типизацией и Python как раз относится к таким языкам, поскольку он не может решить как именно нужно преобразовать данные, чтобы выполнить задачу.

В языках со слабой/нестрогой типизацией компилятор попытается самостоятельно выполнить преобразование данных, чтобы не возникло ошибки. Например в PHP при сложении числа со строкой, интерпретатор попробует вначале преобразовать либо строку в число, либо число в строку.

Таким образом Python относится к языкам с динамической строгой типизацией. Но это ещё не всё: в Python все типы являются объектными.

Понятие объекта

Для новичков это всегда сложно, потому что в других языках переменные — это всего лишь некие данные определенного типа, а в Python это объект в терминах объектно-ориентированного программирования (ООП).

Это накладывает отпечаток на работу с любыми переменными: такая неочевидность поведения составляет львиную долю вопросов на собеседованиях на вакансию Python-программиста.

Итак, в общих чертах, объект — это некая структура данных, в которой могут храниться поля и методы. Поля — это по сути переменные (данные), а методы — функции, как мы и привыкли, но все они доступны только в самом объекте.

Python оперирует стандартными типами данных, например integer, string, float, list и т.д., но на самом деле это объекты, которые встроены в сам язык. Поэтому когда мы задаём в переменной число, то речь идёт о том, что данная переменная будет наследником объекта int. Соответственно в этой переменной будут доступны все методы и поля данного объекта.

Чтобы посмотреть тип объекта в Python используется функция type(). Выполним:

a = 42 
s = 'spam'

print(type(2023)) # class 'int'
print(type(a)) # class 'int'
print(type(s)) # class 'str'

Такая организация данных позволяет автоматически добавлять к переменным функционал исходного объкта. Для примера посмотрим что есть в классе (объекте) str. Для этого воспользуемся функцией help():

help(str)

Мы увидим доступные поля и методы, которые могут быть применены к строковым переменным. Либо можно использовать функцию dir(), которая возвращает список доступных методов переменной:

print(dir(s))
'''
Нас интересуют только те, которые без начальных символов подчеркивания
['capitalize', 'casefold', 'center', 'count', 'encode', 
'endswith', 'expandtabs', 'find', 'format', 'format_map', 
'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 
'isdigit', 'isidentifier', 'islower', 'isnumeric', 
'isprintable', 'isspace', 'istitle', 'isupper', 'join', 
'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 
'removeprefix', 'removesuffix', 'replace', 'rfind', 
'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 
'split', 'splitlines', 'startswith', 'strip', 'swapcase', 
'title', 'translate', 'upper', 'zfill']
'''

В других языках, чтобы работать с переменной, необходимо использовать отдельные функции (например подключить модуль). В Python другой подход — здесь переменная уже является объектом, который сразу же предоставляет многочисленные методы.

Если вы работаете в Visual Studio Code, то как только вы наберёте точку после имени переменной, то редактор сразу высветит подсказку из доступных методов:

Подсказка в Visual Studio Code

Поэтому нужно усвоить, что в Python переменная — не просто набор данных заданного типа, а объект определённого типа/класса. Это настолько всеобъемлющая вещь для Python, что затрагивает тотально всё, что в нём есть. Например любая функция — это объект класса function, или многоточие print(type(...)) это класс ellipsis. Для программистов других языков такие вещи несколько необычны.

Какие типы есть в Python

Объекты строятся по структурам, которые называются классами (class), где класс — это тип данных. Поэтому, если говорить строго, то Python оперирует типами данных, как и любые другие языки. Разница в том, что в других языках в типы данных заложена больше техническая информация: как и где хранить переменную, а в Python — это полноценный объект ООП.

Поэтому, когда мы говорим о типе переменной, то фактически это означает класс, которому она принадлежит.

  • int — целое число, например 10. Могут быть в десятичной (int), двоичной (bin), шестнадцатеричной (hex) и восьмеричной (oct) системе исчисления.
  • str — строка, которая заключается в кавычки, например 'hello'.
  • float — число с плавающей точкой (именно точка, а не запятая), например 3.14.
  • bool — логическое значение: где 0 это False, 1 — True (в сравнении всё что не равно нулю).
  • complex — комплексные числа (для математики), например 3 + 7j.
  • None — специальное значение переменной, когда «нет значения» или оно не определено.

Кроме этого есть ещё массивы:

  • list — список
  • tuple — кортеж
  • dict — словарь
  • set и frozenset — множества

Числа, строки в общем-то интуитивно понятны. Булевый тип ( bool ) напрямую наследуется от int и по сути является числом. То есть в Python можно написать так:

print(True + 100 - 50/True) # 51.0

Если не знать этой особенности (True равен 1), то такой код может загнать в ступор.

Массивы в Python имеют особенности, которые делают их непохожими на другие языки. При изучения обратите на них особое внимание.

Итерируемые типы данных

Для примера рассмотрим любую строку. Нам кажется, что она представляет собой единое целое, но на самом деле строки состоят из символов. В других языках для символов есть отдельный тип данных, например char в Паскале, но в Python ничего подобного нет.

Или другой пример — массивы — очевидно, что они состоят из набора отдельных элементов. Поэтому чтобы к ним обратиться мы используем индекс смещения.

p = 'spam'
k = [42, 54, 23]

print(p[3]) # m
print(k[2]) # 23

Python оперирует понятием «Типы последовательностей» (Sequence Types), где данные представляют собой последовательные элементы, которые можно итерировать. Для этого в Python используется внутренний протокол, который позволяет понять как именно следует обходить данные. Это довольно сложная «кухня», но всё это для того, чтобы нам можно было использовать очень простой код в цикле for (см. Что такое цикл в Python):

for s in 'Hello':
    print(s)

В Python типы str, файлы и все массивы являются итерируемыми. При этом Python предлагает простое создание генераторов, а также несколько магических методов, которые позволяют сделать итератором свои классы.

Почему это важно? В программировании часто стоят задачи по обходу последовательностей. И здесь Python предлагает хорошие возможности.

Мутабельные и иммутабельные типы данных

Или по другому «изменяемые» и «неизменяемые» типы данных. Это особенность Python, которая может сбивать с толку.

Например у нас есть строка. Мы знаем, что строка состоит из символов. Получить символ можно по его смещению, поэтому предполагаем простой код:

s = 'spam'
s[2] = 'L'
# TypeError: 'str' object does not support item assignment

Python вываливается с ошибкой. А всё из-за того, что строки относятся к неизменяемому (иммутабельному) типу данных. Нужно запомнить, что почти все типы в Python иммутабельны. К изменяемым относятся только списки (list), словари (dict) и множества (set).

Поэтому если стоит задача изменить иммутабельный объект, то его придется вначале преобразовать в мутабельный, внести изменения, а потом обратно в исходный тип.

s = 'spam'
lst = list(s) 
lst[2] = 'L'
s = ''.join(lst)

Зачем такие сложности? Всё дело в оптимизации работы Python. Он при хранении данных использует кэширование — для каждого объект и его содержимого строится хэш. Если у двух объектов хэши одинаковые, то Python не будет создавать новый объект, а сделает так, чтобы они оба ссылались на одни и те же данные. Такой подход (запрет на изменение) позволяет экономить память и немного ускорить выполнение программы.

С этой особенностью связано много тонкостей и нюансов, но это отдельная тема урока. Поскольку мы всё равно не можем изменить поведение Python, нужно просто запомнить какие типы могут изменяться и использовать их в работе.

Преобразование типов данных (приведение типов)

Типы могут быть совместимы между собой. Например integer и float — числа, которые можно использовать между собой.

q = 10
t = 2.5
r = q * t
print(r) #25.0

Когда встречаются два этих типа, Python будет их неявно преобразовывать во float. Рассмотрите эти примеры:

print(10*2) # 20 — здесь два integer 
print(10*2.0) # 20.0 — здеcь integer и float
print(10/2) # 5.0 — результат деления всегда float

Но если типы несовместимы, например числа и строки, то для их преобразования нужно будет воспользоваться специальными функциями приведения типов. В основном это int(), float(), str().

Функция int() используется для получения числа из строки. Например, когда пользователь вводит данные, то это всегда строка.

s = input('Введите номер раздела: ')
n = int(s)

Аналогично, если нужна работа с десятичными дробями, используется float():

f = float(input('Введите первое число: '))

Очевидно, что исходная строка должна содержать корректное число. Если в строке будут недопустимые для чисел символы, то Python вывалится с ошибкой. Чтобы не допустить этого используется перехват исключений.

Для преобразования чисел в строку используется str().

n = 200
print(str(n)) # '200'

Поскольку любое число может быть корректно преобразовано в строку, то никаких ошибок никогда не произойдёт.

Автоматическое приведение типов может служить источником проблем. Например в ключах словаря dict:

d = {True: 'yes', 1: 'no', 1.0: 'spam'}
print(d) # {True: 'spam'}

Такие вещи сложно отлаживать, поэтому лучше придерживаться правил написания кода и не использовать неочевидные подходы, даже если Python это позволяет.

Похожие записи