Python предоставляет элегантные инструменты для работы с коллекциями. В этой статье -- продвинутые техники распаковки, сортировки и специализированные структуры данных из стандартной библиотеки.
Расширенная распаковка
# Star unpacking (PEP 3132)
first, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(rest) # [2, 3, 4, 5]
*init, last = [1, 2, 3, 4, 5]
print(init) # [1, 2, 3, 4]
print(last) # 5
first, *middle, last = [1, 2, 3, 4, 5]
print(middle) # [2, 3, 4]
# Nested unpacking
data = [("Иван", [85, 90, 78]), ("Мария", [92, 88, 95])]
for name, [first_score, *other_scores] in data:
print(f"{name}: первый балл {first_score}, остальные {other_scores}")
# Ignore values
_, _, third = (1, 2, 3)
print(third) # 3
# Unpack in function calls
def add(a, b, c):
return a + b + c
args = [1, 2, 3]
print(add(*args)) # 6
kwargs = {"a": 1, "b": 2, "c": 3}
print(add(**kwargs)) # 6
# Merge iterables with unpacking
list1 = [1, 2, 3]
list2 = [4, 5, 6]
merged = [*list1, *list2] # [1, 2, 3, 4, 5, 6]
merged_set = {*list1, *list2} # {1, 2, 3, 4, 5, 6}
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
merged_dict = {**dict1, **dict2} # {'a': 1, 'b': 2, 'c': 3, 'd': 4}
Walrus Operator (:=)
Оператор присваивания выражения (PEP 572, Python 3.8+):
# Read and check in one step
import re
text = "Контакт: +7 (999) 123-45-67"
if match := re.search(r'\d{3}-\d{2}-\d{2}', text):
print(f"Найден номер: {match.group()}")
# Filter with computation
data = [1, 5, 12, 3, 18, 7, 25]
large = [y for x in data if (y := x * 2) > 10]
print(large) # [24, 36, 14, 50]
# While loop with assignment
lines = []
while (line := input(">>> ")) != "quit":
lines.append(line)
# Avoid repeated expensive calls
import math
coords = [(1, 2), (3, 4), (10, 20), (5, 12)]
close_points = [
(x, y, dist)
for x, y in coords
if (dist := math.sqrt(x**2 + y**2)) < 15
]
print(close_points)
# [(1, 2, 2.236...), (3, 4, 5.0), (5, 12, 13.0)]
# Process file chunks
# with open("data.bin", "rb") as f:
# while chunk := f.read(8192):
# process(chunk)
sorted() и ключи сортировки
# Basic sorting
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(sorted(numbers)) # [1, 1, 2, 3, 4, 5, 6, 9]
print(sorted(numbers, reverse=True)) # [9, 6, 5, 4, 3, 2, 1, 1]
# Sort by key function
words = ["banana", "apple", "cherry", "date"]
print(sorted(words, key=len)) # ['date', 'apple', 'banana', 'cherry']
print(sorted(words, key=str.lower)) # Case-insensitive sort
# Sort complex objects
students = [
{"name": "Иван", "grade": 85, "age": 20},
{"name": "Мария", "grade": 92, "age": 19},
{"name": "Пётр", "grade": 78, "age": 21},
{"name": "Анна", "grade": 92, "age": 20},
]
# Sort by grade (descending), then by name
from operator import itemgetter
by_grade = sorted(students, key=itemgetter("grade"), reverse=True)
for s in by_grade:
print(f"{s['name']}: {s['grade']}")
# Multiple sort keys with tuple
by_grade_name = sorted(students, key=lambda s: (-s["grade"], s["name"]))
for s in by_grade_name:
print(f"{s['name']}: {s['grade']}")
# Анна: 92
# Мария: 92
# Иван: 85
# Пётр: 78
# attrgetter for objects
from operator import attrgetter
from dataclasses import dataclass
@dataclass
class Student:
name: str
grade: int
students = [Student("Иван", 85), Student("Мария", 92), Student("Пётр", 78)]
by_grade = sorted(students, key=attrgetter("grade"), reverse=True)
Стабильность сортировки
# Python's sort is STABLE (preserves relative order of equal elements)
data = [("Иван", "А"), ("Мария", "Б"), ("Пётр", "А"), ("Анна", "Б")]
# Sort by group - students within same group keep original order
by_group = sorted(data, key=lambda x: x[1])
print(by_group)
# [('Иван', 'А'), ('Пётр', 'А'), ('Мария', 'Б'), ('Анна', 'Б')]
# Multi-level sort using stability: sort by secondary key first, then primary
# Sort by grade ascending, then by name
data = [("Иван", 85), ("Мария", 92), ("Пётр", 85), ("Анна", 92)]
result = sorted(sorted(data, key=lambda x: x[0]), key=lambda x: x[1])
print(result)
# [('Иван', 85), ('Пётр', 85), ('Анна', 92), ('Мария', 92)]
zip() и itertools
# zip() - parallel iteration
names = ["Иван", "Мария", "Пётр"]
ages = [25, 30, 28]
cities = ["Москва", "Петербург", "Казань"]
for name, age, city in zip(names, ages, cities):
print(f"{name}, {age}, {city}")
# Create dict from two lists
user_dict = dict(zip(names, ages))
print(user_dict) # {'Иван': 25, 'Мария': 30, 'Пётр': 28}
# Unzip (transpose)
pairs = [(1, "a"), (2, "b"), (3, "c")]
numbers, letters = zip(*pairs)
print(numbers) # (1, 2, 3)
print(letters) # ('a', 'b', 'c')
# zip with strict mode (Python 3.10+)
# list(zip([1, 2], [3, 4, 5], strict=True)) # ValueError!
# itertools essentials
from itertools import chain, islice, takewhile, dropwhile, accumulate
# chain - combine multiple iterables
combined = list(chain([1, 2], [3, 4], [5, 6]))
print(combined) # [1, 2, 3, 4, 5, 6]
# islice - slice any iterable (lazy)
from itertools import count
first_10_evens = list(islice((x for x in count() if x % 2 == 0), 10))
print(first_10_evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# accumulate - running totals
from itertools import accumulate
running_sum = list(accumulate([1, 2, 3, 4, 5]))
print(running_sum) # [1, 3, 6, 10, 15]
running_max = list(accumulate([3, 1, 4, 1, 5, 9], max))
print(running_max) # [3, 3, 4, 4, 5, 9]
bisect -- бинарный поиск
import bisect
# Maintain a sorted list efficiently
sorted_list = [1, 3, 5, 7, 9]
# Find insertion point
idx = bisect.bisect(sorted_list, 4) # 2 (insert after existing equal elements)
idx_l = bisect.bisect_left(sorted_list, 5) # 2 (insert before equal elements)
idx_r = bisect.bisect_right(sorted_list, 5) # 3
# Insert maintaining sorted order
bisect.insort(sorted_list, 4)
print(sorted_list) # [1, 3, 4, 5, 7, 9]
bisect.insort(sorted_list, 6)
print(sorted_list) # [1, 3, 4, 5, 6, 7, 9]
# Grade classification using bisect
def grade(score: int) -> str:
"""Classify score into a letter grade."""
breakpoints = [60, 70, 80, 90]
grades = ["F", "D", "C", "B", "A"]
idx = bisect.bisect(breakpoints, score)
return grades[idx]
scores = [33, 60, 72, 85, 91, 100]
for s in scores:
print(f"{s}: {grade(s)}")
# 33: F, 60: D, 72: C, 85: B, 91: A, 100: A
heapq -- куча (приоритетная очередь)
import heapq
# heapq implements a min-heap
data = [3, 1, 4, 1, 5, 9, 2, 6]
# Create a heap
heapq.heapify(data)
print(data) # [1, 1, 2, 6, 5, 9, 4, 3] (heap property)
# Push and pop
heapq.heappush(data, 0)
print(heapq.heappop(data)) # 0 (smallest element)
print(heapq.heappop(data)) # 1
# Push and pop in one step
result = heapq.heappushpop(data, 7) # push 7, pop smallest
print(result)
# N largest/smallest
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print(heapq.nlargest(3, numbers)) # [9, 6, 5]
print(heapq.nsmallest(3, numbers)) # [1, 1, 2]
# With key function
tasks = [
{"name": "Отчёт", "priority": 3},
{"name": "Баг", "priority": 1},
{"name": "Фича", "priority": 2},
]
urgent = heapq.nsmallest(2, tasks, key=lambda t: t["priority"])
print([t["name"] for t in urgent]) # ['Баг', 'Фича']
# Max-heap trick (negate values)
max_heap = []
for val in [3, 1, 4, 1, 5]:
heapq.heappush(max_heap, -val)
# Pop gives maximum
print(-heapq.heappop(max_heap)) # 5
print(-heapq.heappop(max_heap)) # 4
# Merge sorted iterables
list1 = [1, 3, 5, 7]
list2 = [2, 4, 6, 8]
merged = list(heapq.merge(list1, list2))
print(merged) # [1, 2, 3, 4, 5, 6, 7, 8]
collections.deque
Двусторонняя очередь с O(1) операциями на обоих концах:
from collections import deque
# Create a deque
d = deque([1, 2, 3, 4, 5])
# O(1) operations on both ends
d.append(6) # Add to right: [1, 2, 3, 4, 5, 6]
d.appendleft(0) # Add to left: [0, 1, 2, 3, 4, 5, 6]
d.pop() # Remove from right: 6
d.popleft() # Remove from left: 0
# Extend on both ends
d.extend([7, 8]) # Add multiple to right
d.extendleft([-2, -1]) # Add multiple to left (reversed!)
print(d) # deque([-1, -2, 1, 2, 3, 4, 5, 7, 8])
# Rotate
d = deque([1, 2, 3, 4, 5])
d.rotate(2) # Rotate right: deque([4, 5, 1, 2, 3])
d.rotate(-2) # Rotate left: deque([1, 2, 3, 4, 5])
# Bounded deque (maxlen) - automatically discards oldest
recent = deque(maxlen=3)
for i in range(5):
recent.append(i)
print(recent)
# deque([0], maxlen=3)
# deque([0, 1], maxlen=3)
# deque([0, 1, 2], maxlen=3)
# deque([1, 2, 3], maxlen=3)
# deque([2, 3, 4], maxlen=3)
# Sliding window using deque
def moving_average(data: list[float], window: int) -> list[float]:
"""Calculate moving average with a sliding window."""
result = []
window_deque = deque(maxlen=window)
for value in data:
window_deque.append(value)
if len(window_deque) == window:
result.append(sum(window_deque) / window)
return result
prices = [100, 102, 104, 103, 105, 107, 106]
print(moving_average(prices, 3))
# [102.0, 103.0, 104.0, 105.0, 106.0]
Итоги
- Star unpacking
*-- гибкое извлечение элементов из последовательностей - Walrus operator
:=-- присваивание внутри выражений sorted()сkey-- мощная сортировка по любым критериям; кортежи для множественной сортировкиzip()-- параллельная итерация,strict=Trueв Python 3.10+- bisect -- бинарный поиск и вставка в отсортированный список
- heapq -- min-куча для приоритетных очередей и Top-N задач
- deque -- O(1) операции на обоих концах, ограниченный размер с
maxlen itertools-- chain, islice, accumulate и другие инструменты для ленивой обработки