Чому в 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розумінні. Чому так?

Я здогадуюсь, що a tupleє незмінним, але це, здається, не є відповіддю.


15
Є також набір розуміння - яке схоже на розуміння
диктату

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 Дякую, що вказав на це -.-
Шаді

Тільки заради нащадків дискусія про це відбувається в чаті Python
Inbar Rose

Відповіді:


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))виводить набір. Чи означає це, що синтаксис розуміння не потрібен? Можливо, ні, але це жахливо зручно. У рідкісних випадках вам потрібен кортеж, вираження генератора зробить, зрозумілим і не вимагає винаходу іншої дужки або дужки.
Martijn Pieters

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

Реймонд Хеттінгер (один із розробників ядра Python) мав таке сказати про кортежі в недавньому твіті :

Порада #python: Зазвичай списки призначені для циклічного циклу; кортежі для конструкцій. Списки однорідні; кортежі неоднорідні. Списки змінної довжини.

Це (для мене) підтримує думку про те, що якщо елементи в послідовності пов'язані достатньо, щоб їх генерував генератор, ну, це повинен бути список. Незважаючи на те, що кортеж є ітерабельним і здається просто незмінним списком, це справді еквівалент Python C-структури:

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

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

стає в Python

x = (3, 'g', 5.9)

26
Властивість непорушності може бути важливою, хоча і часто є вагомою причиною для використання кортежу, коли ви зазвичай використовуєте список. Наприклад, якщо у вас є список із 5 чисел, які ви хочете використовувати як ключ до диктату, то кортеж - це шлях.
pavon

Ось чудова порада від Реймонда Хеттінгера. Я б ще сказав, що існує випадок використання конструктора кортежів з генератором, наприклад, розпакування іншої структури, можливо, більшої, у меншу за допомогою ітерації над attrs, які ви зацікавлені в перетворенні на запис кортежу.
Дейв

2
@dave Ви можете, ймовірно, просто використовувати operator.itemgetterв цьому випадку.
чепнер

@chepner, я бачу. Це досить близько до того, що я маю на увазі. Він повертає дзвінки, тому якщо мені потрібно зробити це лише раз, я не бачу багато виграшу проти просто використання tuple(obj[item] for item in items)безпосередньо. У моєму випадку я вкладав це до списку, щоб скласти список записів кортежів. Якщо мені потрібно робити це неодноразово протягом коду, тоді itemgetter виглядає чудово. Можливо, предмет в будь-якому випадку був би ідіоматичним?
Дейв

Я бачу залежність між замороженим набором та набором, аналогічним кортежу та списку. Йдеться менше про неоднорідність і більше про незмінність - frozensets та кортежі можуть бути ключем до словників, списки та набори не можуть через їх змінності.
поліглот

56

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

*(x for x in range(10)),

2
Це чудово (і це працює), але я не можу знайти його ніде документально! У вас є посилання?
felixphew

8
Примітка. Як деталь реалізації, це в основному те саме, що робити tuple(list(x for x in range(10)))( кодові шляхи однакові , обидва вони будують a list, з тією лише різницею, що останнім кроком є ​​створення 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))працює.
Ryan H.

4
@RyanH. вам потрібно поставити кому в кінці.
czheo

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
Примітка: tuplelistcomp вимагає максимального використання пам'яті на основі комбінованого розміру остаточного tupleта list. tupleале genexpr, хоча повільніше, означає, що ви платите лише за остаточний tuple, не тимчасовий list(сам genexpr, що займає приблизно фіксовану пам'ять). Зазвичай це не має сенсу, але це може бути важливо, коли розміри задіяні величезні.
ShadowRanger

25

Зрозуміння працює шляхом циклічного перегляду або повторення елементів і присвоєння їх контейнеру, кортеж не може приймати завдання.

Після створення кортежу він не може бути доданий, розширений або призначений. Єдиний спосіб зміни кортежу полягає в тому, якщо одному з його об'єктів можна присвоїти собі (є контейнер без кортежу). Тому що кортеж утримує лише посилання на такий об’єкт.

Також - кортеж має власний конструктор, tuple()якому ви можете надати будь-який ітератор. Що означає, що для створення кортежу ви могли б зробити:

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

9
У чомусь я погоджуюся (про це не потрібно, оскільки список буде), але по-іншому я не згоден (про те, що міркування тому, що це незмінне). У чомусь має сенс мати розуміння незмінних об'єктів. хто робить lst = [x for x in ...]; x.append()?
mgilson

@mgilson Я не впевнений, як це стосується сказаного?
Inbar Rose

2
@mgilson, якщо кортеж є незмінним, це означає, що основна реалізація не може "генерувати" кортеж ("генерація", що передбачає побудову одного фрагмента за раз). незмінне означає, що ви не можете скласти той, який складається з 4 частин, змінивши той, який складається з 3 частин. натомість ви реалізуєте кортеж "покоління", будуючи список, щось призначене для покоління, потім будуєте кортеж як останній крок і викидаєте список. Мова відображає цю реальність. Подумайте про кортежі як C структури.
Скотт

2
хоча синтаксичний цукор розуміння працював би для кортежів, бо не можна використовувати кортеж до повернення розуміння. Ефективно це не так, як змінюється, швидше, розуміння кортежу може поводитись так, як додавання струни.
uchuugaka

12

Моя найкраща здогадка полягає в тому, що вони закінчилися дужками і не думали, що це буде досить корисно для того, щоб додати "потворний" синтаксис ...


1
Кутові дужки невикористані.
uchuugaka

@uchuugaka - Не повністю. Вони використовуються для операторів порівняння. Можливо, це все-таки можна зробити без двозначності, але, можливо, не варто докладати зусиль ...
mgilson

3
@uchuugaka Варто зазначити, що {*()}, хоч і некрасиво, працює як порожній набір буквально!
MI MI Райт

1
Тьфу. З естетичної точки зору, я думаю, що я частковий до set():)
mgilson

1
@QuantumMechanic: Так, це справа; розпакування узагальнень зробило можливим порожній "набір буквальним". Зауважте, що {*[]}суворо поступається іншим варіантам; порожній рядок і порожній tuple, будучи незмінним, є одинаковими, тому для побудови порожнього не потрібні тимчасові set. На відміну від цього, порожній listне є однотонним, тому ви насправді мусите його сконструювати, використовувати його для побудови set, а потім знищити, втрачаючи будь-яку тривіальну перевагу продуктивності, яку надає одноокий оператор мавп.
ShadowRanger

8

Кортежі не можна ефективно додавати, як список.

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

Це було б те саме, що ви робите зараз: кортеж ([розуміння])


3

Дужки не створюють кортеж. aka one = (два) - не кортеж. Єдиний шлях - це один = (два,) або один = кортеж (два). Тож рішення таке:

tuple(i for i in myothertupleorlistordict) 

приємно. це майже те саме.
uchuugaka

-1

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


-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)]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.