Встроенные типы данных (их назначение, методы и стандартное поведение)

Встроенные типы данных (их назначение, методы и стандартное поведение)

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


Коллекции, последовательности

Коллекциями стандартно называются объекты, содержащие упорядоченный или неупорядоченный набор элементов разных типов, в том числе типов, одинаковых с типом самой коллекции (например: списки списков, кортежи кортежей и т.п.).


ВИДЫ КОЛЛЕКЦИЙ
NB!: строки также являются последовательностями

Последовательности - это упорядоченные коллекции, то есть элементы коллекции хранятся в строгом порядке. Такие коллекции поддерживают индексацию элементов и срезы.

Индекс - порядковый номер элемента в последовательности, начиная с нулевого (а не с первого, как нам было бы привычнее).

Срез - выборка элементов из последовательности в формате [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 раз подряд вы отпринтовали одно и то же множество, и оно у вас вывелось в одном и том же порядке, то рано или поздно оно предстанет в совершенно другом виде - это случится неизбежно. Если вы не будете этого учитывать, то ваш код рано или поздно сломается.

Report Page