Навіщо нам потрібні кортежі в Python (або будь-який незмінний тип даних)?


140

Я прочитав кілька навчальних посібників з python (Dive Into Python, для одного) та посилання на мову Python.org - не знаю, чому для цієї мови потрібні кортежі.

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

Незмінюваність?

Чому когось хвилює, чи змінна живе в іншому місці пам’яті, ніж коли вона була призначена спочатку? Весь цей бізнес непорушності в Python, здається, занадто наголошений.

Якщо в C / C ++ я виділяю вказівник і вказую на якусь дійсну пам'ять, мені все одно, де знаходиться адреса, доки вона не буде нульовою, перш ніж я її використовувати.

Щоразу, коли я посилаюсь на цю змінну, мені не потрібно знати, чи вказівник все ще вказує на оригінальну адресу чи ні. Я просто перевіряю наявність нуля і використовую його (чи ні).

У Python, коли я виділяю рядок (або кортеж), присвоюю його x, а потім змінюю рядок, чому мені байдуже, чи це оригінальний об'єкт? Поки змінна вказує на мої дані, це все, що має значення.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

x все ще посилається на потрібні мені дані, чому хтось повинен дбати, чи його ідентичний номер однаковий чи різний?


12
ви звертаєте увагу на неправильний аспект змінності: "чи той самий ідентичний чи різний" - це лише побічний ефект; "чи є дані, на які вказують інші посилання, які раніше вказували на той самий об'єкт, відображають оновлення" є критичним.
Чарльз Даффі

Відповіді:


124
  1. незмінні об'єкти можуть дозволити істотну оптимізацію; це імовірно, тому рядки також незмінні на Java, розроблені зовсім окремо, але приблизно в той же час, що і Python, і майже все є незмінним справді функціональними мовами.

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

Приклад питання оптимізації:

$ python -mtimeit '["fee", "fie", "fo", "fum"]'
1000000 loops, best of 3: 0.432 usec per loop
$ python -mtimeit '("fee", "fie", "fo", "fum")'
10000000 loops, best of 3: 0.0563 usec per loop

11
@musicfreak, дивіться редагування, яке я щойно робив, коли створення кортежу в 7,6 разів швидше, ніж створення еквівалентного списку - тепер ви не можете сказати, що ви "ніколи не бачили помітної різниці", якщо тільки ваше визначення "помітне" "це справді своєрідно ...
Алекс Мартеллі

11
@musicfreak Я думаю, що ви зловживаєте "передчасна оптимізація - корінь усього зла". Існує величезна різниця між тим, що робити передчасну оптимізацію в додатку (наприклад, кажучи, що "кортежі швидші за списки, тому ми збираємось використовувати лише кортежі у всьому додатку!") І робити показники. Тест Алекса проникливий і знаючи, що створення кортежу швидше, ніж створення списку, може допомогти нам у майбутніх операціях з оптимізації (коли це дійсно потрібно).
Вергілій Дупрас

5
@ Алекс, чи "збирання" кортежу дійсно швидше, ніж "складання списку", чи ми бачимо результат кешування кортежу Python? Останнє мені здається.
Триптих

6
@ACoolie, в цілому переважають randomдзвінки (спробуйте зробити саме це, ви побачите!), Тож не дуже важливо. Спробуйте, python -mtimeit -s "x=23" "[x,x]"і ви побачите більш значущу швидкість в 2-3 рази для побудови кортежу проти створення списку.
Алекс Мартеллі

9
для всіх, хто цікавиться - нам вдалося поголитись за годину обробки даних, перейшовши зі списків на кортежі.
Марк Рібау

42

Жодна з наведених вище відповідей не вказує на реальну проблему кортежів та списків, яку багато нових для Python, здається, не повністю розуміють.

Кортежі та списки служать різному призначенню. Списки зберігають однорідні дані. Ви можете і повинні мати такий список:

["Bob", "Joe", "John", "Sam"]

Причиною правильного використання списків є те, що це всі однорідні типи даних, зокрема, імена людей. Але візьміть такий список:

["Billy", "Bob", "Joe", 42]

