Распаковка и оператор *

Документы python предоставляют этот код как обратную операцию zip:

>>> x2, y2 = zip(*zipped)

В частности, "zip() в сочетании с оператором * можно использовать для распаковки списка". Может ли кто-нибудь объяснить мне, как работает оператор * в этом случае? Насколько я понимаю, * является двоичным оператором и может использоваться для умножения или неглубокой копии... ни то, что кажется здесь.

Ответы

Ответ 1

При использовании этого типа * (звездочка, также известная в некоторых кругах как оператор "splat" ), является сигналом для распаковки аргументов из списка. См. http://docs.python.org/tutorial/controlflow.html#unpacking-argument-lists для более полного определения с примерами.

Ответ 2

Хотя hammar answer объясняет, как работает реверсирование в случае функции zip(), может быть полезно посмотреть на распаковку аргументов в более общем смысле. Скажем, мы имеем простую функцию, которая принимает некоторые аргументы:

>>> def do_something(arg1, arg2, arg3):
...     print 'arg1: %s' % arg1
...     print 'arg2: %s' % arg2
...     print 'arg3: %s' % arg3
... 
>>> do_something(1, 2, 3)
arg1: 1
arg2: 2
arg3: 3

Вместо прямого указания аргументов мы можем создать список (или кортеж, если на то пошло), чтобы удержать их, а затем сообщить Python о распаковке этого списка и использовать его содержимое в качестве аргументов функции:

>>> arguments = [42, 'insert value here', 3.14]
>>> do_something(*arguments)
arg1: 42
arg2: insert value here
arg3: 3.14

Это ведет себя как обычно, если у вас недостаточно аргументов (или слишком много):

>>> arguments = [42, 'insert value here']
>>> do_something(*arguments)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/blair/<ipython console> in <module>()

TypeError: do_something() takes exactly 3 arguments (2 given)

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

>>> def show_args(*args):
...     for index, value in enumerate(args):
...         print 'Argument %d: %s' % (index, value)
...
>>> show_args(1, 2, 3)
Argument 0: 1
Argument 1: 2
Argument 2: 3

И, конечно, вы можете объединить два метода:

>>> show_args(*arguments)
Argument 0: 42
Argument 1: insert value here

Вы можете сделать аналогичную вещь с аргументами ключевого слова, используя двойную звездочку (**) и словарь:

>>> def show_kwargs(**kwargs):
...     for arg, value in kwargs.items():
...         print '%s = %s' % (arg, value)
...
>>> show_kwargs(age=24, name='Blair')
age = 24
name = Blair

И, конечно же, вы можете передавать аргументы ключевого слова через словарь:

>>> values = {'name': 'John', 'age': 17}
>>> show_kwargs(**values)
age = 17
name = John

Абсолютно приемлемо смешивать два, и вы всегда можете иметь необходимые аргументы и необязательные дополнительные аргументы функции:

>>> def mixed(required_arg, *args, **kwargs):
...     print 'Required: %s' % required_arg
...     if args:
...         print 'Extra positional arguments: %s' % str(args)
...     if kwargs:
...         print 'Extra keyword arguments: %s' % kwargs
...
>>> mixed(1)
Required: 1
>>> mixed(1, 2, 3)
Required: 1
Extra positional arguments: (2, 3)
>>> mixed(1, 2, 3, test=True)
Required: 1
Extra positional arguments: (2, 3)
Extra keyword arguments: {'test': True}
>>> args = (2, 3, 4)
>>> kwargs = {'test': True, 'func': min}
>>> mixed(*args, **kwargs)
Required: 2
Extra positional arguments: (3, 4)
Extra keyword arguments: {'test': True, 'func': <built-in function min>}

Если вы принимаете необязательные аргументы ключевого слова и хотите иметь значения по умолчанию, помните, что вы имеете дело со словарем, и поэтому вы можете использовать его метод get() со значением по умолчанию для использования, если ключ не существует:

>>> def take_keywords(**kwargs):
...     print 'Test mode: %s' % kwargs.get('test', False)
...     print 'Combining function: %s' % kwargs.get('func', all)
... 
>>> take_keywords()
Test mode: False
Combining function: <built-in function all>
>>> take_keywords(func=any)
Test mode: False
Combining function: <built-in function any>

Ответ 3

zip(*zipped) означает "подавать каждый элемент zipped в качестве аргумента в zip". zip похож на перенос матрицы в том, что повторное выполнение этого вопроса вернет вас туда, где вы начали.

