Singleton 這東西大家想必不陌生,在 Python 裡實做的方式滿多, 這邊用這個當例子介紹 Meta Class 可以做什麼。

概念上很簡當, 讓Class的建構子不能產生Instance, 然後再提供一個 class method 能夠取得Instance(而且只能有一個), 在Python 你可以用id來檢查兩個物件是不是相同的。

class Singleton(type):
    def __init__(cls,name,bases,dic):
        super(Singleton,cls).__init__(name,bases,dic)
        cls.instance=None

    def __call__(cls, *args, **kwargs):
      # 這裡不raise Exception, 是因為doctest比較好寫
            print "please use get_instance function to get the instance"
            # 你也可讓cls()直接傳回instance, 讓class user不用在意他用的class
      # 是不是Singleton, 他只要注意class的主功能即可
            # return cls.get_instance(*args, *kw)

    def get_instance(cls,*args,**kw):
        if cls.instance is None:
            cls.instance=super(Singleton,cls).__call__(*args,**kw)
        return cls.instance

用法

把你想變成Singleton class 的 metaclass 設成 Singleton 就可以了, 後悔的話,把那一行註解起來,這個class就不是Singleton。

要注意的地方是這class的Singleton特性是可以被繼承的,但這也是為什麼我喜歡用 這種方式的原因,另一個好處是Class的功能跟Design Pattern的耦合度會比較低。 缺點就是會遇到metaclass衝突的狀況,但也不是不能解決。

class _db(object):
  __metaclass__ = Singleton

Wrap Up

#!/usr/bin/env python
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
# -*- encoding=utf8 -*-
#
# Author 2012 Hsin-Yi Chen
class Singleton(type):
    def __init__(cls,name,bases,dic):
        super(Singleton,cls).__init__(name,bases,dic)
        cls.instance=None

    def __call__(cls, *args, **kwargs):
            print "please use get_instance function to get the instance"

    def get_instance(cls,*args,**kw):
        if cls.instance is None:
            cls.instance=super(Singleton,cls).__call__(*args,**kw)
        return cls.instance

class _db(object):
    """
    >>> obj=_db()
    please use get_instance function to get the instance
    >>> obj is None
    True
    >>> obj1 = _db.get_instance()
    connecting to 0
    >>> obj2 = _db.get_instance()
    >>> id(obj1) == id(obj2)
    True
    """
    __metaclass__ = Singleton
    session_max = 0

    def __init__(self):
        print 'connecting to {0}'.format(self.session_max)

class MySQL(_db):
    """
    >>> obj1 = MySQL.get_instance()
    connecting to 1000
    >>> obj2 = MySQL.get_instance()
    >>> id(obj1) == id(obj2)
    True
    """
    session_max = 1000

import doctest
doctest.testmod()