📑

Pythonの整数キャッシュの仕組み

2024/12/05に公開

はじめに

先日、X(Twitter)で以下のツイートを見かけました。

https://x.com/clcoding/status/1864406823840674265?s=61

なぜ257の比較ではwrongになるのか?
プログラミング言語では、変数の比較方法やオブジェクト管理の仕組みによって挙動が異なる場合があります。本記事では、Pythonの「整数キャッシュ」とC#の「文字列インターン」を例に、オブジェクト比較について解説します。

Pythonにおけるisと整数キャッシュ

is演算子とは?

Pythonのis演算子は、2つのオブジェクトが同じメモリ位置を参照しているかを確認するために使用されます。これは値そのものを比較する==とは異なります。

a = 256
b = 256
print(a is b)  # True

整数キャッシュの仕組み

Pythonでは、-5から256までの整数は頻繁に使用されるため、メモリ効率を向上させるためにキャッシュされます。この範囲内の整数は同じメモリ位置を参照するため、is演算子で比較してもTrueを返します。
一方で、この範囲外の整数は新しいオブジェクトとして生成されるため、異なるメモリ位置を指します。

a = 257
b = 257
print(a is b)  # False

C#におけるオブジェクト比較:値型と参照型

C#では、オブジェクトの種類(値型か参照型か)によって挙動が異なります。

値型(Value Types)

C#のintやdoubleなどのプリミティブ型は値型に属します。値型は値そのものを保持するため、比較時には値を直接比較します。したがって、Pythonの整数キャッシュのような特別な仕組みは不要です。

int x = 256;
int y = 256;
Console.WriteLine(x == y);  // True

参照型(Reference Types)

文字列(string)やオブジェクトは参照型に属し、通常は参照(メモリ位置)を比較します。ただし、文字列には「文字列インターン」と呼ばれる仕組みがあります。

C#の文字列インターン(String Interning)

C#では、同じリテラル文字列は文字列プーリングによってキャッシュされます。このため、同じ文字列リテラルを持つ変数は、メモリ上で同じオブジェクトを参照します。

string a = "hello";
string b = "hello";
Console.WriteLine(object.ReferenceEquals(a, b));  // True

文字列の生成方法が異なる場合

新しいオブジェクトとして文字列を生成する場合、メモリ位置が異なるため、ReferenceEqualsはFalseを返します。ただし、値自体は等しいため、==はTrueを返します。

string a = "hello";
string b = new string("hello".ToCharArray());
Console.WriteLine(object.ReferenceEquals(a, b));  // False
Console.WriteLine(a == b);  // True

Pythonの整数キャッシュとC#の文字列インターンの違い

特徴 Pythonの整数キャッシュ C#の文字列インターン
対象 -5から256の整数 リテラル文字列
メモリ最適化の目的 頻繁に使われる小さな整数をキャッシュする 同じ文字列リテラルを再利用する
比較演算子の挙動 isはメモリ参照を比較 ReferenceEqualsは参照を比較、==は値を比較
キャッシュ範囲の変更 Python内部に固定(変更不可) ユーザーが明示的にインターンを使用可能

まとめ 2つの言語の比較

  1. Pythonでは、-5から256までの整数がキャッシュされ、同じメモリ位置を指します。これにより、is演算子がTrueになるケースがあります。
  2. C#では、文字列リテラルは文字列プーリングによって最適化されますが、新しい文字列オブジェクトを生成する場合には異なる参照を持ちます。
  3. 注意点として、PythonでもC#でも、メモリ参照を比較する演算子(isやReferenceEquals)を値の比較に使用するのは避けるべきです。

Discussion