Python / オブジェクトと変数
Pythonのオブジェクトは値と動作を持つデータで、変数はそのオブジェクトに結び付いた名前と考えると理解しやすくなります。x = 100はxを整数オブジェクトに結び付け、別の値を代入すると参照先が変わります。代入だけではコピーされず、複数の名前で同じリストを共有できます。
名前・オブジェクト・型の関係
Pythonのデータモデルでは、オブジェクトには同一性(identity)、型(type)、値(value)があります。型は使える演算や性質を決め、同一性はオブジェクトの生存中に維持されます。値を変更できるかは型によります。変数を固定型の箱と捉えると、再代入や共有を説明しにくくなります。
例はPython 3と標準ライブラリだけを使い、Python 3.12.14で出力を確認しました。完全なコードをUTF-8のobjects_demo.pyに保存し、ターミナルでpython objects_demo.pyを実行します。環境によってはpython3です。各例はファイルを置き換えて個別に実行します。
value = 100
print(value, type(value).__name__)
value = "hello"
print(value, type(value).__name__)
100 int
hello str
valueは最初にint、次にstrを参照します。整数100の型自体を変更したわけではありません。代入文は右辺を評価して、その結果を左側の名前に結び付けます。type(value)はその時点の参照先の型を調べます。
リスト共有と名前の再代入
second = firstはリストを複製せず、両方の名前を同じリストに結び付けます。共有するオブジェクトの変更と、名前だけの再代入を比較します。
first = [1, 2]
second = first
second.append(3)
print(first)
print(second)
print(first is second)
first = [0]
print(first)
print(second)
print(first is second)
[1, 2, 3]
[1, 2, 3]
True
[0]
[1, 2, 3]
False
appendは共有リストを変更するため、firstから見ても3が追加されています。一方、first = [0]はfirstだけを新しいリストに結び付けます。secondの参照先は元のままです。名前の結び付きを変えることと、参照先の内容を変えることを区別します。
| 操作 | 意味 | 共有する別名への影響 |
|---|---|---|
second = first |
同じオブジェクトの名前を増やす | 後の変更を共に観察 |
second.append(3) |
リスト自体を変更 | 同じリストから変更後の値を読む |
first = [0] |
firstを別の参照先へ結び直す | secondの結び付きは維持 |
不変オブジェクトと+=
整数・文字列・タプルは不変(immutable)、リスト・辞書は可変(mutable)です。不変とは、そのオブジェクトをその場で変更できないことであり、名前に別の値を代入できないことではありません。
first = 10
second = first
first += 1
print(first)
print(second)
11
10
整数10を11に書き換えたのではなく、firstを加算結果へ結び直しています。secondは10のままです。同じ+=でもリストでは既存オブジェクトを変更できるため、すべての型で再代入と同じとは限りません。タプル内の参照先は差し替えられませんが、含まれるリスト自体の内容は変更できます。
必要な深さでコピーする
リストのcopy()は外側だけを新しくする浅いコピーで、内側のオブジェクトは共有します。同じ元データでcopy.deepcopy()と比較します。
from copy import deepcopy
original = [[1], [2]]
shallow = original.copy()
deep = deepcopy(original)
shallow[0].append(9)
shallow.append([3])
print(original)
print(shallow)
print(deep)
print(original is shallow)
print(original[0] is shallow[0])
[[1, 9], [2]]
[[1, 9], [2], [3]]
[[1], [2]]
False
True
shallowの内側へ9を追加するとoriginalにも現れますが、外側へ[3]を追加してもoriginalの要素数は増えません。deepはコピー時点の入れ子内容を独立して保持します。共有が意図的か判断して必要な範囲だけをコピーします。深いコピーにはコストがあり、ファイルやソケットなどの資源を通常データのように複製する手段でもありません。
==とisは目的が異なる
組み込みリストの==は内容、isは同じオブジェクトそのものかを比較します。次のリストは内容が等しくても別々に作られています。
left = [1, 2]
right = [1, 2]
alias = left
print(left == right)
print(left is right)
print(left is alias)
print(id(left) == id(alias))
missing = None
print(missing is None)
True
False
True
True
True
比較演算子を目的に応じて使います。id()は同一性を示す整数で、実際の数値は実行ごとに変わり得ます。すべてのPython実装でメモリアドレスという保証もありません。数値・文字列の比較にisを使ってキャッシュや再利用に依存しないでください。値には==、None確認にはis Noneを使います。
delは名前の結び付きを削除する文
delは関数ではなく文です。del firstは名前の結び付きを取り除きます。同じオブジェクトを別名が参照していれば、その別名から引き続き利用できます。
first = [1, 2]
second = first
del first
print(second)
try:
print(first)
except NameError:
print("first: NameError")
try:
del unknown_name
except NameError:
print("unknown_name: NameError")
[1, 2]
first: NameError
unknown_name: NameError
secondはリストを出力しますが、firstの参照はNameErrorです。未定義名の削除も同様です。del文は対象によって項目や属性も削除するため、常に名前だけを消すわけではありません。1つの名前の削除でオブジェクトやメモリが即座に解放される保証はありません。ファイルはdelに頼らずwithやcloseで閉じます。
変数名の文法と読みやすさ
英字なら文字かアンダースコアで始め、2文字目以降に数字を使えます。空白・ハイフンは使えず、大文字小文字を区別します。許可されたUnicode文字も使えるため「英字のみ」という規則ではありません。
import keyword
for name in ["user_name", "2name", "class", "first-name", "café", "match"]:
valid = name.isidentifier() and not keyword.iskeyword(name)
print(name, valid)
print("match soft keyword:", keyword.issoftkeyword("match"))
user_name True
2name False
class False
first-name False
café True
match True
match soft keyword: True
isidentifier()だけではclassを除けないため、keyword.iskeyword()も使います。この環境のmatchは通常の予約語ではなく、特定の文脈で特別な意味を持つソフトキーワードです。識別子とキーワードの文法を確認します。一覧はバージョンで変わる可能性があるため、使用中のPythonのkeywordで調べます。
文法上有効でもlistやstrで組み込み名を隠すと、元の機能を呼びにくくなります。user_nameやitem_countなど役割の分かる名前を選びます。NameErrorでは大文字小文字、代入済みか、名前が有効なスコープを確認します。




