Почему в Python нет понимания кортежей?

340

Как мы все знаем, есть понимание списка, как

[i for i in [1, 2, 3, 4]]

и есть словарное понимание, как

{i:j for i, j in {1: 'a', 2: 'b'}.items()}

но

(i for i in (1, 2, 3))

окажется в генераторе, а не в tupleпонимании. Это почему?

Я предполагаю, что а tupleявляется неизменным, но это не похоже на ответ.

Shady Xu
источник
16
Есть также определенное понимание, которое во многом похоже на глубокое понимание ...
mgilson
3
В вашем коде есть синтаксическая ошибка: {i:j for i,j in {1:'a', 2:'b'}}должно быть{i:j for i,j in {1:'a', 2:'b'}.items()}
Inbar Rose
@InbarRose Спасибо, что указали на это -.-
Shady Xu
Просто ради потомков, в Python Chat
Inbar Rose
Видимо, есть. stackoverflow.com/a/51811147/9627166
Super S

Ответы:

471

Вы можете использовать выражение генератора:

tuple(i for i in (1, 2, 3))

но скобки уже были приняты для ... выражений генератора.

Мартейн Питерс
источник
15
К этому аргументу, мы могли бы сказать , список постижение ненужно тоже: list(i for i in (1,2,3)). Я действительно думаю, что это просто потому, что нет чистого синтаксиса для него (или, по крайней мере, никто не думал об этом)
mgilson
79
Понимание списка, набора или диктовки - это просто синтаксический сахар для использования выражения-генератора, которое выводит определенный тип. list(i for i in (1, 2, 3))является выражением генератора, которое выводит список, set(i for i in (1, 2, 3))выводит набор. Означает ли это, что синтаксис понимания не нужен? Возможно нет, но это очень удобно. В тех редких случаях вам нужен кортеж, вместо того, чтобы генерировать выражение генератора, он понятен и не требует изобретения другой скобки или скобки.
Мартин Питерс
16
Ответ очевиден, потому что синтаксис кортежа и скобки неоднозначны
Чарльз Сальвия,
19
Разница между использованием понимания и использованием конструктора + генератора более чем незначительна, если вы заботитесь о производительности. Понимание приводит к более быстрому построению по сравнению с использованием генератора, переданного конструктору. В последнем случае вы создаете и выполняете функции, а функции на Python стоят дорого. [thing for thing in things]создает список гораздо быстрее, чем list(thing for thing in things). Понимание кортежей не будет бесполезным; tuple(thing for thing in things)имеет проблемы с задержкой и tuple([thing for thing in things])может иметь проблемы с памятью.
Джастин Тернер Артур
9
@MartijnPieters, вы можете перефразировать A list or set or dict comprehension is just syntactic sugar to use a generator expression? Это вызывает путаницу, когда люди видят в них эквивалентное средство для достижения цели. Технически это не синтаксический сахар, поскольку процессы на самом деле разные, даже если конечный продукт одинаков.
jpp
77

Рэймонд Хеттингер (Raymond Hettinger) (один из разработчиков ядра Python) сказал следующее о кортежах в последнем твите :

#python tip: Как правило, списки предназначены для зацикливания; кортежи для структур. Списки однородны; кортежи разнородные. Списки для переменной длины.

Это (для меня) поддерживает идею, что если элементы в последовательности достаточно связаны, чтобы быть сгенерированным, ну, в общем, генератором, то это должен быть список. Хотя кортеж является итеративным и выглядит просто как неизменяемый список, на самом деле это Python-эквивалент структуры C:

struct {
    int a;
    char b;
    float c;
} foo;

struct foo x = { 3, 'g', 5.9 };

становится в Python

x = (3, 'g', 5.9)
chepner
источник
26
Свойство неизменяемости может быть важно, хотя и часто является хорошей причиной для использования кортежа, когда вы обычно используете список. Например, если у вас есть список из 5 чисел, которые вы хотите использовать в качестве ключа к диктовке, тогда кортеж - это путь.
павон
Вот хороший совет от Рэймонда Хеттингера. Я бы все же сказал, что есть вариант использования конструктора кортежей с генератором, такой как распаковка другой структуры, возможно большей, в меньшую, путем итерации атрибутов, которые вы заинтересованы в преобразовании в запись кортежа.
Дэйв
2
@dave Вы, вероятно, можете просто использовать operator.itemgetterв этом случае.
chepner
@chepner, я вижу. Это довольно близко к тому, что я имею в виду. Он возвращает вызываемое, поэтому, если мне нужно сделать это только один раз, я не вижу особой победы по сравнению с простым использованием tuple(obj[item] for item in items). В моем случае я включил это в понимание списка, чтобы составить список записей кортежей. Если мне нужно сделать это несколько раз по всему коду, то itemgetter выглядит великолепно. Возможно itemgetter был бы более идиоматическим в любом случае?
Дэйв
Я вижу взаимосвязь между frozenset и множеством, аналогичную кортежу и списку. Речь идет не о неоднородности, а о неизменяемости - заморозки и кортежи могут быть ключами к словарям, списки и наборы не могут из-за их изменчивости.
полиглот
56

Начиная с Python 3.5 , вы также можете использовать *синтаксис распаковки для распаковки выражения генератора:

