Перечислимые типы (iterable)

Базовые перечислимые типы

Тип Название Объявление Порядок
list Список
[1, 2, 1], ["a", "b", "a"], []
Да
tuple Кортеж
(1, 2, 1), ("a", "b", "a"), ("a",)
Да
set Набор
{1, 3, 2}, {"a","c","b"}, set()
Нет
dict Словарь
{"a":1, "c":2, "b":1}, {}
Нет

Перечислимые типы являются изменяемыми (mutable). При присвоении переменные этих типов не копируются.

Типы tuple, str и bytes имеют промежуточный статус. Они является неизменяемыми, но переменные этих типов можно перечислять.

Особенности перечислимых типов

  • Элементы перечислимых типов могут быть произвольного, в том числе перечислимого типа:
data = [5,7,9,12,"test",True,["ab","cd","ef"]]
  • Списки и кортежи сохраняют порядок, а наборы и словари - нет:
data_list = [5,4,3,6,2,1]
data_set = {5,4,3,6,2,1}
print(data_list) # [5, 4, 3, 6, 2, 1]
print(data_set) # {1, 2, 3, 4, 5, 6}
  • Переменные перечислимых типов (как и все составных) при присвоении не копируются:
data = [5,7,9,12]
double_data = data
double_data[1] = 42
print(data) # [5, 42, 9, 12]
  • Переменные перечислимых типов можно перечислять.

Итерационный цикл for

Цикл for используется для перебора элементов в переменных перечислимых типов.

Синтаксис:

for <новая переменная> in <переменная перечислимого типа> :
␣␣␣␣<оператор>
␣␣␣␣<оператор>

Примеры:

data = ['a', 'b', 'c', 'd']
for x in data:
    print(x)

Список: list

  • Используется в качестве замены массивам и векторам
  • Может иметь произвольную длину
  • Его можно удлинять и укорачивать

Индексирование списков

Индексирование в Python выполняется с 0.

data = ['a', 'b', 'c', 'd']
data[0] # 'a'
data[3] # 'd'
data[4] # Ошибка: выход за пределы списка
# Отрицательные индексы нумеруют список с конца и с -1
data[-1] # 'd'
data[-4] # 'a'
# Длина
x = len(data) # x == 4
# Поиск по значению
x = data.index('c') # x == 2

Срезы списков

Срезы (slices) — это способ получать из списка его часть не прибегая к циклам.

Синтаксис:

<индекс первого элемента>:<индекс последнего элемента +1>

<индекс первого элемента>:<индекс последнего элемента +1>:<шаг>

data = ['a', 'b', 'c', 'd']
data[1:3] # ['b', 'c']
data[2:] # ['c', 'd']
data[:] # ['a', 'b', 'c', 'd']
data[-2:] # ['c', 'd']
data[1:4:2] # ['b', 'd']

Срезы создают копию списка.

data = ['a', 'b', 'c', 'd']

# нет копирования temp и data это один список с двумя именами
temp = data
temp[0] = 'z' # data[0] == 'z'

# есть копирование temp и data это разные списки
temp = data[:]
temp[0] = 'z' # data[0] == 'a'

Модификация списков

Изменения в самом списке:

data = ['a', 'b', 'c', 'd']
data[1] = 42 # data == ['a', 42, 'c', 'd']
del data[1] # data == ['a', 'c', 'd']
data.insert(0,42) # data == [42, 'a', 'c', 'd']
data.append(42) # data == [42, 'a', 'c', 'd', 42]
data = data + [1,2,3]
# data == [42, 'a', 'c', 'd', 42, 1, 2, 3]
v = data.pop()
# v == 3, data == [42, 'a', 'c', 'd', 42, 1, 2] 
v = data.pop(0)
# v == 42, data == ['a', 'c', 'd', 42, 1, 2]

Обход списков

data = ['a', 'b', 'c', 'd']
for v in data:
    print(v) # a b c d
for i,v in enumerate(data):
    print(i,v) # 0 a 1 b 2 c 3 d

Если элемент списка неизменяемого типа, то при присвоение переменной созданной в цикле for (в примере, v) нового значение не меняет содержание массива.

i = 0
while i < len(data):
    data[i] = 42
    i += 1
    
for i in range(0,len(data)):
    data[i] = 42

Явное изменение списка.

Кортеж: tuple

Кортеж это неизменяемый список.

tple = (1, 2, 3)
tple[0] # 1
tple[0] = 0 # Ошибка
tple.append(0) # Ошибка

Набор: set

Набор не сохраняет порядок. Все элементы набора различны.

vset = {4, 3, 2, 3, 1}
print(vset) # {1, 2, 3, 4}
vset[0] # Ошибка
vset.add(0) # {0, 1, 2, 3, 4}
vset.add(4) # {0, 1, 2, 3, 4}
vset.remove(4) # {0, 1, 2, 3}

Типичное применение — удаление дубликатов из списка:

data = [1, 1, 2, 2, 2]
data = list(set(data)) # [1, 2]

Словарь: dict

  • Набор пар ключ-значение. Ключи всегда различны.
  • Значения могут быть любого типа, ключи — с некоторыми ограничениями.
data = {"a":1, "b":2, "c":3}
data["b"] # 2

Модификация словарей

data = {"a":1, "b":2, "c":3}
data["b"] = 3 # data == {"a":1, "b":3, "c":3}
del data["b"] # data == {"a":1, "c":3}
data["f"] = 99 # data == {'a': 1, 'f': 99, 'c': 3}
x = len(data) # x == 3
data.update({"a":99, "q":64})
# data == {'a': 99, 'f': 99, 'q': 64, 'c': 3}

Обход словарей

data = {"a":1, "b":2, "c":3}

for k in data:
    print(k) # a c b
    print(data[k]) # 1 3 2

for k in sorted(data.keys()):
    print(k,data[k]) # a 1 b 2 c 3

Копирование переменных составных типов

При присвоении переменные составных типов (и в частности перечислимых) не копируются:

data = {"a":1, "b":2, "c":3}
new_data = data
new_data["a"] = 42
print(data["a"]) # 42 !!!

Копирование переменных составных типов

Для их копирования используются срезы и методы copy и deepcopy из модуля copy:

import copy

data = {"a":1, "b":2, "c":3}
new_data = copy.copy(data) # Поверхностная копия
new_data["a"] = 42
print(data["a"]) # 1

data = {"a":1, "b":[2,3,4], "c":3}
new_data = copy.deepcopy(data) # Полная копия
new_data["b"][0] = 42
print(data["b"]) # [2,3,4]

Сортировка списков

adata = [5,4,2,1,4]
# Сортировка с копированием
bdata = sorted(adata) # bdata == [1, 2, 4, 4, 5]
# Сортировка на месте (in-place)
adata.sort() # adata == [1, 2, 4, 4, 5]
# Обратная сортировка
bdata = sorted(adata, reverse=True) #[5, 4, 4, 2, 1]
adata.sort(reverse=True) #[5, 4, 4, 2, 1]

Приведение перечислимых типов

Исходный тип data
Целевой тип list tuple set dict
list list(data) list(data) list(data.keys()), list(data.values())
tuple tuple(data) tuple(data) tuple(data.keys()), tuple(data.values())
set set(data) set(data) set(data.keys()), set(data.values())
dict dict(zip(keys,data)) dict(zip(keys,data)) dict(zip(keys,data))