Встроенные типы данных (их назначение, методы и стандартное поведение)
https://t.me/el_pythonistИзменяемые и неизменяемые типы данных
В пайтоне, как и в других высокоуровневых языках, существуют изменяемые (mutable) и неизменяемые (immutable) типы данных.
К стандартным встроенным неизменяемым типам относятся:
- None
- bool - True/False
- числа - int(), float(), complex()
- строки - str()
- кортежи - tuple()
- неизменяемые ("замороженные") множества - frozenset()
- байты - bytes()
К изменяемым:
- списки - list()
- множества - set()
- словари - dict()
- списки байтов - bytearray()
Что это значит простыми словами? Неизменяемый тип данных нельзя изменить без потери первоначального объекта. Изменяемый - можно менять
на лету без потери объекта.
Вспомним пример из вводной статьи про пятёрку:
>>> a = 5 >>> id(a) 1609562080
Заменяя пятёрку на любое другое число, мы теряем эту пятёрку, и переменная "а" начинает ссылаться на новое число, которое будет храниться в новой ячейке памяти:
>>> a = 6 >>> id(a) 1411840000
Тоже самое происходит с любым из объектов неизменяемого типа: если нам нужно изменить содержимое - мы либо создаём новый на основе старого без его изменения, либо неизбежно теряем старый. Как это происходит - мы увидим чуть ниже.
С изменяемыми в этом плане всё куда проще - объект остаётся тем же самым, чтобы мы с ним ни делали, лишь бы не удаляли полностью.
>>> lst = [1, 2, 3] # создаём список из трёх чисел >>> id(lst) # запрашиваем номер ячейки памяти, где он сохранён 49718408 # получаем ответ на запрос >>> lst # просим вывести переменную на печать
Прим.: в коде вывод на печать - только через print(), в консоли можно и так, и так: и через "print()", и через "переменная+Enter"
[1, 2, 3] # вывод на экран содержимого переменной "lst" >>> lst.clear() # очищаем список полностью, делая его пустым >>> lst [] # список опустел >>> id(lst) 49718408 # адрес ячейки не изменился - значит объект всё тот же
Т.е. этот список можно заполнять, опустошать, выдирать из него или вставлять в него элементы по одному или пачками - сам объект от этого не изменится,
мы его не потеряем и не создадим новый за счёт этих изменений.
Однако, есть нюанс: если всегда помнить, что переменные - это ссылки,
то нужно также понимать, что объекты нельзя копировать через назначение новой ссылки.
>>> a = 5 >>> b = a >>> a 5 >>> b 5 >>> id(a) 1407776752 >>> id(b) 1407776752 >>> b += 1 >>> a 5 >>> b 6 >>> id(a) 1407776752 >>> id(b) 1407776768
Из этого примера видно, что сначала обе переменные ссылаются на одну и ту же пятёрку. Затем мы к "b" прибавили единицу, и, о чудо, в ней оказалась шестёрка, а шестёрка - это новый объект, у которого своё место в памяти. При этом "a" никуда не делась, не изменилась и осталась ссылаться на пятёрку.
Подобный фокус с изменяемыми типами не пройдёт.
>>> lst = [1, 2, 3] >>> new_lst = lst >>> id(lst) 49718216 >>> id(new_lst) 49718216 >>> lst [1, 2, 3] >>> new_lst [1, 2, 3] >>> new_lst.clear() >>> new_lst [] >>> lst [] >>> id(lst) 49718216 >>> id(new_lst) 49718216
Попытка получить новый пустой список из старого привела к тому, что мы, во-первых, очистили старый и, во-вторых, не получили никакого нового. Обе переменные как ссылались на один и тот же объект - так и ссылаются.
Для копирования изменяемых типов существует встроенный модуль "copy".
Цитата из официальной документации модуля:
DESCRIPTION Interface summary: import copy x = copy.copy(y) # make a shallow copy of y (поверхностное копирование) x = copy.deepcopy(y) # make a deep copy of y (глубокое копирование вложенных структур)
Т.е. здесь уже по названию самих переменных понятно, что в результате создаются новые объекты.
И ещё разок: назначение нескольких переменных для одного объекта - это НЕ КОПИРОВАНИЕ, т.к. вследствие этих действий вы не создаёте новый объект. Копирование неизменяемых объектов в принципе невозможно, так как неизменяемый объект уникален, и даже применение модуля copy лишь создаст новую ссылку на этот объект.
>>> a = 5 >>> b = copy.copy(a) >>> a 5 >>> b 5 >>> id(a) 1403844592 >>> id(b) 1403844592
Копировать в чистом виде можно только изменяемые объекты.
>>> lst = [1,2,3] >>> new_lst = copy.copy(lst) >>> lst [1, 2, 3] >>> new_lst [1, 2, 3] >>> id(lst) 49634696 >>> id(new_lst) 49794312
Коллекции, последовательности
Коллекциями стандартно называются объекты, содержащие упорядоченный или неупорядоченный набор элементов разных типов, в том числе типов, одинаковых с типом самой коллекции (например: списки списков, кортежи кортежей и т.п.).
Последовательности - это упорядоченные коллекции, то есть элементы коллекции хранятся в строгом порядке. Такие коллекции поддерживают индексацию элементов и срезы.
Индекс - порядковый номер элемента в последовательности, начиная с нулевого (а не с первого, как нам было бы привычнее).
Срез - выборка элементов из последовательности в формате [start:stop:step], т.е.:
- стартовый индекс,
- конечный индекс (невключительно),
- шаг (необязательный параметр, по умолчанию равен единице).
Если задан только один параметр среза без двоеточий, то он по умолчанию считается просто порядковым индексом.
>>> s = "Hello, world!" # строки - разновидность последовательностей >>> s[0] # первый элемент строки 'H' >>> s[-1] # последний элемент строки '!' >>> s[-2] # предпоследний элемент строки 'd' >>> s[2:-2] # срез строки c ТРЕТЬЕГО (считаем от нуля) по предпоследний (НЕВКЛЮЧИТЕЛЬНО) элементы 'llo, worl' >>> s[1:-1:3] # срез строки cо ВТОРОГО (считаем от нуля) по последний (НЕВКЛЮЧИТЕЛЬНО) элементы с шагом "каждый третий (в человеческом понимании) элемент от старта, включая стартовый" 'eowl'
Если задан один параметр среза с двоеточиями, то в зависимости от его местоположения он может выступать стартом, стопом или шагом:
>>> s[2:] # Срез от третьего элемента до конца последовательности 'llo, world!' >>> s[:2] # Срез начала последовательности до третьего элемента невключительно 'He' >>> s[::2] # Срез последовательности с шагом "каждый второй элемент, включая первый" 'Hlo ol!'
Шаг может быть также отрицательным. Тогда последовательность обходится с конца (в обратном порядке).
>>> s[::-1] # Срез с шагом "каждый элемент в обратном порядке" '!dlrow ,olleH' >>> s[::-2] # Срез с шагом "каждый второй элемент в обратном порядке, начиная с последнего" '!lo olH'
Все остальные виды коллекций - неупорядоченные (исключение - OrderedDict из модуля collections, но в данном случае он нас не интересует) , то есть вызвать элемент по его индексу из множества или словаря невозможно, равно как и сделать их срез.
Кроме того, передавая неупорядоченную коллекцию в print не стоит полагаться на тот порядок, который перед вами возник. Даже если вдруг 10000 раз подряд вы отпринтовали одно и то же множество, и оно у вас вывелось в одном и том же порядке, то рано или поздно оно предстанет в совершенно другом виде - это случится неизбежно. Если вы не будете этого учитывать, то ваш код рано или поздно сломается.