Pythonの内部動作とメモリ管理:インポート、スコープ、比較、コピー

モジュールのインポートと検索パス

Pythonがimport文を実行する際、インタプリタは特定の順序でモジュールファイルを検索します。この検索パスはsys.pathで確認でき、文字列のリスト形式で格納されています。

import sys
print(sys.path)

リストの最初の要素は空文字列('')となることが多く、これは現在の作業ディレクトリを示します。モジュールが見つからない場合は、リスト内のパスを順に探索します。

実行時に動的に検索パスを追加したい場合、リストの操作を行います。insert(0, path)を使用すれば、指定したパスを最優先で探索させることが可能です。

sys.path.insert(0, '/user/local/lib/my_modules')

モジュールの再読み込み

一度インポートされたモジュールは、再度import文を実行しても再読み込みされません。開発中にコードを変更し、その変更を反映させたい場合は、importlib.reload()関数を使用する必要があります。

import my_module
import importlib

# コード変更後
importlib.reload(my_module)

名前空間とスコープ

名前空間とは、変数名や関数名がどの範囲で有効かを管理する概念です。例えば、異なるクラスに同じ名前の生徒がいたとして、放送で「生徒」と呼んでも誰を指すか特定できませんが、「A組の生徒」と呼べば特定できます。この「A組」に相当するのが名前空間です。

変数のスコープ(有効範囲)は、その変数が定義された名前空間に依存します。

# config.py
count = 100

def show_config():
    print(f"Config count: {count}")
# main.py
from config import *

count = 999

def show_main():
    print(f"Main count: {count}")

show_config()  # 出力: Config count: 100 (configの名前空間)
show_main()    # 出力: Main count: 999 (mainの名前空間)

関数内からグローバルスコープやローカルスコープの情報を取得するには、組み込み関数globals()locals()を使用します。これらはそれぞれの名前空間の辞書を返します。

オブジェクトの比較: is==

Pythonには等価性を判定する2つの異なる演算子が存在します。

  • is: 両者の参照がメモリ上の同一オブジェクトかを判定します(同一性)。
  • ==: 両者の値が等しいかを判定します(等価性)。
a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)  # True: 値は同じ
print(a is b)  # False: 異なるオブジェクト

__eq__メソッドのカスタマイズ

クラスを定義する際、__eq__特殊メソッドをオーバーライドすることで、==演算子の挙動を独自に定義できます。

class User:
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        if not isinstance(other, User):
            return False
        return self.name == other.name

u1 = User("Alice")
u2 = User("Alice")

print(u1 is u2)  # False: インスタンスは異なる
print(u1 == u2)  # True: __eq__により名前が等しければTrue

オブジェクトのコピー:浅いコピーと深いコピー

浅いコピー (Shallow Copy)

copy.copy()はオブジェクトの浅いコピーを作成します。これは新しいオブジェクトを作りますが、その中身(要素)は元のオブジェクトの参照を共有します。

深いコピー (Deep Copy)

copy.deepcopy()はオブジェクトの深いコピーを作成します。元のオブジェクトと、その中に含まれるすべてのオブジェクトを再帰的に複製するため、完全に独立したコピーになります。

import copy

original = [[1, 2], [3, 4]]
shallow_copied = copy.copy(original)
deep_copied = copy.deepcopy(original)

original[0][0] = 99

print(shallow_copied[0][0])  # 99 (参照を共有しているため影響を受ける)
print(deep_copied[0][0])     # 1  (完全に独立しているため影響を受けない)

不変型における注意点

タプルや文字列などの不変オブジェクトをcopy.copy()した場合、コピーの必要がないため、同一のオブジェクトへの参照が返されます。

import copy

t1 = (1, 2, 3)
t2 = copy.copy(t1)

print(t1 is t2)  # True: 同一オブジェクト

また、リストのスライス表記list[:]や、辞書のdict.copy()list(list)といったコンストラクタ呼び出しも浅いコピーとして機能します。

タグ: Python sys.module namespace object-comparison deep-copy

7月13日 20:50 投稿