Menu
Coddy logo textTech

Singleton パターン

CoddyのPythonジャーニー「Object Oriented Programming」セクションの一部 — レッスン 45/64。

Singleton パターンは、class に instance が 1 つだけ存在することを保証し、それへのグローバルな access ポイントを提供します。これは、database 接続や設定などのリソースに便利です。

基本的な Singleton の実装は次のとおりです。

class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

__new__ メソッドは object の作成を制御します。新しいものを作成する前に、instance がすでに存在するかどうかを Check します。

Singleton クラスの2つの instance を作成します:

singleton1 = Singleton()
singleton2 = Singleton()

両方の変数が同じオブジェクトを参照しているか確認します:

print(singleton1 is singleton2)  # True
print(id(singleton1))            # 同じメモリアドレス
print(id(singleton2))            # 同じメモリアドレス

データベース接続に関するより実践的な例を以下に示します。

class DatabaseConnection:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.connection = "Connected to MySQL database"
            print("Creating new database connection")
        return cls._instance
    
    def query(self, sql):
        return f"Executing: {sql}"

# 最初のアクセスで接続が作成されます
db1 = DatabaseConnection()
print(db1.connection)

# 2回目のアクセスで同じ接続が再利用されます
db2 = DatabaseConnection()
print(db2.connection)

print(db1.query("SELECT * FROM users"))
print(db1 is db2)

Singleton を使用して設定マネージャーを作成します:

class Config:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.settings = {}
        return cls._instance
    
    def set_setting(self, key, value):
        self.settings[key] = value
    
    def get_setting(self, key):
        return self.settings.get(key)

config1 = Config()
config1.set_setting("debug", True)

config2 = Config()
print(config2.get_setting("debug"))  # True - 同じ設定

出力:

True
140234567890123
140234567890123
Creating new database connection
Connected to MySQL database
Connected to MySQL database
Executing: SELECT * FROM users
True
True

Key Point: Singleton パターンは __new__ を使用して object の作成を制御し、1 つの instance のみが存在するようにします。databaseconnection、logger、configuration manager のように、アプリケーション全体で 1 つのコピーのみを持つべきリソースに使用します。Singleton を指すすべての変数は、memory 上の same object を参照することに注意してください。

challenge icon

チャレンジ

簡単

このチャレンジでは、データベース接続クラスのための Singleton デザインパターンを実装します。

  • TODO コメントに従って Singleton パターンを実装するために databaseconnection.py を編集してください
  • driver.py ファイルには広範なテストシナリオが含まれており、変更しないでください

自分で試してみよう

from databaseconnection import DatabaseConnection

# 包括的なテストケースハンドラー
test_case = input()

if test_case == "identity_check":
    db1 = DatabaseConnection()
    db2 = DatabaseConnection()
    print(db1 is db2)  # True を出力するはず

elif test_case == "connect_state":
    db = DatabaseConnection()
    print(f"Initial connected state: {db.connected}")  # False を出力するはず
    db.connect()
    print(f"After connect: {db.connected}")  # True を出力するはず

elif test_case == "disconnect_state":
    db = DatabaseConnection()
    db.connect()
    db.disconnect()
    print(f"After disconnect: {db.connected}")  # False を出力するはず

elif test_case == "multiple_instances_same_state":
    db1 = DatabaseConnection()
    db1.connect()
    db2 = DatabaseConnection()
    print(f"db2 connected state: {db2.connected}")  # True を出力するはず

elif test_case == "host_value":
    db = DatabaseConnection()
    print(f"Default host: {db.host}")  # "localhost" を出力するはず
    db.host = "new-server"
    db2 = DatabaseConnection()
    print(f"New instance host: {db2.host}")  # "new-server" を出力するはず

elif test_case == "init_once":
    db1 = DatabaseConnection()
    db1.host = "custom-host"
    db2 = DatabaseConnection()
    print(f"Both instances have same host: {db1.host == db2.host}")  # True を出力するはず
    print(f"Host value: {db1.host}")  # "custom-host" を出力するはず

elif test_case == "connect_message":
    db = DatabaseConnection()
    db.connect()  # Should print "Connected to database at localhost"

elif test_case == "disconnect_message":
    db = DatabaseConnection()
    db.connect()
    db.disconnect()  # Should print "Disconnected from database"

elif test_case == "attribute_modification":
    db1 = DatabaseConnection()
    db1.port = 3306
    db2 = DatabaseConnection()
    print(f"db2 has port attribute: {hasattr(db2, 'port')}")  # True を出力するはず
    print(f"db2 port value: {db2.port}")  # 3306 を出力するはず

elif test_case == "reset_connection":
    db1 = DatabaseConnection()
    db1.connect()
    print(f"Connected state: {db1.connected}")  # True を出力するはず
    db2 = DatabaseConnection()
    print(f"New instance connected state: {db2.connected}")  # True を出力するはず
    db2.disconnect()
    db3 = DatabaseConnection()
    print(f"After disconnect, new instance state: {db3.connected}")  # False を出力するはず

elif test_case == "multiple_connects":
    db = DatabaseConnection()
    db.connect()
    db.connect()
    db.connect()
    print(f"Connected state after multiple connects: {db.connected}")  # True を出力するはず
    db.disconnect()
    print(f"Connected state after disconnect: {db.connected}")  # False を出力するはず

elif test_case == "host_change_affects_message":
    db = DatabaseConnection()
    db.host = "custom-server"
    db.connect()  # Will still print "Connected to database at localhost"
    # connect メソッドには host 属性を使用しないハードコードされたメッセージがある
    print("Message still shows localhost: True")
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン