На этом шаге мы рассмотрим особенности такой распаковки.
Вам нужно распаковать N элементов из итерируемого объекта, но этот объект может содержать больше N элементов, что вызывает исключение
"too many values to unpack" ("слишком много значений для распаковки").
Для решения этой задачи можно использовать "выражения со звездочкой". Предположим, например, что вы ведете учебный курс. В конце семестра вы решаете, что не будете принимать во внимание оценки за первое и последнее домашние задания, а по остальным оценкам посчитаете среднее значение. Если у вас было четыре задания, то можно просто распаковать все четыре. Но что делать, если их 24? Выражение со звездочкой позволит легко решить эту задачу:
def drop_first_last(grades): first, *middle, last = grades return avg(middle)
Рассмотрим еще один пример: предположим, что у вас есть записи о пользователях, которые состоят из имени и адреса электронной почты, за которыми следует произвольное количество телефонных номеров. Вы можете распаковать записи так:
>>> record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212') >>> name, email, *phone_numbers = record >>> name 'Dave' >>> email 'dave@example.com' >>> phone_numbers ['773-555-1212', '847-555-1212'] >>>
Стоит отметить, что переменная phone_numbers всегда будет списком - несмотря на то, сколько телефонных номеров распаковано (даже если и ни одного). Любому коду, который будет использовать переменную phone numbers, не нужно учитывать возможность того, что в ней будет не список (и производить дополнительные проверки на предмет этого).
Переменная со звездочкой также может быть первой в списке. Например, у вас есть последовательность значений, представляющая продажи вашей компании за последние восемь кварталов. Если вы хотите посмотреть, как последний квартал соотносится со средним значением по первым семи, вы можете сделать так:
*trailing_qtrs, current_qtr = sales_record trailing_avg = sum(trailing_qtrs) / len(trailing_qtrs) return avg_comparison(trailing_avg, current_qtr)
Интерпретатор Python выдаст:
>>> *trailing, current = [10, 8, 7, 1, 9, 5, 10, 3] >>> trailing [10, 8, 7, 1, 9, 5, 10] >>> current 3 >>>
Расширенная распаковка отлично подходит для распаковки итерируемых объектов неизвестной или произвольной длины. Часто эти объекты имеют некоторые известные элементы или шаблоны (например, "все, что после элемента 1, является телефонным номером"). Распаковка со звездочкой позволяет программисту легко использовать эти шаблоны - вместо того чтобы исполнять акробатические трюки для извлечения нужных элементов из итерируемого объекта.
Стоит отметить, что синтаксис звездочки может быть особенно полезен при итерировании по последовательности кортежей переменной длины. Например, возможна такая последовательность кортежей с тегами:
records = [ ('foo', 1, 2), ('bar', 'hello'), ('foo', 3, 4), ] def do_foo(x, y): print('foo', x, y) def do_bar(s): print('bar', s) for tag, *args in records: if tag == 'foo': do_foo(*args) elif tag == 'bar': do_bar(*args)
Распаковка со звездочкой также может быть полезна в комбинации с операциями обработки строк, такими как разрезание. Например:
>>> line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false' >>> uname, *fields, homedir, sh = line.split(':') >>> uname 'nobody' >>> homedir '/var/empty' >>> sh '/usr/bin/false' >>>
Иногда вам может быть нужно распаковать значения и отбросить их. Вы не можете просто определить одну * при распаковке, но можно использовать обычное для отбрасывания имя переменной, такое как _ или ign (ignored). Например:
>>> record = ('ACME', 50, 123.45, (12, 18, 2012)) >>> name, *_, (*_, year) = record >>> name 'ACME' >>> year 2012 >>>
Есть некоторое сходство между распаковкой со звездочкой и обработкой списков в функциональных языках. Например, если у вас есть список, то вы можете легко разделить его на "хвост" и "голову":
>>> items = [1, 10, 7, 4, 5, 9] >>> head, *tail = items >>> head 1 >>> tail [10, 7, 4, 5, 9] >>>
Можно представить себе функцию, которая произведет такое разрезание с помощью хитрого рекурсивного алгоритма. Например:
>>> def sum(items): head, *tail = items return head + sum(tail) if tail else head >>> items = [1, 10, 7, 4, 5, 9] >>> sum(items) 36
Однако вам следует знать, что рекурсия не относится к числу сильных сторон Python из-за внутреннего лимита на нее. Поэтому последний пример на практике оказывается просто любопытным предметом для размышления.
На следующем шаге мы рассмотрим реализацию оставления последних N элементов в последовательности.