Изящный unpacking в питоне
Частенько бывает нужно проитерироваться по большой коллекции в которой лежат другие коллекции.
Возьмем для примера вот эту:
In [32]: L = [ [i] for i in range(10) ]
In [33]: L
Out[33]: [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]]
Очевидный способ пробежать по вложенным элементам - это чтение по индексу:
In [24]: [ i[0] for i in L ]
Out[24]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Но есть и другой способ сделать нечто похожее, со своими особенностями и преимуществами:
In [24]: [ i for i, in L ]
Out[24]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
По сути, это хитро записанная распаковка листа/тупла, состоящего строго из одного элемента.
Это работает потому что, в python конструктором тупла является запятая, а не скобки.
In [29]: 5,
Out[29]: (5,)
In [30]: (5)
Out[30]: 5
А есть отличия?
Да.
Кажется, что сложно придумать что-то быстрее, чем чтение по индесу из начала списка - но распаковка оказывается быстрее, процентов на 10.
In [24]: L = [ [i] for i in range(1000) ]
In [25]: %timeit [ i for i, in L ]
19.7 µs ± 31.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [26]: %timeit [ i[0] for i in L ]
22.1 µs ± 150 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
Есть и еще кое-что.
Если на вход попадут пустые списки, то оба варианта упадут с ошибками, правда, каждый со своей:
In [30]: [ i[0] for i in L+[[]] ]
# я удалил traceback, вы и так читаете Traceback :)
IndexError: list index out of range
In [31]: [ i for i, in L+[[]] ]
# я удалил traceback, вы и так читаете Traceback :)
ValueError: not enough values to unpack (expected 1, got 0)
Но если на вход придут листы/туплы с более чем одним элементом, то итерация с распаковкой упадет, а с чтением по индексу - молча вернет первое значение. Возможно, это не лучший вариант, особенно если вспомнить вторую заповедь.