Зачем нам нужны кортежи в Python (или любом неизменяемом типе данных)?

140

Я прочитал несколько руководств по python (например, «Dive Into Python») и справочник по языку на Python.org - я не понимаю, зачем этому языку нужны кортежи.

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

Неизменяемость?

Почему кого-то волнует, находится ли переменная в другом месте в памяти, чем когда она была изначально выделена? Кажется, что этому вопросу неизменности в Python уделяется чрезмерное внимание.

В C / C ++, если я выделяю указатель и указываю на некоторую допустимую память, мне все равно, где находится адрес, если он не равен нулю, прежде чем я его использую.

Всякий раз, когда я ссылаюсь на эту переменную, мне не нужно знать, указывает ли указатель на исходный адрес или нет. Я просто проверяю значение null и использую его (или нет).

В Python, когда я выделяю строку (или кортеж), назначаю ее x, а затем изменяю строку, почему меня волнует, является ли это исходным объектом? Пока переменная указывает на мои данные, это все, что имеет значение.

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

x по-прежнему ссылается на данные, которые я хочу, почему кому-то нужно заботиться о том, такой же или другой идентификатор?

pyNewGuy
источник
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
@Alex, действительно ли «строит» кортеж быстрее, чем «строит список», или мы видим результат кэширования кортежа средой выполнения 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

Грант Пол
источник
14
Я думаю, у вас есть видение, с которым не согласен, по крайней мере, я, других не знаю.
Стефано Борини
13
Я тоже категорически не согласен с этим ответом. Однородность данных не имеет абсолютно никакого отношения к тому, следует ли использовать список или кортеж. Ничто в Python не предполагает такого различия.
Гленн Мейнард
14
Гвидо тоже говорил об этом несколько лет назад. aspn.activestate.com/ASPN/Mail/Message/python-list/1566320
Джон Ла Рой,
11
Несмотря на то, что Гвидо (разработчик Python) предназначен для использования списков для однородных данных и кортежей для разнородных, факт в том, что язык не обеспечивает этого. Поэтому я думаю, что эта интерпретация больше касается стиля, чем чего-либо еще. Так получилось, что во многих типичных случаях использования списки имеют тенденцию быть похожими на массивы, а кортежи - на записи. Но это не должно останавливать людей от использования списков для разнородных данных, если это лучше подходит для их проблемы. Как гласит Дзен Питона: Практичность важнее чистоты.
John Y
9
@ Гленн, ты в корне ошибаешься. Одно из основных применений кортежей - это составной тип данных для хранения нескольких связанных частей данных. Тот факт, что вы можете перебирать кортеж и выполнять многие из тех же операций, этого не меняет. (В качестве справки учтите, что кортежи на многих других языках не имеют тех же итерационных функций, что и их аналоги из списка)
HS.
23

если я должен преобразовать кортеж в набор или список, чтобы иметь возможность отсортировать их, в чем вообще смысл использования кортежа?

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

Как вы отметили, кортежи неизменяемы. Причины наличия неизменяемых типов применимы к кортежам:

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

Обратите внимание, что конкретная реализация Python может не использовать все вышеперечисленные функции.

Ключи словаря должны быть неизменными, в противном случае изменение свойств ключевого объекта может сделать недействительными инварианты базовой структуры данных. Таким образом, кортежи потенциально могут использоваться как ключи. Это следствие правильности const.

См. Также « Введение в кортежи » из книги « Dive Into Python» .

Outis
источник
2
id ((1,2,3)) == id ((1,2,3)) ложно. Вы не можете сравнивать кортежи, просто сравнивая расположение, потому что нет гарантии, что они были скопированы по ссылке.
Гленн Мейнард
@Glenn: Обратите внимание на уточняющее замечание «когда вы используете копирование по ссылке». Хотя кодировщик может создавать свою собственную реализацию, копирование по ссылке для кортежей в значительной степени является делом интерпретатора / компилятора. Я в основном имел в виду, как ==это реализовано на уровне платформы.
Outis 01
1
@Glenn: также обратите внимание, что копирование по ссылке не применяется к кортежам в (1,2,3) == (1,2,3). Это больше вопрос интернирования.
outis 01
Как я уже сказал довольно ясно, нет никакой гарантии, что они были скопированы по ссылке . Кортежи в Python не интернируются; это концепция строки.
Гленн Мейнард,
Как я уже сказал очень четко: я не говорю о программисте, сравнивающем кортежи путем сравнения местоположения. Я говорю о возможности того, что платформа может гарантировать копирование по ссылке. Кроме того, интернирование может применяться к любому неизменяемому типу, а не только к строкам. Основная реализация Python может не интернировать неизменяемые типы, но тот факт, что Python имеет неизменяемые типы, делает интернирование опцией.
Outis 01
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 You ' Мы только что изменили изменяемый тип данных, поэтому он не имеет смысла, связанный с исходным вопросом. х = 'привет»Идентификатор (х) 12345 х =„прощайте“Идентификатор (х) 65432 Кто заботится , если это новый объект или нет Пока х точки на данный я присвоенный, это все , что имеет значение..
pyNewGuy 01
4
Вы запутались, и я не в силах вам помочь.
Гленн Мейнард
+1 за указание на путаницу в подвопросах, которые, кажется, являются основным источником трудностей при восприятии значения кортежей.
Outis 01
1
Если бы я мог, еще один +1 за указание на то, что истинная рубрика для ключей заключается в том, является ли объект хешируемым ( docs.python.org/glossary.html#term-hashable ).
Outis 01
7

Как сказал gnibbler в комментарии, у Гвидо было мнение , которое не полностью принято / не оценено: «списки предназначены для однородных данных, кортежи - для разнородных данных». Конечно, многие противники истолковали это как означающее, что все элементы списка должны быть одного типа.

Мне нравится видеть это по-другому, в отличие от других, которые были в прошлом:

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

Обратите внимание, что я считаю alist однородным, даже если type (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 foo & в C ++). Кортежи дают вам это, если вообще удобно использовать кортеж, но если вы получили список от вызывающей стороны, действительно ли вы собираетесь преобразовать его в кортеж, прежде чем передавать его в другое место?
Гленн Мейнард
Я согласен с вами в этом. Кортеж - это не то же самое, что нажатие на ключевое слово const. Я хочу сказать, что неизменность кортежа имеет дополнительный смысл для читателя кода. Учитывая ситуацию, когда и то и другое будет работать, и вы ожидаете, что это не должно измениться, использование кортежа добавит это дополнительное значение для читателя (в то же время обеспечивая его)
Мэтью Манела, 01
a = [1,1,1] doWork (a), если dowork () определен как def dowork (arg): arg = [0,0,0] вызов dowork () для списка или кортежа дает тот же результат
pyNewGuy
1

вы можете увидеть здесь обсуждение этого

призрачная собака74
источник
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)): ' value '} в порядке.
Нед Дейли, 01
Нед, это правда, но я не уверен, что это различие имеет отношение к задаваемому вопросу.
Чарльз Даффи,
@ K.Nicholas, правка, которую вы здесь одобрили, изменила код таким образом, чтобы назначать целое число, а не кортеж, что привело к сбою последующих операций с индексами, поэтому они не могли проверить, что новый расшифровка была действительно возможна. Конечно, правильно обозначенная проблема; недопустимое решение.
Чарльз Даффи
@MichaelPuckettII, см. Выше.
Чарльз Даффи