Por que precisamos de tuplas em Python (ou qualquer tipo de dado imutável)?

140

Eu li vários tutoriais em python (Dive Into Python, por exemplo) e a referência de linguagem no Python.org - não vejo por que a linguagem precisa de tuplas.

As tuplas não têm métodos comparados a uma lista ou conjunto, e se eu devo converter uma tupla em um conjunto ou lista para poder classificá-las, qual é o sentido de usar uma tupla em primeiro lugar?

Imutabilidade?

Por que alguém se importa se uma variável vive em um lugar diferente na memória do que quando foi originalmente alocada? Todo esse negócio de imutabilidade em Python parece ter sido enfatizado demais.

No C / C ++, se eu alocar um ponteiro e apontar para alguma memória válida, não me importo onde o endereço está localizado, desde que não seja nulo antes de usá-lo.

Sempre que faço referência a essa variável, não preciso saber se o ponteiro ainda está apontando para o endereço original ou não. Eu apenas verifico se há nulo e o uso (ou não).

No Python, quando eu aloco uma string (ou tupla), atribua-a a x e modifique a string, por que me importo se é o objeto original? Desde que a variável aponte para meus dados, isso é tudo o que importa.

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

x ainda faz referência aos dados que eu quero, por que alguém precisa se preocupar se seu ID é igual ou diferente?

pyNewGuy
fonte
12
você está prestando atenção ao aspecto errado da mutabilidade: "se o ID é igual ou diferente" é apenas um efeito colateral; "se os dados apontados por outras referências que anteriormente apontavam para o mesmo objeto agora refletem atualizações" é crítico.
Charles Duffy

Respostas:

124
  1. objetos imutáveis ​​podem permitir otimização substancial; presumivelmente, é por isso que as strings também são imutáveis ​​em Java, desenvolvidas separadamente, mas quase ao mesmo tempo que o Python, e praticamente tudo é imutável em linguagens verdadeiramente funcionais.

  2. em Python em particular, apenas imutáveis ​​podem ser laváveis ​​(e, portanto, membros de conjuntos ou chaves em dicionários). Novamente, isso permite otimização, mas muito mais do que apenas "substancial" (projetar tabelas de hash decentes armazenando objetos completamente mutáveis ​​é um pesadelo - você tira cópias de tudo assim que o hash ou o pesadelo de verificar se o hash do objeto mudou desde a última vez que você fez uma referência a ele;

Exemplo de problema de otimização:

$ 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
Alex Martelli
fonte
11
@musicfreak, veja a edição que acabei de fazer quando a construção de uma tupla é 7,6 vezes mais rápida que a lista equivalente - agora você não pode dizer que "nunca viu uma diferença perceptível", a menos que sua definição de "perceptível "é verdadeiramente peculiar ...
Alex Martelli
11
@musicfreak Eu acho que você está usando mal "a otimização prematura é a raiz de todo mal". Há uma enorme diferença entre fazer a otimização prematura em um aplicativo (por exemplo, dizer "tuplas são mais rápidas que listas, portanto, usaremos apenas tuplas em todo o aplicativo!") E fazer benchmarks. O benchmark de Alex é perspicaz e saber que construir uma tupla é mais rápido do que construir uma lista pode nos ajudar em futuras operações de otimização (quando é realmente necessário).
Virgil Dupras
5
@ Alex, "construir" uma tupla é realmente mais rápido que "construir uma lista" ou estamos vendo o resultado do tempo de execução do Python armazenando em cache a tupla? Parece o último para mim.
Triptych
6
@ ACoolie, que é totalmente dominado pelas randomchamadas (tente fazer exatamente isso, você verá!), Portanto, não é muito significativo. Experimente python -mtimeit -s "x=23" "[x,x]"e você verá uma aceleração mais significativa de 2 a 3 vezes para criar a tupla versus criar a lista.
Alex Martelli
9
para qualquer pessoa que esteja se perguntando - fomos capazes de economizar mais de uma hora de processamento de dados alternando de listas para tuplas.
Mark Ribau
42

Nenhuma das respostas acima aponta o problema real das tuplas versus listas, que muitas novas no Python parecem não entender completamente.

Tuplas e listas servem a propósitos diferentes. As listas armazenam dados homogêneos. Você pode e deve ter uma lista como esta:

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

O motivo do uso correto das listas é porque esses são todos os tipos de dados homogêneos, especificamente os nomes das pessoas. Mas faça uma lista como esta:

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

Essa lista é o nome completo de uma pessoa e sua idade. Esse não é um tipo de dados. A maneira correta de armazenar essas informações é em uma tupla ou em um objeto. Digamos que temos alguns:

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

A imutabilidade e mutabilidade de Tuplas e Listas não é a principal diferença. Uma lista é uma lista do mesmo tipo de itens: arquivos, nomes, objetos. Tuplas são um agrupamento de diferentes tipos de objetos. Eles têm usos diferentes, e muitos codificadores Python abusam de listas para o que as tuplas são destinadas.

Por favor não.


Editar:

Eu acho que este post explica porque eu acho isso melhor do que eu: http://news.e-scribe.com/397

Grant Paul
fonte
13
Eu acho que você tem uma visão que não é de acordo, pelo menos por mim, não conhece os outros.
Stefano Borini
13
Também discordo totalmente desta resposta. A homogeneidade dos dados não tem absolutamente nada a ver com o uso de uma lista ou de uma tupla. Nada no Python sugere essa distinção.
Glenn Maynard
14
Guido também fez isso há alguns anos. aspn.activestate.com/ASPN/Mail/Message/python-list/1566320
John La Rooy
11
Embora Guido (o criador do Python) pretenda que as listas sejam usadas para dados homogêneos e as tuplas para heterogêneas, o fato é que a linguagem não impõe isso. Portanto, acho que essa interpretação é mais uma questão de estilo do que qualquer outra coisa. Acontece que, nos casos de uso típicos de muitas pessoas, as listas tendem a ser de matriz e as tuplas tendem a ser de registro. Mas isso não deve impedir as pessoas de usar listas para dados heterogêneos, se eles se adequarem melhor ao seu problema. Como o Zen do Python diz: A praticidade supera a pureza.
John Y
9
@ Glenn, você está basicamente errado. Um dos principais usos das tuplas é como um tipo de dados composto para armazenar vários dados relacionados. O fato de você poder iterar sobre uma tupla e executar muitas das mesmas operações não altera isso. (Como referência, considere que as tuplas em muitos outros idiomas não possuem os mesmos recursos iteráveis ​​que os correspondentes da lista)
HS.
22

se devo converter uma tupla em um conjunto ou lista para poder classificá-las, qual é o sentido de usar uma tupla em primeiro lugar?

Nesse caso em particular, provavelmente não há razão. Esse não é um problema, pois esse não é um dos casos em que você considera usar uma tupla.

Como você aponta, as tuplas são imutáveis. As razões para ter tipos imutáveis ​​se aplicam às tuplas:

  • eficiência de cópia: em vez de copiar um objeto imutável, você pode alterná-lo (vincular uma variável a uma referência)
  • eficiência de comparação: ao usar copiar por referência, você pode comparar duas variáveis ​​comparando a localização, em vez do conteúdo
  • interning: você precisa armazenar no máximo uma cópia de qualquer valor imutável
  • não é necessário sincronizar o acesso a objetos imutáveis ​​em código simultâneo
  • const correção: alguns valores não devem ser alterados. Essa (para mim) é a principal razão para tipos imutáveis.

Observe que uma implementação específica do Python pode não fazer uso de todos os recursos acima.

As chaves do dicionário devem ser imutáveis, caso contrário, alterar as propriedades de um objeto-chave pode invalidar invariantes da estrutura de dados subjacente. Assim, as tuplas podem ser potencialmente usadas como chaves. Isso é uma consequência da correção constante.

Veja também " Introdução às tuplas ", do Dive Into Python .

fora
fonte
2
id ((1,2,3)) == id ((1,2,3)) é falso. Você não pode comparar tuplas apenas comparando o local, porque não há garantia de que elas foram copiadas por referência.
Glenn Maynard
@ Glenn: Observe a observação qualificada "quando você estiver usando a cópia por referência". Embora o codificador possa criar sua própria implementação, copiar por referência para tuplas é uma questão importante para o intérprete / compilador. Eu estava me referindo principalmente a como ==é implementado no nível da plataforma.
outis
1
@ Glenn: observe também que copiar por referência não se aplica às tuplas de (1,2,3) == (1,2,3). É mais uma questão de internar.
Outis
Como eu disse claramente, não há garantia de que eles foram copiados por referência . Tuplas não são internadas em Python; esse é um conceito de string.
Glenn Maynard
Como eu disse muito claramente: não estou falando sobre o programador comparando tuplas comparando localização. Estou falando da possibilidade da plataforma, que pode garantir cópia por referência. Além disso, a internação pode ser aplicada a qualquer tipo imutável, não apenas a cadeias. A principal implementação do Python pode não internar tipos imutáveis, mas o fato de o Python ter tipos imutáveis ​​torna a internação uma opção.
Outis
15

Às vezes gostamos de usar objetos como chaves de dicionário

Pelo que vale, as tuplas recentemente (2.6+) cresceram index()e os count()métodos

John La Rooy
fonte
5
+1: uma lista mutável (ou conjunto mutável ou dicionário mutável) como uma chave de dicionário não pode funcionar. Então, precisamos de listas imutáveis ​​("tuplas"), conjuntos congelados e ... bem ... um dicionário congelado, suponho.
S.Lott
9

Sempre achei que ter dois tipos completamente separados para a mesma estrutura básica de dados (matrizes) era um design estranho, mas na prática não era um problema real. (Toda linguagem tem suas verrugas, inclusive o Python, mas isso não é importante.)

Por que alguém se importa se uma variável vive em um lugar diferente na memória do que quando foi originalmente alocada? Todo esse negócio de imutabilidade em Python parece ter sido enfatizado demais.

Essas são coisas diferentes. A mutabilidade não está relacionada ao local em que está armazenada na memória; significa que as coisas para as quais aponta não podem mudar.

Objetos Python não podem mudar de local após serem criados, mutáveis ​​ou não. (Mais precisamente, o valor de id () não pode mudar - a mesma coisa, na prática.) O armazenamento interno de objetos mutáveis ​​pode mudar, mas esse é um detalhe oculto da implementação.

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

Isso não está modificando ("mutando") a variável; está criando uma nova variável com o mesmo nome e descartando a antiga. Compare com uma operação de mutação:

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

Como outros já apontaram, isso permite usar matrizes como chaves para dicionários e outras estruturas de dados que precisam de imutabilidade.

Observe que as chaves dos dicionários não precisam ser completamente imutáveis. Somente a parte usada como chave precisa ser imutável; para alguns usos, essa é uma distinção importante. Por exemplo, você pode ter uma classe representando um usuário, que compara igualdade e um hash pelo nome de usuário exclusivo. Você pode então pendurar outros dados mutáveis ​​na classe - "o usuário está logado" etc. etc. Como isso não afeta a igualdade ou o hash, é possível e perfeitamente válido usá-lo como chave em um dicionário. Isso não é muito comum em Python; Apenas aponto, já que várias pessoas alegaram que as chaves precisam ser "imutáveis", o que é apenas parcialmente correto. Eu usei isso muitas vezes com mapas e conjuntos de C ++.

Glenn Maynard
fonte
>>> a = [1,2,3] >>> identificação (a) 3084599212L >>> a [1] = 5 >>> a [1, 5, 3] >>> identificação (a) 3084599212L Você ' acabamos de modificar um tipo de dados mutáveis, para que não faça sentido em relação à pergunta original. x = 'olá' id (x) 12345 x = "adeus" id (x) 65432 Quem se importa se é um novo objeto ou não. Contanto que x aponte para os dados que eu designei, isso é tudo o que importa.
pyNewGuy
4
Você está confuso muito além da minha capacidade de ajudá-lo.
Glenn Maynard
+1 por apontar confusão nas sub-perguntas, que parecem ser a principal fonte de dificuldade em perceber o valor das tuplas.
Outis
1
Se eu pudesse, outro +1 para apontar que a verdadeira rubrica para chaves é se o objeto é ou não lavável ( docs.python.org/glossary.html#term-hashable ).
Outis
7

Como o gnibbler ofereceu em um comentário, Guido teve uma opinião que não é totalmente aceita / apreciada: “listas são para dados homogêneos, tuplas são para dados heterogêneos”. Certamente, muitos dos opositores interpretaram isso como significando que todos os elementos de uma lista devem ser do mesmo tipo.

Eu gosto de ver de maneira diferente, não muito diferente dos outros no passado:

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

Note que eu considero alist homogêneo, mesmo que type (alist [1])! = Type (alist [2]).

Se eu puder alterar a ordem dos elementos e não apresentar problemas no meu código (além de suposições, por exemplo, "ele deve ser classificado"), uma lista deverá ser usada. Caso contrário (como na tupla blueacima), devo usar uma tupla.

tzot
fonte
Se eu pudesse votar nesta resposta 15 vezes. É exatamente assim que me sinto em relação às tuplas.
Grant Paul
6

Eles são importantes, pois garantem ao chamador que o objeto pelo qual passarem não será alterado. Se você fizer isto:

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

O chamador não tem garantia do valor de a após a chamada. Contudo,

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

Agora você, como chamador ou como leitor deste código, sabe que a é o mesmo. Você sempre pode, nesse cenário, fazer uma cópia da lista e passar isso, mas agora está desperdiçando ciclos em vez de usar uma construção de linguagem que faz mais sentido semântico.

Matthew Manela
fonte
1
Esta é uma propriedade muito secundária de tuplas. Existem muitos casos em que você tem um objeto mutável que deseja passar para uma função e não o modifica, seja uma lista preexistente ou alguma outra classe. Não existe um conceito de "parâmetros const por referência" no Python (por exemplo, const foo & em C ++). As tuplas fornecem isso a você, se for conveniente usar uma tupla, mas se você recebeu uma lista do seu interlocutor, você realmente a converterá em uma tupla antes de passá-la para outro lugar?
Glenn Maynard
Eu concordo com você nisso. Uma tupla não é o mesmo que digitar uma palavra-chave const. O que quero dizer é que a imutabilidade de uma tupla traz um significado adicional ao leitor do código. Dada uma situação onde ambos iria funcionar e sua expectativa é que ele não deve mudar usando o tuple irá adicionar esse significado extra para o leitor (enquanto garantindo que tão bem)
Matthew Manela
a = [1,1,1] doWork (a) se dowork () for definido como def dowork (arg): arg = [0,0,0] chamar dowork () em uma lista ou tupla tem o mesmo resultado
pyNewGuy
1

você pode ver aqui para alguma discussão sobre isso

ghostdog74
fonte
1

Sua pergunta (e comentários de acompanhamento) concentram-se em saber se o id () muda durante uma tarefa. Focar esse efeito subsequente da diferença entre substituição imutável de objetos e modificação mutável de objetos, em vez da diferença em si, talvez não seja a melhor abordagem.

Antes de continuarmos, verifique se o comportamento demonstrado abaixo é o que você espera do Python.

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

Nesse caso, o conteúdo de a2 foi alterado, mesmo que apenas a1 tenha um novo valor atribuído. Contraste com o seguinte:

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

Neste último caso, substituímos a lista inteira, em vez de atualizar seu conteúdo. Com tipos imutáveis, como tuplas, esse é o único comportamento permitido.

Por que isso importa? Digamos que você tenha um ditado:

>>> 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'}

Usando uma tupla, o dicionário pode evitar que suas chaves sejam alteradas "de baixo para baixo" para itens com hash com um valor diferente. Isso é crítico para permitir uma implementação eficiente.

Charles Duffy
fonte
Como outros já apontaram, imutabilidade! = Hashability. Nem todas as tuplas podem ser usadas como chaves de dicionário: {([1], [2]): 'value'} falha porque as listas mutáveis ​​da tupla podem ser alteradas, mas {((1), (2)): ' value '} está OK.
Ned Deily
Ned, isso é verdade, mas não tenho certeza de que a distinção seja pertinente à pergunta que está sendo feita.
Charles Duffy
@ K.Nicholas, a edição que você aprovou aqui alterou o código de forma a atribuir um número inteiro, não uma tupla, fazendo com que as operações de índice posteriores falhem, por isso não poderiam ter testado se o novo transcrição era realmente possível. Problema corretamente identificado, com certeza; solução inválida.
Charles Duffy
@MichaelPuckettII, da mesma forma, veja acima.
Charles Duffy