У цьому списку є повне ім’я однієї людини та її вік. Це не один тип даних. Правильний спосіб зберігання цієї інформації знаходиться або в кортежі, або в об'єкті. Скажімо, у нас є кілька:

[("Billy", "Bob", "Joe", 42), ("Robert", "", "Smith", 31)]

Незмінність та незмінність кортежів та списків - не головна відмінність. Список - це список однотипних елементів: файлів, імен, об’єктів. Кортежі - це групування предметів різних типів. Вони мають різні види використання, і багато кодерів Python зловживають списками для того, для чого призначені кортежі.

Будь ласка, не варто.


Редагувати:

Я думаю, що це повідомлення в блозі пояснює, чому я вважаю це краще, ніж я: http://news.e-scribe.com/397


13
Я думаю, у вас є бачення, яке, по меншій мірі, не погоджується, не знаю інших.
Стефано Борині

13
Я також категорично не згоден з цією відповіддю. Однорідність даних абсолютно не має нічого спільного з тим, чи слід використовувати список або кортеж. Ніщо в Python не пропонує цього розрізнення.
Глен Мейнард

14
Гвідо висловився на цьому і кілька років тому. aspn.activestate.com/ASPN/Mail/Message/python-list/1566320
Джон Ла Рой

11
Навіть незважаючи на те, що Гвідо (дизайнер Python) призначений для списків, які використовуються для однорідних даних, а кортежі для неоднорідних, факт полягає в тому, що мова цього не виконує. Тому я вважаю, що це тлумачення є скоріше стильовим питанням, ніж будь-що інше. Так трапляється, що у багатьох типових випадках використання списки мають бути схожими на масив, а кортежі мають тенденцію до запису. Але це не повинно заважати людям використовувати списки для неоднорідних даних, якщо це краще відповідає їх проблемі. Як говорить Дзен Пітона: Практичність перемагає чистоту.
Джон Y

9
@Glenn, ти в основному помилився. Одне з головних застосувань кортежів - як складений тип даних для зберігання декількох даних, які пов'язані між собою. Те, що ви можете перебирати кортеж і виконувати багато одних і тих же операцій, це не змінює. (В якості посилання вважають, що кортежі багатьох інших мов не мають таких самих ітерабельних ознак, як їхні аналоги списку)
HS.

22

якщо я повинен перетворити кортеж у набір чи список, щоб мати можливість їх сортувати, який сенс використовувати кортеж в першу чергу?

У цьому конкретному випадку, мабуть, немає сенсу. Це не проблема, оскільки це не один із випадків, коли ви могли б розглянути можливість використання кортежу.

Як ви вказуєте, кортежі незмінні. Причини виникнення непорушних типів стосуються кортежів:

  • ефективність копіювання: замість копіювання незмінного об'єкта, ви можете його псевдонімом (прив'язувати змінну до посилання)
  • ефективність порівняння: коли ви використовуєте копіювання за посиланням, ви можете порівняти дві змінні, порівнявши розташування, а не вміст
  • стажування: вам потрібно зберігати щонайменше одну копію будь-якого незмінного значення
  • немає потреби синхронізувати доступ до незмінних об'єктів у паралельному коді
  • правильність const: деякі значення не можна дозволяти змінювати. Це (для мене) головна причина непорушних типів.

Зауважте, що певна реалізація Python може не використовувати всі перераховані вище функції.

Ключі словника повинні бути незмінними, інакше зміна властивостей ключа-об'єкта може призвести до недійсності інваріантів основної структури даних. Таким чином, кортежі потенційно можуть використовуватися як ключі. Це наслідок правильності const.

Дивіться також " Представлення кортежів ", від Dive Into Python .


2
id ((1,2,3)) == id ((1,2,3)) хибний. Ви не можете порівняти кортежі, лише порівнявши місцеположення, оскільки немає гарантії, що вони були скопійовані за посиланням.
Гленн Мейнард

@Glenn: Зверніть увагу на кваліфікуюче зауваження "коли ви використовуєте копію за посиланням". Хоча кодер може створити власну реалізацію, копіювання за посиланням на кортежі є значною мірою питанням перекладача / компілятора. Я здебільшого мав на увазі те, як ==реалізується на рівні платформи.
outis

