Python: структуры данных, срезы, генераторы

Списки, кортежи, словари и множества и когда что применять, срезы, генераторы списков и выражения-генераторы, распаковка, сортировка по ключу, модуль collections.

Питон ценят за то, что задача, требующая двадцати строк в C++, здесь решается одной. Разберём инструменты, которые это обеспечивают, — и правила выбора между ними.

Четыре базовых структуры

ТипИзменяемыйПорядокДубликатыПоискКогда применять
listДаЕстьДаO(n)Последовательность, нужен индекс
tupleНетЕстьДаO(n)Неизменяемая запись, ключ словаря
dictДаПорядок вставкиКлючи уникальныO(1)Соответствие ключ-значение
setДаНетНетO(1)Уникальность, операции над множествами
nums = [4, 8, 15, 16, 23, 42]          # список
point = (10.5, 20.3)                    # кортеж — нельзя изменить
grades = {'математика': 5, 'физика': 4} # словарь
unique = {1, 2, 3}                      # множество

# Кортеж как ключ словаря — список так использовать нельзя
cache = {(1, 2): 'результат', (3, 4): 'другой'}

# Операции над множествами
a, b = {1, 2, 3, 4}, {3, 4, 5}
print(a & b)   # {3, 4}      пересечение
print(a | b)   # {1,2,3,4,5} объединение
print(a - b)   # {1, 2}      разность
print(a ^ b)   # {1,2,5}     симметрическая разность
Проверка «есть ли элемент» в списке из миллиона значений занимает миллион сравнений, в множестве — одно. Замена list на set в этом месте — самая частая и самая дешёвая оптимизация в учебных программах.

Срезы

  последовательность[начало:конец:шаг]

  начало включается, конец — нет
  отрицательные индексы считаются с конца

  a[2:5]    элементы 2, 3, 4
  a[:3]     первые три
  a[-3:]    последние три
  a[::2]    каждый второй
  a[::-1]   в обратном порядке
  a[:]      полная копия списка
s = 'алгоритмы'
print(s[:4])      # алго
print(s[::-1])    # ымтирогла

nums = list(range(10))
print(nums[2:8:2])   # [2, 4, 6]

# Срез создаёт новый список — исходный не меняется
copy = nums[:]
copy[0] = 99
print(nums[0])    # 0

# Присваивание в срез изменяет список на месте
nums[1:3] = [100, 200, 300]   # длина может измениться

Генераторы списков

# Вместо цикла с append
squares = [x**2 for x in range(10)]

# С условием
evens = [x for x in range(20) if x % 2 == 0]

# С преобразованием и условием
names = ['иванов', 'петров', 'сидоров']
short = [n.capitalize() for n in names if len(n) <= 6]

# Вложенные циклы: порядок такой же, как в обычной записи
pairs = [(i, j) for i in range(3) for j in range(3) if i != j]

# Матрица и её транспонирование
m = [[1, 2, 3], [4, 5, 6]]
t = [[row[i] for row in m] for i in range(len(m[0]))]

# Словарь и множество тем же способом
sq_dict = {x: x**2 for x in range(5)}
lengths = {len(n) for n in names}

Выражения-генераторы

# Квадратные скобки создают весь список в памяти
total = sum([x**2 for x in range(10_000_000)])   # ~400 МБ

# Круглые — значения вычисляются по одному
total = sum(x**2 for x in range(10_000_000))     # память не растёт

# Своя функция-генератор
def read_large_file(path):
    with open(path, encoding='utf-8') as f:
        for line in f:
            yield line.rstrip()

# Файл на гигабайт обрабатывается без загрузки в память
for line in read_large_file('log.txt'):
    if 'ERROR' in line:
        print(line)

# Генератор одноразовый: после обхода он исчерпан
gen = (x for x in range(3))
print(list(gen))   # [0, 1, 2]
print(list(gen))   # []  — уже пусто
Ключевое слово yield превращает функцию в генератор: она не выполняется целиком, а приостанавливается на каждом значении. Это позволяет работать с данными, которые физически не поместятся в память, — и стоит отдельного упоминания в отчёте по курсовой.

Распаковка

a, b = 1, 2
a, b = b, a                    # обмен без временной переменной

first, *middle, last = [1, 2, 3, 4, 5]
print(middle)                  # [2, 3, 4]

# Обход словаря с распаковкой
for subject, grade in grades.items():
    print(f'{subject}: {grade}')

# Параллельный обход
names = ['А', 'Б', 'В']
scores = [90, 85, 78]
for name, score in zip(names, scores):
    print(name, score)

# С индексом
for i, name in enumerate(names, start=1):
    print(i, name)

# Передача в функцию
def area(width, height): return width * height
size = (3, 4)
print(area(*size))
params = {'width': 3, 'height': 4}
print(area(**params))

Сортировка

students = [
    {'name': 'Иванов', 'course': 3, 'grade': 4.5},
    {'name': 'Петров', 'course': 2, 'grade': 4.9},
    {'name': 'Сидоров', 'course': 3, 'grade': 3.8},
]

# По одному полю
by_grade = sorted(students, key=lambda s: s['grade'], reverse=True)

# По нескольким: сначала курс, внутри — оценка по убыванию
by_two = sorted(students, key=lambda s: (s['course'], -s['grade']))

# sort меняет список на месте и возвращает None
nums.sort()                    # правильно
# nums = nums.sort()           # ошибка: nums станет None

# Максимум по ключу
best = max(students, key=lambda s: s['grade'])

Модуль collections

from collections import Counter, defaultdict, deque, namedtuple

# Подсчёт частот
words = 'алгоритм анализ алгоритм данные анализ алгоритм'.split()
print(Counter(words).most_common(2))   # [('алгоритм', 3), ('анализ', 2)]

# Словарь с значением по умолчанию — не нужно проверять наличие ключа
groups = defaultdict(list)
for w in words:
    groups[len(w)].append(w)

# Двусторонняя очередь: O(1) с обоих концов
queue = deque([1, 2, 3])
queue.appendleft(0)
queue.pop()

# Именованный кортеж: читается как класс, весит как кортеж
Point = namedtuple('Point', 'x y')
p = Point(3, 4)
print(p.x, p.y)

Частые ошибки

Частые вопросы

Кортеж или список?

Кортеж, если набор значений не должен меняться: координаты, запись из базы, ключ словаря. Он немного быстрее и защищает от случайного изменения. В остальных случаях список.

Чем генератор отличается от списка?

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

Почему словарь ищет за O(1)?

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

Читайте также

Сделаем работу по этой теме

Опишите задачу — ответим в течение 15 минут в личных сообщениях ВКонтакте, назовём срок и цену. Предоплаты за оценку нет.

  • Оценка заявки бесплатно
  • Правки по замечаниям преподавателя
  • Работы по всем техническим и IT-дисциплинам

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

Написать