Stage de chaîne Python

91

Bien que cette question n'ait aucune utilité réelle dans la pratique, je suis curieux de savoir comment Python effectue l'internement de chaînes. J'ai remarqué ce qui suit.

>>> "string" is "string"
True

C'est comme je m'y attendais.

Vous pouvez également le faire.

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

Et c'est assez intelligent!

Mais tu ne peux pas faire ça.

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

Pourquoi Python n'évaluerait-il pas, ne se rendrait-il pas s1+"g"compte que c'est la même chose s2et ne le ferait-il pas pointer vers la même adresse? Que se passe-t-il réellement dans ce dernier bloc pour qu'il revienne False?

Ze'ev G
la source

Réponses:

94

Ceci est spécifique à l'implémentation, mais votre interpréteur est probablement en train d'interner des constantes au moment de la compilation, mais pas les résultats des expressions d'exécution.

Dans ce qui suit, j'utilise CPython 2.7.3.

Dans le deuxième exemple, l'expression "strin"+"g"est évaluée au moment de la compilation et est remplacée par "string". Cela fait que les deux premiers exemples se comportent de la même manière.

Si nous examinons les bytecodes, nous verrons qu'ils sont exactement les mêmes:

  # 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)

Le troisième exemple implique une concaténation d'exécution, dont le résultat n'est pas automatiquement interné:

  # 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        

Si vous deviez manuellement intern()le résultat de la troisième expression, vous obtiendrez le même objet qu'auparavant:

>>> s3a = "strin"
>>> s3 = s3a + "g"
>>> s3 is "string"
False
>>> intern(s3) is "string"
True
NPE
la source
21
Et pour le disque: optimisation peep-trous de Python précalculer des opérations arithmétiques sur des constantes ( "string1" + "s2", 10 + 3*20, etc.) au moment de la compilation, mais les limites résultant des séquences à seulement 20 éléments (pour éviter [None] * 10**1000de se dilater excessivement votre bytecode). C'est cette optimisation qui s'est effondrée "strin" + "g"en "string"; le résultat est inférieur à 20 caractères.
Martijn Pieters
13
Et pour être doublement clair: il n'y a pas du tout de stage ici. Les littéraux immuables sont à la place stockés sous forme de constantes avec le bytecode. Interner ne lieu pour les noms utilisés dans le code, mais pas pour les valeurs de chaîne créées par le programme , sauf si interné spécifiquement par la intern()fonction.
Martijn Pieters
9
Pour ceux qui essaient de trouver une internfonction dans Python 3 - il est déplacé vers sys.intern
Timofey Chernousov
1

Cas 1

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

Cas 2

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

Maintenant, votre question est de savoir pourquoi l'identifiant est le même dans le cas 1 et non dans le cas 2.
Dans le cas 1, vous avez assigné une chaîne littérale "123"à xet y.

Comme les chaînes sont immuables, il est logique que l'interpréteur stocke le littéral de chaîne une seule fois et pointe toutes les variables vers le même objet.
Par conséquent, vous voyez l'identifiant comme identique.

Dans le cas 2, vous modifiez à l' xaide de la concaténation. Les deux xet yont les mêmes valeurs, mais pas la même identité.
Les deux pointent vers différents objets en mémoire. Par conséquent, ils ont différents idet l' isopérateur est retournéFalse

cppcoder
la source
Comment se fait-il que, puisque les chaînes sont immuables, l'affectation de x + "3" (et la recherche d'un nouvel emplacement pour stocker la chaîne) n'assigne pas la même référence que y?
nicecatch
Parce qu'il doit alors comparer la nouvelle chaîne avec toutes les chaînes existantes; opération potentiellement très coûteuse. Cela pourrait le faire en arrière-plan après l'affectation, je suppose, pour réduire la mémoire, mais vous vous retrouveriez alors avec un comportement encore plus étrange: id(x) != id(x)par exemple, parce que la chaîne a été déplacée dans le processus d'évaluation.
DylanYoung
1
@AndreaConte parce que la concaténation des chaînes ne fait pas le travail supplémentaire de rechercher dans le pool de toutes les chaînes utilisées à chaque fois qu'elle en génère une nouvelle. D'un autre côté, l'interpréteur "optimise" l'expression x = "12" + "3"en x = "123"(concaténation de deux chaînes littérales dans une seule expression) de sorte que l'affectation effectue réellement la recherche et trouve la même chaîne "interne" que pour y = "123".
derenio
En fait, ce n'est pas cette affectation qui effectue la recherche plutôt que chaque chaîne littérale du code source est "internalisée" et cet objet est réutilisé dans tous les autres endroits.
derenio