*(x for x in range(10)),
czheo
источник
2
Это здорово (и это работает), но я не могу найти нигде, что это задокументировано! У вас есть ссылка?
Феликсфью
8
Примечание. В качестве детали реализации, это в основном то же самое, что и выполнение tuple(list(x for x in range(10)))( пути к коду идентичны , причем оба они строят a list, с той лишь разницей, что последний шаг заключается в создании a tupleиз listи выбрасывании listкогда tupleвывод необходимо). Означает, что вы на самом деле не избегаете пары временных.
ShadowRanger
4
Чтобы расширить комментарий @ShadowRanger, вот вопрос, где они показывают, что буквальный синтаксис splat + tuple на самом деле немного медленнее, чем передача выражения генератора в конструктор кортежа.
Лукубратор
Я пытаюсь это в Python 3.7.3 и *(x for x in range(10))не работает. Я получаю SyntaxError: can't use starred expression here. Однако tuple(x for x in range(10))работает.
Райан Х.
4
@RyanH. вам нужно поставить запятую в конце.
Чео
27

Как macmупоминалось в другом постере , самый быстрый способ создать кортеж из генератора - это tuple([generator]).


Сравнение производительности

  • Понимание списка:

    $ python3 -m timeit "a = [i for i in range(1000)]"
    10000 loops, best of 3: 27.4 usec per loop
  • Кортеж из списка понимания:

    $ python3 -m timeit "a = tuple([i for i in range(1000)])"
    10000 loops, best of 3: 30.2 usec per loop
  • Кортеж из генератора:

    $ python3 -m timeit "a = tuple(i for i in range(1000))"
    10000 loops, best of 3: 50.4 usec per loop
  • Кортеж из распаковки:

    $ python3 -m timeit "a = *(i for i in range(1000)),"
    10000 loops, best of 3: 52.7 usec per loop

Моя версия Python :

$ python3 --version
Python 3.6.3

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

Том
источник
10
Примечание: tupleдля listcomp требуется пиковое использование памяти на основе объединенного размера финала tupleи list. tupleИз-за того, что genexpr медленнее, он означает, что вы платите только за финал tuple, а не за временный list(сам генxpr занимает примерно фиксированную память). Обычно не имеет смысла, но это может быть важно, когда размеры огромны.
ShadowRanger
25

Понимание работает путем зацикливания или перебора элементов и назначения их в контейнер, а кортеж не может получать назначения.

После создания кортежа его нельзя добавлять, расширять или назначать. Единственный способ изменить кортеж - это если один из его объектов может быть назначен самому себе (это контейнер без кортежа). Потому что кортеж содержит только ссылку на этот тип объекта.

Также - у кортежа есть собственный конструктор, tuple()который вы можете предоставить любому итератору. Что означает, что для создания кортежа вы можете сделать:

tuple(i for i in (1,2,3))
Инбар Роуз
источник
9
В некотором смысле я согласен (об этом нет необходимости, потому что список подойдет), но в других отношениях я не согласен (о том, что рассуждение является неизменным). В некотором смысле, имеет больше смысла иметь понимание для неизменных объектов. кто делает lst = [x for x in ...]; x.append()?
Мгилсон
@mgilson Я не уверен, как это относится к тому, что я сказал?
Инбар Роуз
2
@mgilson, если кортеж является неизменным, это означает, что базовая реализация не может «генерировать» кортеж («генерация» подразумевает создание одного фрагмента за раз). неизменный означает, что вы не можете построить один из 4 частей, изменив один из 3 частей. вместо этого вы реализуете «генерацию» кортежей, создавая список, предназначенный для генерации, затем создавая кортеж в качестве последнего шага и отбрасывая список. Язык отражает эту реальность. Думайте о кортежах как о структурах.
Скотт
2
хотя было бы разумно, чтобы синтаксический сахар пониманий работал для кортежей, поскольку вы не можете использовать кортеж до тех пор, пока не будет возвращено понимание. По сути, он не действует как изменчивый, а понимание кортежа может вести себя подобно добавлению строки.
Учуугака
12

Мое лучшее предположение, что они вышли за скобки и не думали, что это будет достаточно полезно, чтобы добавить "уродливый" синтаксис ...

mgilson
источник
1
Угловые скобки не используются.
Учуугака
@uchuugaka - Не совсем. Они используются для сравнения операторов. Это, вероятно, все еще можно сделать без двусмысленности, но, возможно, не стоит усилий ...
mgilson
3
@uchuugaka Стоит отметить, что {*()}, хотя и некрасиво, работает как пустой набор букв!
М.И. Райт
1
Тьфу. С эстетической точки зрения я думаю, что я неравнодушен к set():)
mgilson
1
@QuantumMechanic: Да, в этом все дело; обобщения распаковки сделали возможным пустое «множество букв». Обратите внимание, что {*[]}строго уступает другим параметрам; пустая строка и empty tuple, будучи неизменяемыми, являются синглетонами, поэтому для создания пустого не требуется никаких временных set. Напротив, пустое listне является синглтоном, поэтому вам действительно нужно его сконструировать, использовать его для построения set, а затем уничтожить, теряя при этом любое тривиальное преимущество в производительности, которое дает одноглазый оператор обезьяны.
ShadowRanger
8

Кортежи не могут быть эффективно добавлены в виде списка.

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

Это было бы так же, как то, что вы делаете сейчас: tuple ([понимание])

MACM
источник
3

Круглые скобки не создают кортеж. иначе один = (два) не кортеж. Единственный выход - один = (два) или один = кортеж (два). Итак, решение:

tuple(i for i in myothertupleorlistordict) 
Илиас Илиадис
источник
отлично. это почти то же самое.
Учуугака
-1

Я считаю, что это просто ради ясности, мы не хотим загромождать язык слишком многими различными символами. Кроме того, tupleпонимание никогда не требуется , вместо этого можно использовать список с незначительными различиями в скорости, в отличие от точного понимания в отличие от понимания списка.

jamylak
источник
-2

Мы можем генерировать кортежи из списка понимания. Следующий добавляет два числа последовательно в кортеж и дает список из чисел 0-9.

>>> print k
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> r= [tuple(k[i:i+2]) for i in xrange(10) if not i%2]
>>> print r
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Рохит Малгаонкар
источник