Python string interning

91

Embora essa pergunta não tenha nenhum uso real na prática, estou curioso para saber como o Python faz o estágio de string. Eu percebi o seguinte.

>>> "string" is "string"
True

Isso é o que eu esperava.

Você também pode fazer isso.

>>> "strin"+"g" is "string"
True

E isso é muito inteligente!

Mas você não pode fazer isso.

>>> s1 = "strin"
>>> s2 = "string"
>>> s1+"g" is s2
False

Por que o Python não avaliaria s1+"g"e perceberia que é o mesmo s2e apontaria para o mesmo endereço? O que realmente está acontecendo naquele último bloco para que ele volte False?

Ze'ev G
fonte

Respostas:

94

Isso é específico da implementação, mas seu interpretador provavelmente está internando constantes de tempo de compilação, mas não os resultados de expressões de tempo de execução.

A seguir, uso o CPython 2.7.3.

No segundo exemplo, a expressão "strin"+"g"é avaliada em tempo de compilação e é substituída por "string". Isso faz com que os dois primeiros exemplos tenham o mesmo comportamento.

Se examinarmos os bytecodes, veremos que eles são exatamente os mesmos:

  # s1 = "string"
  2           0 LOAD_CONST               1 ('string')
              3 STORE_FAST               0 (s1)

  # s2 = "strin" + "g"
  3           6 LOAD_CONST               4 ('string')
              9 STORE_FAST               1 (s2)

O terceiro exemplo envolve uma concatenação de tempo de execução, o resultado da qual não é automaticamente internado:

  # s3a = "strin"
  # s3 = s3a + "g"
  4          12 LOAD_CONST               2 ('strin')
             15 STORE_FAST               2 (s3a)

  5          18 LOAD_FAST                2 (s3a)
             21 LOAD_CONST               3 ('g')
             24 BINARY_ADD          
             25 STORE_FAST               3 (s3)
             28 LOAD_CONST               0 (None)
             31 RETURN_VALUE        

Se você fosse manualmente intern()o resultado da terceira expressão, você obteria o mesmo objeto de antes:

>>> s3a = "strin"
>>> s3 = s3a + "g"
>>> s3 is "string"
False
>>> intern(s3) is "string"
True
NPE
fonte
21
E para o registro: otimização olho mágico do Python vai operações aritméticas sobre constantes (a calcular pré "string1" + "s2", 10 + 3*20, etc.) em tempo de compilação, mas limites decorrentes seqüências para apenas 20 elementos (para evitar [None] * 10**1000a partir excessivamente expandir o seu bytecode). É essa otimização que entrou "strin" + "g"em colapso "string"; o resultado é menor que 20 caracteres.
Martijn Pieters
13
E para deixar isso duplamente claro: não há estágio aqui. Em vez disso, literais imutáveis ​​são armazenados como constantes com o bytecode. Internar não ter lugar para nomes usados no código, mas não para valores de cadeia criadas pelo programa a menos que especificamente internado pela intern()função.
Martijn Pieters
9
Para aqueles que tentam encontrar a internfunção em Python 3 - ela foi movida para sys.intern
Timofey Chernousov
1

Caso 1

>>> x = "123"  
>>> y = "123"  
>>> x == y  
True  
>>> x is y  
True  
>>> id(x)  
50986112  
>>> id(y)  
50986112  

Caso 2

>>> x = "12"
>>> y = "123"
>>> x = x + "3"
>>> x is y
False
>>> x == y
True

Agora, sua pergunta é por que o id é o mesmo no caso 1 e não no caso 2.
No caso 1, você atribuiu um literal de string "123"para xey .

Como a string é imutável, faz sentido para o interpretador armazenar a string literal apenas uma vez e apontar todas as variáveis ​​para o mesmo objeto.
Portanto, você vê o id como idêntico.

No caso 2, você está modificando xusando concatenação. Ambos xe ytêm os mesmos valores, mas não a mesma identidade.
Ambos apontam para objetos diferentes na memória. Portanto, eles têm diferentes ide o isoperador retornouFalse

cppcoder
fonte
Por que, visto que as strings são imutáveis, atribuir x + "3" (e procurar um novo local para armazenar a string) não atribui à mesma referência que y?
Nicecatch de
Porque então ele precisa comparar a nova string com todas as strings existentes; potencialmente uma operação muito cara. Ele poderia fazer isso em segundo plano após a atribuição, suponho, para reduzir a memória, mas então você terminaria com um comportamento ainda mais estranho: id(x) != id(x)por exemplo, porque a string foi movida no processo de avaliação.
Dylan Young,
1
@AndreaConte porque a concatenação de strings não faz o trabalho extra de pesquisar no pool de todas as strings usadas cada vez que gera uma nova. Por outro lado, o interpretador "otimiza" a expressão x = "12" + "3"em x = "123"(concatenação de dois literais de string em uma única expressão), de modo que a atribuição realmente faz a pesquisa e encontra a mesma string "interna" de y = "123".
derenio
Na verdade, não é essa atribuição que faz a pesquisa, em vez de cada string literal do código-fonte ser "internalizada" e esse objeto ser reutilizado em todos os outros lugares.
derenio