Python文字列インターン


92

この質問は実際には実際には使用されませんが、Pythonが文字列インターンをどのように行うかについて興味があります。私は次のことに気づきました。

>>> "string" is "string"
True

思った通りです。

これを行うこともできます。

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

そして、それはかなり賢いです!

しかし、これを行うことはできません。

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

なぜPythonは評価せずs1+"g"、それが同じであることに気づきs2、同じアドレスを指すのでしょうか?その最後のブロックで実際に何が起こっているのFalseでしょうか?

回答:


95

これは実装固有ですが、インタプリタはおそらくコンパイル時定数をインターンしていますが、実行時式の結果はインターンしていません。

以下では、CPython2.7.3を使用します。

2番目の例では、式"strin"+"g"はコンパイル時に評価され、に置き換えられ"string"ます。これにより、最初の2つの例は同じように動作します。

バイトコードを調べると、まったく同じであることがわかります。

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

3番目の例には、実行時の連結が含まれ、その結果は自動的にインターンされません。

  # 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        

intern()3番目の式の結果を手動で取得すると、以前と同じオブジェクトが得られます。

>>> s3a = "strin"
>>> s3 = s3a + "g"
>>> s3 is "string"
False
>>> intern(s3) is "string"
True

22
そして、レコードの:Pythonののぞき穴最適化(定数になります事前に計算演算"string1" + "s2"10 + 3*20コンパイル時など、)が、得られる限界のシーケンスだけで20の要素(防ぐためにを [None] * 10**1000過度にあなたのバイトコードを拡大してから)。に崩壊"strin" + "g"したのはこの最適化"string"です。結果は20文字より短くなります。
MartijnPieters

13
そしてそれを二重に明確にするために:ここではインターンはまったく行われていません。代わりに、不変のリテラルがバイトコードとともに定数として格納されます。コードで使用される名前に対してインターン行われますが、intern()関数によって特にインターンされない限り、プログラムによって作成された文字列値に対しては行われません。
MartijnPieters

9
internPython 3で関数を見つけようとする人のために、それはsys.internに
Chernousov

1

ケース1

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

ケース2

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

ここで、質問は、IDがケース2
ではなくケース1で同じである理由です。ケース1では、文字列リテラル"123"xとに割り当てましたy

文字列は不変であるため、インタプリタが文字列リテラルを1回だけ格納し、すべての変数を同じオブジェクトにポイントすることは理にかなっています。
したがって、IDは同一であると見なされます。

ケース2では、x連結を使用して変更しています。xyは両方とも同じ値を持ちますが、同じアイデンティティではありません。
どちらもメモリ内の異なるオブジェクトを指しています。したがって、それらは異なりidis演算子が返されますFalse


文字列は不変であるため、x + "3"を割り当てる(そして文字列を格納するための新しい場所を探す)と、yと同じ参照が割り当てられないのはなぜですか?
nicecatch 2016

その場合、新しい文字列を既存のすべての文字列と比較する必要があるためです。潜在的に非常に費用のかかる操作。割り当て後にバックグラウンドでこれを実行してメモリを削減することはできますがid(x) != id(x)、評価の過程で文字列が移動されたためなど、さらに奇妙な動作になってしまいます。
DylanYoung 2017

1
@AndreaConteは、文字列の連結では、新しい文字列を生成するたびに、使用されているすべての文字列のプールを検索するという余分な作業が行われないためです。一方、インタプリタは式x = "12" + "3"x = "123"(1つの式内の2つの文字列リテラルの連結)に「最適化」するため、割り当ては実際にルックアップを実行し、と同じ「内部」文字列を見つけy = "123"ます。
derenio 2017

実際には、ソースコードからのすべての文字列リテラルが「内部化」され、そのオブジェクトが他のすべての場所で再利用されるのではなく、割り当てによってルックアップが実行されるわけではありません。
derenio 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.