1
@Glenn: також зауважте, що копіювання за посиланням не поширюється на кортежі в (1,2,3) == (1,2,3). Це більше питання інтернування.
outis

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

Як я сказав дуже чітко: я не кажу про програміста, який порівнює кортежі, порівнюючи місцеположення. Я кажу про можливість платформи, яка може гарантувати копіювання за посиланням. Також стажування може застосовуватися до будь-якого непорушного типу, не тільки до рядків. Основна реалізація Python може не інтернувати незмінні типи, але факт, що Python має незмінні типи, робить можливість інтернування варіантом.
outis

15

Іноді ми любимо використовувати предмети як словникові клавіші

Для чого це варто, останнім часом (2.6+) кортежі виросли index()та count()методи


5
+1: Змінений список (або набір змінних або змінений словник) як клавіша словника не може працювати. Тому нам потрібні незмінні списки ("кортежі"), заморожені набори і ... ну ... заморожений словник, я думаю.
S.Lott

9

Я завжди вважав, що два абсолютно окремих типи для однієї і тієї ж базової структури даних (масивів) є незручним дизайном, але не є справжньою проблемою на практиці. (У кожної мови є бородавки, включений Python, але це не важливо.)

Чому когось хвилює, чи змінна живе в іншому місці пам’яті, ніж коли вона була призначена спочатку? Весь цей бізнес непорушності в Python, здається, занадто наголошений.

Це різні речі. Переміщення не пов'язане з місцем, яке воно зберігається в пам'яті; це означає, що речі, на які він вказує, не можуть змінитися.

Об'єкти Python не можуть змінити місцезнаходження після їх створення, зміни або відсутності. (Точніше, значення id () не може змінитися - те ж саме на практиці.) Внутрішнє зберігання змінних об'єктів може змінюватися, але це прихована деталь реалізації.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

Це не змінює ("мутує") змінну; це створити нову змінну з тим самим іменем та відкинути стару. Порівняйте з операцією, що мутує:

>>> a = [1,2,3]
>>> id(a)
3084599212L
>>> a[1] = 5
>>> a
[1, 5, 3]
>>> id(a)
3084599212L

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

Зауважте, що ключі для словників не повинні бути повністю незмінними. Тільки та частина, яка використовується як ключова, повинна бути непорушною; для деяких застосувань це важлива відмінність. Наприклад, у вас може бути клас, що представляє користувача, який порівнює рівність і хеш за унікальним іменем користувача. Потім ви можете повісити інші змінні дані про клас - "користувач увійшов у систему" і т. Д. Оскільки це не впливає на рівність або хеш, можна використовувати і цілком справедливо використовувати це як ключ у словнику. Це не надто часто потрібно в Python; Я лише зазначу це, оскільки кілька людей заявляли, що ключі повинні бути «незмінними», що лише частково правильно. Однак я багато разів використовував це з картами та наборами C ++.


>>> a = [1,2,3] >>> id (a) 3084599212L >>> a [1] = 5 >>> a [1, 5, 3] >>> id (a) 3084599212L Ви ' Ви просто змінили тип даних, що змінюються, так що це не має сенсу, пов'язане з початковим запитанням. x = 'привіт' id (x) 12345 x = "до побачення" id (x) 65432 Кого хвилює, чи це новий об'єкт чи ні. Поки x вказує на дані, які я призначив, це все, що важливо.
pyNewGuy

4
Ви заплутані набагато більше, ніж я можу вам допомогти.
Гленн Мейнард

+1 для вказівки плутанини на підпитаннях, які, здається, є основним джерелом труднощів у сприйнятті цінності кортежів.
outis

