Python | 関数:id

Python
スポンサーリンク

概要(id は「そのオブジェクトだけに割り当てられた識別値」を返す)

id は、渡したオブジェクトの“同一性”を表す識別値(identity)を返す組み込み関数です。これにより「2つの変数が同じオブジェクトを指しているのか」「コピーしたつもりが参照共有になっていないか」を、瞬時に見分けられます。表示は整数ですが、意味は「その実体を区別するためのラベル」です。

print(id(10))         # 例:整数の識別値(identity)
print(id("hello"))    # 例:文字列の識別値
print(id([1, 2, 3]))  # 例:リストの識別値
Python

基本動作と「同一性」の考え方(ここが重要)

代入は“同じものへの別名”、id は同じ値を返す

変数へ代入すると、同じオブジェクトを指す“別名”ができます。id を比べると一致し、「同じ実体」であることが分かります。

a = [1, 2]
b = a
print(id(a), id(b))      # 同じ値
b.append(3)
print(a)                 # [1, 2, 3](同じオブジェクトに変更が反映)
Python

コピーすると“別の実体”、id は異なる

浅いコピーでも深いコピーでも、新しいコンテナ(外側)が作られます。id は異なり、「別物」であることが確認できます(ただし浅いコピーは内側要素の参照を共有します)。

import copy

a = [[1], [2]]
b = a.copy()             # 浅いコピー
c = copy.deepcopy(a)     # 深いコピー

print(id(a), id(b), id(c))        # a と b/c は異なる
print(id(a[0]), id(b[0]))         # 浅いコピーは内側参照を共有(同じ)
print(id(a[0]), id(c[0]))         # 深いコピーは内側も別物(異なる)
Python

同一性・等価性・型判定の線引き(id / is / == / isinstance を深掘り)

id と is は“同一性”の確認、== は“値の等価性”

  • id(x) と id(y) が同じ、または x is y が True → 同じ実体(同一オブジェクト)
  • x == y が True → 値として等しい(別オブジェクトでもあり得る)
x = [1, 2]
y = [1, 2]
print(x == y)        # True(値が等しい)
print(x is y)        # False(別オブジェクト)
print(id(x), id(y))  # 異なる
Python

isinstance は「その型として扱えるか」の判定

isinstance(x, 型または型のタプル) は、継承を含めた「型ガード」。同一性ではなく、“扱い方”のための判定です。用途が違うので、混同しないこと。

class A: pass
class B(A): pass
b = B()
print(isinstance(b, A))  # True(A系として扱える)
Python

よくある挙動と注意点(イミュータブル・ミュータブル・最適化)

イミュータブルは“再利用されることがある”

整数や短い文字列などの不変オブジェクトは、実装によって同じ値を再利用することがあります。そのため、同じ値を別変数に代入しても id が一致するケースを観測できます。これは“仕様依存の最適化”であり、常にそうなると期待してロジックを組むべきではありません。

a = 10
b = 10
print(id(a), id(b))  # 一致することがある(最適化)

s1 = "hello"
s2 = "hello"
print(id(s1), id(s2))  # こちらも一致することがある
Python

ミュータブルは“変更すれば実体は同じまま中身が変わる”

リスト・辞書・集合などは、変更してもオブジェクト自体は同じ(id 不変)です。参照共有のまま中身だけ変わるため、意図せぬ共有がないかを id で確認すると安心です。

items = [1, 2]
print(id(items))
items.append(3)
print(id(items))  # 変わらない(同じオブジェクトの中身が変化)
Python

デバッグと設計での実用(参照共有の検出・防御的コーディング)

「共有されてしまった?」を即座に見抜く

関数に渡した引数を中で変更したら呼び出し元に影響するのか——ミュータブル引数の扱いはバグの温床です。id をログに出すだけで、共有かコピーかが明快になります。

def add_tag_inplace(post, tag):
    print("post id:", id(post))
    post.setdefault("tags", []).append(tag)

p = {"title": "coffee"}
add_tag_inplace(p, "drink")
add_tag_inplace(p, "hot")
print(p)  # {'title': 'coffee', 'tags': ['drink', 'hot']}
Python

“新しいものを返す”設計で副作用を避ける

中身を変えず新しいコンテナを返すと、id が変わり副作用を避けられます。テスト容易性も上がります。

def add_user(users, name):
    new_users = users + [name]
    print("old id:", id(users), "new id:", id(new_users))
    return new_users

u = ["taro"]
u2 = add_user(u, "hanako")  # 別オブジェクトを返す
Python

実践例(定番から一歩先まで)

例題1:同一性の確認と値の等価性の違い

a = [1, 2]
b = a
c = [1, 2]
print(a == c)    # True(値は等しい)
print(a is c)    # False(別物)
print(a is b)    # True(同一)
Python

例題2:浅いコピーと深いコピーの id を比べる

import copy

src = [{"x": 1}, {"x": 2}]
shallow = src.copy()
deep = copy.deepcopy(src)

print(id(src), id(shallow), id(deep))            # 外側は全部異なる
print(id(src[0]), id(shallow[0]))                # 浅いコピーは内側共有(同じ)
print(id(src[0]), id(deep[0]))                   # 深いコピーは内側も別(異なる)
Python

例題3:イミュータブルの“最適化っぽい”挙動を観測

a = 100
b = 100
print(id(a) == id(b))  # True になることがある(実装の最適化)

s1 = "tea"
s2 = "t" + "ea"        # コンパイル時に最適化されることがある
print(id(s1) == id(s2))  # True になることがある
Python

例題4:引数が“同一オブジェクト”かどうかで分岐

def maybe_inplace(dst, src):
    if dst is src:
        # 同一オブジェクトなら何もしない(無駄な処理を避ける)
        return dst
    # 別物なら安全にコピーして結合
    return dst + src

x = [1]
print(maybe_inplace(x, x))      # 同一 → そのまま
print(maybe_inplace([1], [2]))  # 別物 → 新規作成
Python

まとめ

id は「同一性」を確認するための基礎ツールです。代入は同一(id 一致)、コピーは別物(id 不一致)。値の等価性(==)や“扱えるか”の型判定(isinstance)とは目的が違います。イミュータブルには実装依存の再利用(同じ id を観測)があり得るため、それを前提にロジックを組まないこと。ミュータブルの参照共有は id ログで早期発見、設計は“新しいものを返す”方針で副作用を最小化する——この感覚を体に入れると、デバッグも設計も一段クリアに進みます。

タイトルとURLをコピーしました