python單例模式與metaclass

jopen 8年前發布 | 9K 次閱讀 單例模式 Python Python開發

單例模式的實現方式

將類實例綁定到類變量上

class Singleton(object):
    _instance = None

    def __new__(cls, *args):
        if not isinstance(cls._instance, cls):
            cls._instance = super(Singleton, cls).__new__(cls, *args)
        return cls._instance

但是子類在繼承后可以重寫 __new__ 以失去單例特性

class D(Singleton):

    def __new__(cls, *args):
        return super(D, cls).__new__(cls, *args)

使用裝飾器實現

def singleton(_cls):
    inst = {}

    def getinstance(*args, **kwargs):
        if _cls not in inst:
            inst[_cls] = _cls(*args, **kwargs)
        return inst[_cls]
    return getinstance

@singleton
class MyClass(object):
    pass

問題是這樣裝飾以后返回的不是類而是函數,當然你可以 singleton 里定義一個類來解決問題,但這樣就顯得很麻煩了

使用 __metaclass__ ,這個方式最推薦

class Singleton(type):
    _inst = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._inst:
            cls._inst[cls] = super(Singleton, cls).__call__(*args)
        return cls._inst[cls]


class MyClass(object):
    __metaclass__ = Singleton

metaclass

元類就是用來創建 的東西,可以簡單把元類稱為“類工廠”,類是元類的實例。 type 就是Python的內建元類, type 也是自己的元類,任何一個類

>>> type(MyClass)
type
>>> type(type)
type

python在創建類 MyClass 的過程中,會在類的定義中尋找 __metaclass__ ,如果存在則用其創建類 MyClass ,否則使用內建的 type 來創建類。對于類有繼承的情況,如果當前類沒有找到,會繼續在父類中尋找 __metaclass__ ,直到所有父類中都沒有找到才使用 type 創建類。

如果模塊里有 __metaclass__ 的全局變量的話,

其中的類都將以其為元類

,親自試了,沒這個作用,無任何影響

查看 type 的定義,

type(object) -> the object's typetype(name, bases, dict) -> a new type

所以利用 type 定義一個類的元類,可以用函數返回一個上面第二種定義的對象,也可以繼承 type 并重寫其中的方法。

直接使用type生成的對象作為元類,函數作用是使屬性變為大寫

def update_(name, bases, dct):
    attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
    uppercase_attr = {name.upper(): value for name, value in attrs}
    return type(name, bases, uppercase_attr)


class Singleton(object):
    __metaclass__ = update_
    abc = 2

d = Singleton()
print d.ABC
# 2

上一節中,單例模式 元類實現 用的是類繼承方式,而對于第一種 __new__ 的方式,本質上調用的是 type.__new__ ,不過使用 super 能使繼承更清晰一些并避免一些問題

這里簡單說明一下, __new__ 是在 __init__ 前調用的方法,會創建對象并返回,而 __init__ 則是用傳入的參數將對象初始化。看一下 type 中這兩者以及 __call__ 的實現

def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        # (copied from class doc)
        """
        pass

@staticmethod # known case of __new__
def __new__(S, *more): # real signature unknown; restored from __doc__
    """ T.__new__(S, ...) -> a new object with type S, a subtype of T """
    pass

def __call__(self, *more): # real signature unknown; restored from __doc__
    """ x.__call__(...) <==> x(...) """
    pass

前面提到類相當于元類的實例化,再聯系創建單例模式時使用的函數,用的是 __call__ ,其實用三種magic method中任何一種都是可以的,來看一下使用元類時各方法的調用情況

class Basic(type):
    def __new__(cls, name, bases, newattrs):
        print "new: %r %r %r %r" % (cls, name, bases, newattrs)
        return super(Basic, cls).__new__(cls, name, bases, newattrs)

    def __call__(self, *args):
        print "call: %r %r" % (self, args)
        return super(Basic, self).__call__(*args)

    def __init__(cls, name, bases, newattrs):
        print "init: %r %r %r %r" % (cls, name, bases, newattrs)
        super(Basic, cls).__init__(name, bases, dict)


class Foo:
    __metaclass__ = Basic

    def __init__(self, *args, **kw):
        print "init: %r %r %r" % (self, args, kw)

a = Foo('a')
b = Foo('b')

結果

new: <class '__main__.Basic'> 'Foo' () {'__module__': '__main__', '__metaclass__': <class '__main__.Basic'>, '__init__': <function init at 0x106fd5320>}

init: <class '__main__.Foo'> 'Foo' () {'__module__': '__main__', '__metaclass__': <class '__main__.Basic'>, '__init__': <function init at 0x106fd5320>}

call: <class '__main__.Foo'> ('a',)

init: <__main__.Foo object at 0x106fee990> ('a',) {}

call: <class '__main__.Foo'> ('b',)

init: <__main__.Foo object at 0x106feea50> ('b',) {}

元類的 __init__ 和 __new__ 只在創建類 Foo 調用了一次,而創建 Foo 的實例時,每次都會調用元類的 __call__ 方法

來自: http://segmentfault.com/a/1190000004278703

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!