Respostas:
Essa é uma excelente pergunta.
O principal insight é que as tuplas não têm como saber se os objetos dentro delas são mutáveis. A única coisa que torna um objeto mutável é ter um método que altere seus dados. Em geral, não há como detectar isso.
Outra percepção é que os contêineres do Python não contêm nada. Em vez disso, eles mantêm referências a outros objetos. Da mesma forma, as variáveis do Python não são como variáveis nas linguagens compiladas; em vez disso, os nomes das variáveis são apenas chaves em um dicionário de namespace onde estão associados a um objeto correspondente. Ned Batchhelder explica isso muito bem em seu blog . De qualquer maneira, os objetos conhecem apenas sua contagem de referência; eles não sabem o que são essas referências (variáveis, contêineres ou os elementos internos do Python).
Juntas, essas duas idéias explicam seu mistério (por que uma tupla imutável "contendo" uma lista parece mudar quando a lista subjacente muda). De fato, a tupla não foi alterada (ainda possui as mesmas referências a outros objetos que antes). A tupla não pôde ser alterada (porque não havia métodos de mutação). Quando a lista foi alterada, a tupla não foi notificada da alteração (a lista não sabe se é referida por uma variável, uma tupla ou outra lista).
Enquanto estamos no assunto, aqui estão alguns outros pensamentos para ajudar a completar seu modelo mental do que são as tuplas, como elas funcionam e o uso pretendido:
As tuplas são caracterizadas menos por sua imutabilidade e mais por seu objetivo pretendido.
Tuplas são a maneira do Python de coletar informações heterogêneas sob o mesmo teto. Por exemplo,
s = ('www.python.org', 80)
reúne uma sequência e um número para que o par host / porta possa ser transmitido como um soquete, um objeto composto. Visto sob essa luz, é perfeitamente razoável ter componentes mutáveis.
A imutabilidade anda de mãos dadas com outra propriedade, a hashability . Mas a capacidade de habitar não é uma propriedade absoluta. Se um dos componentes da tupla não for lavável, a tupla geral também não será lavável. Por exemplo, t = ('red', [10, 20, 30])
não é lavável.
O último exemplo mostra uma tupla 2 que contém uma sequência e uma lista. A tupla em si não é mutável (ou seja, não possui métodos para alterar seu conteúdo). Da mesma forma, a string é imutável porque as strings não possuem métodos de mutação. O objeto de lista possui métodos de mutação, para que possa ser alterado. Isso mostra que a mutabilidade é uma propriedade de um tipo de objeto - alguns objetos têm métodos de mutação e outros não. Isso não muda apenas porque os objetos estão aninhados.
Lembre-se de duas coisas. Primeiro, imutabilidade não é mágica - é apenas a ausência de métodos de mutação. Segundo, os objetos não sabem quais variáveis ou contêineres se referem a eles - eles conhecem apenas a contagem de referência.
Espero que isso tenha sido útil para você :-)
hash()
porque tudo o que herda de object () é lavável e, portanto, as subclasses precisam desativar explicitamente o hash. 2) Hashibility não garante imutabilidade - é fácil criar exemplos de objetos hash que são mutáveis. 3) Tuplas, como a maioria dos contêineres em Python, apenas têm referências ao objeto subjacente - elas não têm obrigação de inspecioná-las e fazer inferências sobre elas.
Isso porque as tuplas não contêm listas, strings ou números. Eles contêm referências a outros objetos . 1 A incapacidade de alterar a sequência de referências que uma tupla contém não significa que você não pode alterar os objetos associados a essas referências. 2
1. Objetos, valores e tipos (veja: segundo ao último parágrafo)
2. A hierarquia de tipos padrão (veja: "Sequências imutáveis")
Antes de tudo, a palavra "imutável" pode significar muitas coisas diferentes para pessoas diferentes. Gosto particularmente de como Eric Lippert categorizou a imutabilidade em seu blog . Lá, ele lista esses tipos de imutabilidade:
Eles podem ser combinados de várias maneiras para criar ainda mais tipos de imutabilidade, e tenho certeza de que existem mais. O tipo de imutabilidade em que você parece interessado em imutabilidade profunda (também conhecida como transitiva), na qual objetos imutáveis podem conter apenas outros objetos imutáveis.
O ponto principal disso é que a imutabilidade profunda é apenas um de muitos, muitos tipos de imutabilidade. Você pode adotar o tipo que preferir, desde que saiba que sua noção de "imutável" provavelmente difere da noção de "imutável" de outra pessoa.
Pelo que entendi, essa pergunta precisa ser reformulada como uma pergunta sobre decisões de design: Por que os designers do Python escolheram criar um tipo de sequência imutável que pode conter objetos mutáveis?
Para responder a essa pergunta, temos que pensar no objetivo das tuplas : elas servem como sequências rápidas e de propósito geral . Com isso em mente, torna-se bastante óbvio porque as tuplas são imutáveis, mas podem conter objetos mutáveis. A saber:
As tuplas são rápidas e eficientes na memória: as tuplas são mais rápidas de criar do que as listas porque são imutáveis. Imutabilidade significa que as tuplas podem ser criadas como constantes e carregadas como tal, usando dobras constantes . Isso também significa que eles são mais rápidos e mais eficientes na criação de memória, porque não há necessidade de alocação geral, etc. Eles são um pouco mais lentos que as listas para acesso aleatório a itens, mas mais rápidos novamente para descompactar (pelo menos na minha máquina). Se as tuplas fossem mutáveis, elas não seriam tão rápidas para propósitos como esses.
As tuplas são de uso geral : as tuplas precisam ser capazes de conter qualquer tipo de objeto. Eles estão acostumados (rapidamente) a fazer coisas como listas de argumentos de comprimento variável (via *
operador nas definições de função). Se as tuplas não pudessem conter objetos mutáveis, elas seriam inúteis para coisas assim. O Python teria que usar listas, o que provavelmente atrasaria as coisas e certamente seria menos eficiente em termos de memória.
Como você vê, para cumprir seu objetivo, as tuplas devem ser imutáveis, mas também devem poder conter objetos mutáveis. Se os designers do Python quisessem criar um objeto imutável que garanta que todos os objetos que ele "contém" também sejam imutáveis, eles teriam que criar um terceiro tipo de sequência. O ganho não vale a complexidade extra.
Você não pode alterar os id
itens. Por isso, sempre conterá os mesmos itens.
$ python
>>> t = (1, [2, 3])
>>> id(t[1])
12371368
>>> t[1].append(4)
>>> id(t[1])
12371368
Vou falar sobre isso aqui e dizer que a parte relevante aqui é que, embora você possa alterar o conteúdo de uma lista ou o estado de um objeto contido em uma tupla, o que você não pode mudar é que o objeto ou lista está lá. Se você tivesse algo que dependesse de uma lista [3], mesmo que vazia, eu poderia ver isso sendo útil.
Uma razão é que não existe uma maneira geral no Python de converter um tipo mutável em imutável (consulte o PEP 351 rejeitado e a discussão vinculada sobre o motivo pelo qual ele foi rejeitado). Portanto, seria impossível colocar vários tipos de objetos nas tuplas se houvesse essa restrição, incluindo praticamente qualquer objeto não hashable criado pelo usuário.
A única razão pela qual dicionários e conjuntos têm essa restrição é que eles exigem que os objetos sejam laváveis, uma vez que são implementados internamente como tabelas de hash. Mas observe que, ironicamente, dicionários e conjuntos em si não são imutáveis (ou hashable). As tuplas não usam o hash de um objeto, portanto, sua mutabilidade não importa.
Uma tupla é imutável no sentido de que a própria tupla não pode expandir ou encolher, nem que todos os itens contidos sejam imutáveis. Caso contrário, as tuplas são opacas.