>>> a = [(1, 2, 3), (4, 5, 6)]
>>> b = zip(*a)
>>> b
[(1, 4), (2, 5), (3, 6)]
>>> zip(*b)
[(1, 2, 3), (4, 5, 6)]

Ответ 4

Это на самом деле довольно просто, когда вы действительно понимаете, что делает zip().

Функция zip принимает несколько аргументов (все итерируемого типа) и объединяет элементы из этих итераций в соответствии с их соответствующими позициями.

Например, скажем, у нас есть два аргумента ranked_athletes, rewards передаваемые zip, вызов функции zip(ranked_athletes, rewards) будет:

  • пара спортсмен, который занял первое место (позиция я = 0) с первой/лучшей наградой (позиция я = 0)
  • это переместит следующий элемент, я = 1
  • соедините 2-го спортсмена с его наградой, 2-го с reward.
  • ...

Это будет повторяться до тех пор, пока не останется ни спортсмена, ни награды. Например, если мы возьмем 100 метров на Олимпийских играх в 2016 году и zip награды мы имеем:

ranked_athletes = ["Usain Bolt", "Justin Gatlin", "Andre De Grasse", "Yohan Blake"]
rewards = ["Gold medal", "Silver medal", "Bronze medal"]
zip(ranked_athletes, rewards)

Вернет итератор для следующих кортежей (пар):

('Usain Bolt', 'Gold medal')
('Justin Gatlin', 'Silver medal')
('Andre De Grasse', 'Bronze medal')

Обратите внимание, что Йохан Блейк не имеет награды.

Теперь оператор *, это еще проще, если у вас есть список [1, 2] это распаковывает его в 1, 2. Он в основном превращает один объект во множество (столько же, сколько размер списка).

Таким образом, если мы объединяем эти два, zip(*x) фактически означает: взять этот список объектов, распаковать его со многими объектами и связать элементы из всех этих объектов в соответствии с их индексами. Это имеет смысл, только если объекты являются итеративными (например, списки), в противном случае понятие индекса на самом деле не имеет смысла.

Вот как это выглядит, если вы делаете это шаг за шагом:

>>> print(x)              # x is a list of lists 
[[1, 2, 3], ['a', 'b', 'c', 'd']]

>>> print(*x)             # unpack x
[1, 2, 3]  ['a', 'b', 'c', 'd']

>>> print(list(zip(*x)))  # And pair items from the resulting lists
[(1, 'a'), (2, 'b'), (3, 'c')]

Обратите внимание, что в этом случае, если мы вызовем print(list(zip(x))) мы просто скомпонуем элементы из x (которые являются 2 списками) ни с чем (так как нет другого итерируемого для их сопряжения):

[  ([1, 2, 3],    ),  (['a', 'b', 'c', 'd'],    )]
               ^                              ^
    [1, 2, 3] is paired with nothing          |
                                              |
                        same for the 2nd item from x: ['a', 'b', 'c', 'd']

Еще один хороший способ понять, как работает zip - это реализовать свою собственную версию. Вот кое-что, что выполнит более или менее ту же работу, что и zip но ограничено случаем двух списков (вместо множества итераций):

def zip_two_lists(A, B):
    shortest_list_size = min(len(A), len(B))
    # We create empty pairs
    pairs = [tuple() for _ in range(shortest_list_size)]
    # And fill them with items from each iterable 
    # according to their the items index:
    for index in range(shortest_list_size):
        pairs[index] = (A[index], B[index])
    return pairs

print(zip_two_lists(*x))
# Outputs: [(1, 'a'), (2, 'b'), (3, 'c')]

Обратите внимание, что я не вызывал print(list(zip_two_lists(*x))), потому что эта функция, в отличие от реального zip, не является генератором (функцией, которая создает итератор), а вместо этого мы создаем список в памяти. Поэтому эта функция не так хороша, вы можете найти лучшее приближение к настоящему zip в документации Python. Часто хорошей идеей является прочтение этих эквивалентностей кода, которые есть у вас в этой документации, это хороший способ понять, что функция делает без какой-либо двусмысленности.

Ответ 5

Я предлагаю это, чтобы распаковать zipped список списков, когда zip выполняется с помощью izip_longest:

>>> a =[2,3,4,5,6]
>>> b = [5,4,3,2]
>>> c=[1,0]]

>>>[list([val for val in k if val != None]) for k in 
                                       zip(*itertools.izip_longest(a,b,c))]

поскольку izip_longest добавляет None для списков, самый короткий, чем самый длинный, я заранее удаляю None. И я вернусь к оригиналу a, b, c

[[2, 3, 4, 5, 6], [5, 4, 3, 2], [1, 0]]