1
Якщо я міг би, ще один +1 для вказівки на те, що справжня рубрика для ключів - це те, чи об’єкт є придатним для використання ( docs.python.org/glossary.html#term-hashable ).
outis

7

Як запропонував у коментарі гніблер, Гвідо висловив думку, яка не є повністю прийнятою / оціненою: "списки - це однорідні дані, кортежі - для неоднорідних даних". Звичайно, багато опонентів трактували це як означає, що всі елементи списку повинні бути одного типу.

Мені подобається бачити це по-іншому, на відміну від інших, які також були в минулому:

blue= 0, 0, 255
alist= ["red", "green", blue]

Зауважте, що я вважаю аліст однорідним, навіть якщо тип (alist [1])! = Type (alist [2]).

Якщо я можу змінити порядок елементів і не матиму проблем у своєму коді (крім припущень, наприклад, "це слід відсортувати"), тоді слід використовувати список. Якщо ні (як у кортежі blueвище), то я повинен використовувати кортеж.


Якби я міг, я би проголосував за цю відповідь 15 разів. Саме так я відчуваю кортежі.
Грант Пол

6

Вони важливі, оскільки гарантують абоненту, що об'єкт, який вони передають, не буде вимкнено. Якщо ви це зробите:

a = [1,1,1]
doWork(a)

Абонент не має гарантії значення a після дзвінка. Однак,

a = (1,1,1)
doWorK(a)

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


1
Це дуже вторинна властивість кортежів. Занадто багато випадків, коли у вас є об'єкт, що змінюється, який ви хочете передати функції, а не змінювати його, будь то попередній список або якийсь інший клас. У Python просто немає поняття "параметри const за посиланням" (наприклад, const foo & в C ++). Кортежі трапляються вам, якщо вам зручно взагалі використовувати кортеж, але якщо ви отримали список від свого абонента, чи справді ви збираєтесь перетворити його в кортеж, перш ніж передати його кудись ще?
Гленн Мейнард

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

a = [1,1,1] doWork (a) якщо dowork () визначено як def dowork (arg): arg = [0,0,0] виклик dowork () у списку або кортеж має той же результат
pyNewGuy


1

Ваше запитання (та подальші коментарі) зосереджуються на тому, чи змінюється id () під час виконання завдання. Орієнтація на цей подальший ефект різниці між заміною незмінного об'єкта та модифікацією змінних об'єктів, а не сама різниця - це, мабуть, не найкращий підхід.

Перш ніж продовжити, переконайтесь, що поведінка, продемонстрована нижче, - це те, чого ви очікуєте від Python.

>>> a1 = [1]
>>> a2 = a1
>>> print a2[0]
1
>>> a1[0] = 2
>>> print a2[0]
2

У цьому випадку зміст a2 було змінено, навіть якщо лише a1 було призначено нове значення. На противагу цьому:

>>> a1 = (1,)
>>> a2 = a1
>>> print a2[0]
1
>>> a1 = (2,)
>>> print a2[0]
1

В останньому випадку ми замінили весь список, а не оновлювали його зміст. З незмінними типами, такими як кортежі, це єдина дозволена поведінка.

Чому це має значення? Скажімо, у вас є диктант:

>>> t1 = (1,2)
>>> d1 = { t1 : 'three' }
>>> print d1
{(1,2): 'three'}
>>> t1[0] = 0  ## results in a TypeError, as tuples cannot be modified
>>> t1 = (2,3) ## creates a new tuple, does not modify the old one
>>> print d1   ## as seen here, the dict is still intact
{(1,2): 'three'}

Використовуючи кортеж, словник захищений від зміни його ключів "з-під нього" на елементи, які мають хеш на інше значення. Це важливо для ефективної реалізації.


Як вже вказували інші, незмінність! = Хеш. Не всі кортежі можна використовувати як словникові ключі: {([1], [2]): 'value'} виходить з ладу, оскільки змінні списки в кортежі можуть бути змінені, але {((1), (2)): ' значення '} в порядку.
Нед Дейлі

Нед, це правда, але я не впевнений, що це відмінність відповідає питанням, що задається.
Чарльз Даффі

@ K.Nicholas, редакція, яку ви затвердили тут, змінила код таким чином, щоб призначити ціле число, а не кортеж взагалі - збивши пізніші операції з індексом, тому вони не могли перевірити, що нові транскрипція була реально можливою. Правильно визначена проблема, звичайно; недійсне рішення.
Чарльз Даффі

@MichaelPuckettII, також див. Вище.
Чарльз Даффі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.