深刻理解Python中的元类(metaclass)以及元类实现单例模式
在看一些框架源代碼的過程中碰到很多元類的實(shí)例,看起來很吃力很晦澀;在看python cookbook中關(guān)于元類創(chuàng)建單例模式的那一節(jié)有些疑惑。因此花了幾天時(shí)間研究下元類這個(gè)概念。通過學(xué)習(xí)元類,我對(duì)python的面向?qū)ο笥辛烁由钊氲牧私狻_@里將一篇寫的非常好的文章基本照搬過來吧,這是一篇在Stack overflow上很熱的帖子,我看http://blog.jobbole.com/21351/這篇博客對(duì)其進(jìn)行了翻譯。
一、理解類也是對(duì)象
在理解元類之前,你需要先掌握Python中的類。Python中類的概念借鑒于Smalltalk,這顯得有些奇特。在大多數(shù)編程語言中,類就是一組用來描述如何生成一個(gè)對(duì)象的代碼段。在Python中這一點(diǎn)仍然成立:
class ObjectCreator(object):passmy_object = ObjectCreator() print my_object #輸出:<__main__.ObjectCreator object at 0x8974f2c>但是,Python中的類還遠(yuǎn)不止如此。類同樣也是一種對(duì)象。只要你使用關(guān)鍵字class,Python解釋器在執(zhí)行的時(shí)候就會(huì)創(chuàng)建一個(gè)對(duì)象。下面的代碼段:
class ObjectCreator(object):pass將在內(nèi)存中創(chuàng)建一個(gè)對(duì)象,名字就是ObjectCreator。這個(gè)對(duì)象(類)自身?yè)碛袆?chuàng)建對(duì)象(類實(shí)例)的能力,而這就是為什么它是一個(gè)類的原因。但是,它的本質(zhì)仍然是一個(gè)對(duì)象,于是你可以對(duì)它做如下的操作:
你可以將它賦值給一個(gè)變量, 你可以拷貝它, 你可以為它增加屬性, 你可以將它作為函數(shù)參數(shù)進(jìn)行傳遞。
下面是示例:
print ObjectCreator # 你可以打印一個(gè)類,因?yàn)樗鋵?shí)也是一個(gè)對(duì)象 #輸出:<class '__main__.ObjectCreator'> Idef echo(o):print oecho(ObjectCreator) # 你可以將類做為參數(shù)傳給函數(shù) #輸出:<class '__main__.ObjectCreator'> print hasattr(ObjectCreator, 'new_attribute') #輸出:False ObjectCreator.new_attribute = 'foo' # 你可以為類增加屬性 print hasattr(ObjectCreator, 'new_attribute') #輸出:True print ObjectCreator.new_attribute #輸出:foo ObjectCreatorMirror = ObjectCreator # 你可以將類賦值給一個(gè)變量 print ObjectCreatorMirror() #輸出:<__main__.ObjectCreator object at 0x108551310>?
?二、動(dòng)態(tài)地創(chuàng)建類
1、通過return class動(dòng)態(tài)的構(gòu)建需要的類
因?yàn)轭愐彩菍?duì)象,你可以在運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建它們,就像其他任何對(duì)象一樣。首先,你可以在函數(shù)中創(chuàng)建類,使用class關(guān)鍵字即可。?
def choose_class(name):if name == 'foo':class Foo(object):passreturn Foo # 返回的是類,不是類的實(shí)例else:class Bar(object):passreturn Bar MyClass = choose_class('foo')print MyClass # 函數(shù)返回的是類,不是類的實(shí)例 #輸出:<class '__main__.Foo'>print MyClass() # 你可以通過這個(gè)類創(chuàng)建類實(shí)例,也就是對(duì)象 #輸出:<__main__.Foo object at 0x1085ed9502、通過type函數(shù)構(gòu)造類
但這還不夠動(dòng)態(tài),因?yàn)槟闳匀恍枰约壕帉懻麄€(gè)類的代碼。由于類也是對(duì)象,所以它們必須是通過什么東西來生成的才對(duì)。當(dāng)你使用class關(guān)鍵字時(shí),Python解釋器自動(dòng)創(chuàng)建這個(gè)對(duì)象。但就和Python中的大多數(shù)事情一樣,Python仍然提供給你手動(dòng)處理的方法。還記得內(nèi)建函數(shù)type嗎?這個(gè)古老但強(qiáng)大的函數(shù)能夠讓你知道一個(gè)對(duì)象的類型是什么,就像這樣:
print type(1) #輸出:<type 'int'> print type("1") #輸出:<type 'str'> print type(ObjectCreator) #輸出:<type 'type'> print type(ObjectCreator()) #輸出:<class '__main__.ObjectCreator'>這里,type有一種完全不同的能力,它也能動(dòng)態(tài)的創(chuàng)建類。type可以接受一個(gè)類的描述作為參數(shù),然后返回一個(gè)類。(我知道,根據(jù)傳入?yún)?shù)的不同,同一個(gè)函數(shù)擁有兩種完全不同的用法是一件很傻的事情,但這在Python中是為了保持向后兼容性)
type的語法:
type(類名, 父類的元組(針對(duì)繼承的情況,可以為空),包含屬性的字典(名稱和值))比如下面的代碼:
class MyShinyClass(object):pass可以手動(dòng)通過type創(chuàng)建,其實(shí)
MyShinyClass = type('MyShinyClass', (), {}) # 返回一個(gè)類對(duì)象 print MyShinyClass #輸出:<class '__main__.MyShinyClass'> print MyShinyClass() # 創(chuàng)建一個(gè)該類的實(shí)例 #輸出:<__main__.MyShinyClass object at 0x1085cd810>?你會(huì)發(fā)現(xiàn)我們使用“MyShinyClass”作為類名,并且也可以把它當(dāng)做一個(gè)變量來作為類的引用。
接下來我們通過一個(gè)具體的例子看看type是如何創(chuàng)建類的,范例:
1、構(gòu)建Foo類 #構(gòu)建目標(biāo)代碼 class Foo(object):bar = True #使用type構(gòu)建 Foo = type('Foo', (), {'bar':True})2.繼承Foo類 #構(gòu)建目標(biāo)代碼: class FooChild(Foo):pass #使用type構(gòu)建 FooChild = type('FooChild', (Foo,),{})print FooChild #輸出:<class '__main__.FooChild'> print FooChild.bar # bar屬性是由Foo繼承而來 #輸出:True3.為Foochild類增加方法 def echo_bar(self):print self.barFooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) hasattr(Foo, 'echo_bar') #輸出:False hasattr(FooChild, 'echo_bar') #輸出:True my_foo = FooChild() my_foo.echo_bar() #輸出:True可以看到,在Python中,類也是對(duì)象,你可以動(dòng)態(tài)的創(chuàng)建類。這就是當(dāng)我們使用關(guān)鍵字class時(shí)Python在幕后做的事情,而這就是通過元類來實(shí)現(xiàn)的。
三、元類
1、什么是元類
通過上文的描述,我們知道了Python中的類也是對(duì)象。元類就是用來創(chuàng)建這些類(對(duì)象)的,元類就是類的類,你可以這樣理解為:
MyClass = MetaClass() #元類創(chuàng)建 MyObject = MyClass() #類創(chuàng)建實(shí)例 實(shí)際上MyClass就是通過type()來創(chuàng)創(chuàng)建出MyClass類,它是type()類的一個(gè)實(shí)例;同時(shí)MyClass本身也是類,也可以創(chuàng)建出自己的實(shí)例,這里就是MyObject函數(shù)type實(shí)際上是一個(gè)元類。type就是Python在背后用來創(chuàng)建所有類的元類。現(xiàn)在你想知道那為什么type會(huì)全部采用小寫形式而不是Type呢?好吧,我猜這是為了和str保持一致性,str是用來創(chuàng)建字符串對(duì)象的類,而int是用來創(chuàng)建整數(shù)對(duì)象的類。type就是創(chuàng)建類對(duì)象的類。你可以通過檢查__class__屬性來看到這一點(diǎn)。Python中所有的東西,注意,我是指所有的東西——都是對(duì)象。這包括整數(shù)、字符串、函數(shù)以及類。它們?nèi)慷际菍?duì)象,而且它們都是從一個(gè)類創(chuàng)建而來。
age = 35 age.__class__ #輸出:<type 'int'> name = 'bob' name.__class__ #輸出:<type 'str'> def foo(): pass foo.__class__ #輸出:<type 'function'> class Bar(object): pass b = Bar() b.__class__ #輸出:<class '__main__.Bar'> 對(duì)于任何一個(gè)__class__的__class__屬性又是什么呢? a.__class__.__class__ #輸出:<type 'type'> age.__class__.__class__ #輸出:<type 'type'> foo.__class__.__class__ #輸出:<type 'type'> b.__class__.__class__ #輸出:<type 'type'>因此,元類就是創(chuàng)建類這種對(duì)象的東西,?type就是Python的內(nèi)建元類,當(dāng)然了,你也可以創(chuàng)建自己的元類。
2、__metaclass__屬性
你可以在寫一個(gè)類的時(shí)候?yàn)槠涮砑觃_metaclass__屬性,定義了__metaclass__就定義了這個(gè)類的元類。
class Foo(object): #py2__metaclass__ = something…class Foo(metaclass=something): #py3__metaclass__ = something…例如:當(dāng)我們寫如下代碼時(shí) :
class Foo(Bar):pass在該類并定義的時(shí)候,它還沒有在內(nèi)存中生成,知道它被調(diào)用。Python做了如下的操作:
1)Foo中有__metaclass__這個(gè)屬性嗎?如果是,Python會(huì)在內(nèi)存中通過__metaclass__創(chuàng)建一個(gè)名字為Foo的類對(duì)象(我說的是類對(duì)象,請(qǐng)緊跟我的思路)。
2)如果Python沒有找到__metaclass__,它會(huì)繼續(xù)在父類中尋找__metaclass__屬性,并嘗試做和前面同樣的操作。
3)如果Python在任何父類中都找不到__metaclass__,它就會(huì)在模塊層次中去尋找__metaclass__,并嘗試做同樣的操作。
4)如果還是找不到__metaclass__,Python就會(huì)用內(nèi)置的type來創(chuàng)建這個(gè)類對(duì)象。
現(xiàn)在的問題就是,你可以在__metaclass__中放置些什么代碼呢?
答案就是:可以創(chuàng)建一個(gè)類的東西。那么什么可以用來創(chuàng)建一個(gè)類呢?type,或者任何使用到type或者子類化type的東西都可以。
三、自定義元類
元類的主要目的就是為了當(dāng)創(chuàng)建類時(shí)能夠自動(dòng)地改變類。通常,你會(huì)為API做這樣的事情,你希望可以創(chuàng)建符合當(dāng)前上下文的類。假想一個(gè)很傻的例子,你決定在你的模塊里所有的類的屬性都應(yīng)該是大寫形式。有好幾種方法可以辦到,但其中一種就是通過設(shè)定__metaclass__。采用這種方法,這個(gè)模塊中的所有類都會(huì)通過這個(gè)元類來創(chuàng)建,我們只需要告訴元類把所有的屬性都改成大寫形式就萬事大吉了。
__metaclass__實(shí)際上可以被任意調(diào)用,它并不需要是一個(gè)正式的類。所以,我們這里就先以一個(gè)簡(jiǎn)單的函數(shù)作為例子開始。
1、使用函數(shù)當(dāng)做元類
# 元類會(huì)自動(dòng)將你通常傳給‘type’的參數(shù)作為自己的參數(shù)傳入 def upper_attr(future_class_name, future_class_parents, future_class_attr):'''返回一個(gè)類對(duì)象,將屬性都轉(zhuǎn)為大寫形式'''#選擇所有不以'__'開頭的屬性attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))# 將它們轉(zhuǎn)為大寫形式uppercase_attr = dict((name.upper(), value) for name, value in attrs)#通過'type'來做類對(duì)象的創(chuàng)建return type(future_class_name, future_class_parents, uppercase_attr)#返回一個(gè)類class Foo(object):__metaclass__ = upper_attrbar = 'bip'? print hasattr(Foo, 'bar') # 輸出: False print hasattr(Foo, 'BAR') # 輸出:True f = Foo() print f.BAR # 輸出:'bip'2、使用class來當(dāng)做元類
由于__metaclass__必須返回一個(gè)類。
# 請(qǐng)記住,'type'實(shí)際上是一個(gè)類,就像'str'和'int'一樣。所以,你可以從type繼承 # __new__ 是在__init__之前被調(diào)用的特殊方法,__new__是用來創(chuàng)建對(duì)象并返回之的方法,__new_()是一個(gè)類方法 # 而__init__只是用來將傳入的參數(shù)初始化給對(duì)象,它是在對(duì)象創(chuàng)建之后執(zhí)行的方法。 # 你很少用到__new__,除非你希望能夠控制對(duì)象的創(chuàng)建。這里,創(chuàng)建的對(duì)象是類,我們希望能夠自定義它,所以我們這里改寫__new__ # 如果你希望的話,你也可以在__init__中做些事情。還有一些高級(jí)的用法會(huì)涉及到改寫__call__特殊方法,但是我們這里不用,下面我們可以單獨(dú)的討論這個(gè)使用class UpperAttrMetaClass(type):def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))uppercase_attr = dict((name.upper(), value) for name, value in attrs)return type(future_class_name, future_class_parents, uppercase_attr)#返回一個(gè)對(duì)象,但同時(shí)這個(gè)對(duì)象是一個(gè)類?但是,這種方式其實(shí)不是OOP。我們直接調(diào)用了type,而且我們沒有改寫父類的__new__方法。現(xiàn)在讓我們這樣去處理:
class UpperAttrMetaclass(type):def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))uppercase_attr = dict((name.upper(), value) for name, value in attrs)# 復(fù)用type.__new__方法# 這就是基本的OOP編程,沒什么魔法。由于type是元類也就是類,因此它本身也是通過__new__方法生成其實(shí)例,只不過這個(gè)實(shí)例是一個(gè)類.return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)你可能已經(jīng)注意到了有個(gè)額外的參數(shù)upperattr_metaclass,這并沒有什么特別的。類方法的第一個(gè)參數(shù)總是表示當(dāng)前的實(shí)例,就像在普通的類方法中的self參數(shù)一樣。當(dāng)然了,為了清晰起見,這里的名字我起的比較長(zhǎng)。但是就像self一樣,所有的參數(shù)都有它們的傳統(tǒng)名稱。因此,在真實(shí)的產(chǎn)品代碼中一個(gè)元類應(yīng)該是像這樣的:
class UpperAttrMetaclass(type):def __new__(cls, name, bases, dct):attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')uppercase_attr = dict((name.upper(), value) for name, value in attrs)return type.__new__(cls, name, bases, uppercase_attr)如果使用super方法的話,我們還可以使它變得更清晰一些。
class UpperAttrMetaclass(type):def __new__(cls, name, bases, dct):attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))uppercase_attr = dict((name.upper(), value) for name, value in attrs)return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)四、使用原來創(chuàng)建ORM的實(shí)例?
我們通過創(chuàng)建一個(gè)類似Django中的ORM來熟悉一下元類的使用,通常元類用來創(chuàng)建API是非常好的選擇,使用元類的編寫很復(fù)雜但使用者可以非常簡(jiǎn)潔的調(diào)用API。
#我們想創(chuàng)建一個(gè)類似Django的ORM,只要定義字段就可以實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)表和字段的操作。 class User(Model):# 定義類的屬性到列的映射:id = IntegerField('id')name = StringField('username')email = StringField('email')password = StringField('password')例如:
# 創(chuàng)建一個(gè)實(shí)例: u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd') # 保存到數(shù)據(jù)庫(kù): u.save()接下來我么來實(shí)現(xiàn)這么個(gè)功能:
#coding:utf-8 #一、首先來定義Field類,它負(fù)責(zé)保存數(shù)據(jù)庫(kù)表的字段名和字段類型: class Field(object):def __init__(self, name, column_type):self.name = nameself.column_type = column_typedef __str__(self):return '<%s:%s>' % (self.__class__.__name__, self.name)class StringField(Field):def __init__(self, name):super(StringField, self).__init__(name, 'varchar(100)')class IntegerField(Field):def __init__(self, name):super(IntegerField, self).__init__(name, 'bigint')#二、定義元類,控制Model對(duì)象的創(chuàng)建 class ModelMetaclass(type):'''定義元類'''def __new__(cls, name, bases, attrs):if name=='Model':return super(ModelMetaclass,cls).__new__(cls, name, bases, attrs)mappings = dict()for k, v in attrs.iteritems():# 保存類屬性和列的映射關(guān)系到mappings字典if isinstance(v, Field):print('Found mapping: %s==>%s' % (k, v))mappings[k] = vfor k in mappings.iterkeys():#將類屬性移除,使定義的類字段不污染User類屬性,只在實(shí)例中可以訪問這些key attrs.pop(k)attrs['__table__'] = name.lower() # 假設(shè)表名和為類名的小寫,創(chuàng)建類時(shí)添加一個(gè)__table__類屬性attrs['__mappings__'] = mappings # 保存屬性和列的映射關(guān)系,創(chuàng)建類時(shí)添加一個(gè)__mappings__類屬性return super(ModelMetaclass,cls).__new__(cls, name, bases, attrs)#三、編寫Model基類 class Model(dict):__metaclass__ = ModelMetaclassdef __init__(self, **kw):super(Model, self).__init__(**kw)def __getattr__(self, key):try:return self[key]except KeyError:raise AttributeError(r"'Model' object has no attribute '%s'" % key)def __setattr__(self, key, value):self[key] = valuedef save(self):fields = []params = []args = []for k, v in self.__mappings__.iteritems():fields.append(v.name)params.append('?')args.append(getattr(self, k, None))sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))print('SQL: %s' % sql)print('ARGS: %s' % str(args))#最后,我們使用定義好的ORM接口,使用起來非常的簡(jiǎn)單。 class User(Model):# 定義類的屬性到列的映射:id = IntegerField('id')name = StringField('username')email = StringField('email')password = StringField('password')# 創(chuàng)建一個(gè)實(shí)例: u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd') # 保存到數(shù)據(jù)庫(kù): u.save()#輸出 # Found mapping: email==><StringField:email> # Found mapping: password==><StringField:password> # Found mapping: id==><IntegerField:id> # Found mapping: name==><StringField:username> # SQL: insert into User (password,email,username,id) values (?,?,?,?) # ARGS: ['my-pwd', 'test@orm.org', 'Michael', 12345] ORM代碼?
五、使用__new__方法和元類方式分別實(shí)現(xiàn)單例模式
1、__new__、__init__、__call__的介紹
在講到使用元類創(chuàng)建單例模式之前,比需了解__new__這個(gè)內(nèi)置方法的作用,在上面講元類的時(shí)候我們用到了__new__方法來實(shí)現(xiàn)類的創(chuàng)建。然而我在那之前還是對(duì)__new__這個(gè)方法和__init__方法有一定的疑惑。因此這里花點(diǎn)時(shí)間對(duì)其概念做一次了解和區(qū)分。
__new__方法負(fù)責(zé)創(chuàng)建一個(gè)實(shí)例對(duì)象,在對(duì)象被創(chuàng)建的時(shí)候調(diào)用該方法它是一個(gè)類方法。__new__方法在返回一個(gè)實(shí)例之后,會(huì)自動(dòng)的調(diào)用__init__方法,對(duì)實(shí)例進(jìn)行初始化。如果__new__方法不返回值,或者返回的不是實(shí)例,那么它就不會(huì)自動(dòng)的去調(diào)用__init__方法。
__init__ 方法負(fù)責(zé)將該實(shí)例對(duì)象進(jìn)行初始化,在對(duì)象被創(chuàng)建之后調(diào)用該方法,在__new__方法創(chuàng)建出一個(gè)實(shí)例后對(duì)實(shí)例屬性進(jìn)行初始化。__init__方法可以沒有返回值。
__call__方法其實(shí)和類的創(chuàng)建過程和實(shí)例化沒有多大關(guān)系了,定義了__call__方法才能被使用函數(shù)的方式執(zhí)行。
例如: class A(object):def __call__(self):print "__call__ be called"a = A() a() #輸出 #__call__ be called?打個(gè)比方幫助理解:如果將創(chuàng)建實(shí)例的過程比作建一個(gè)房子。
- 那么class就是一個(gè)房屋的設(shè)計(jì)圖,他規(guī)定了這個(gè)房子有幾個(gè)房間,每個(gè)人房間的大小朝向等。這個(gè)設(shè)計(jì)圖就是累的結(jié)構(gòu)
- __new__就是一個(gè)房屋的框架,每個(gè)具體的房屋都需要先搭好框架后才能進(jìn)行專修,當(dāng)然現(xiàn)有了房屋設(shè)計(jì)才能有具體的房屋框架出來。這個(gè)就是從類到類實(shí)例的創(chuàng)建。
- __init__就是裝修房子的過程,對(duì)房屋的墻面和地板等顏色材質(zhì)的豐富就是它該做的事情,當(dāng)然先有具體的房子框架出來才能進(jìn)行裝飾了。這個(gè)就是實(shí)例屬性的初始化,它是在__new__出一個(gè)實(shí)例后才能初始化。
- __call__就是房子的電話,有了固定電話,才能被打電話嘛(就是通過括號(hào)的方式像函數(shù)一樣執(zhí)行)。
?子類如果重寫__new__方法,一般依然要調(diào)用父類的__new__方法。
class Child(Foo):def __new__(cls, *args, **kwargs): return suyper(Child, cls).__new__(cls, *args, **kwargs)?必須注意的是,類的__new__方法之后,必須生成本類的實(shí)例才能自動(dòng)調(diào)用本類的__init__方法進(jìn)行初始化,否則不會(huì)自動(dòng)調(diào)用__init__.
class Foo(object):def __init__(self, *args, **kwargs):print "Foo __init__"def __new__(cls, *args, **kwargs):return object.__new__(Stranger, *args, **kwargs)class Stranger(object):def __init__(self,name):print "class Stranger's __init__ be called"self.name = namefoo = Foo("test") print type(foo) #<class '__main__.Stranger'> print foo.name #AttributeError: 'Stranger' object has no attribute 'name'#說明:如果new方法返回的不是本類的實(shí)例,那么本類(Foo)的init和生成的類(Stranger)的init都不會(huì)被調(diào)用2.實(shí)現(xiàn)單例模式:
依照Python官方文檔的說法,__new__方法主要是當(dāng)你繼承一些不可變的class時(shí)(比如int, str, tuple), 提供給你一個(gè)自定義這些類的實(shí)例化過程的途徑。還有就是實(shí)現(xiàn)自定義的metaclass。接下來我們分別通過這兩種方式來實(shí)現(xiàn)單例模式。當(dāng)初在看到cookbook中的元類來實(shí)現(xiàn)單例模式的時(shí)候?qū)ζ湎喈?dāng)疑惑,因此才有了上面這些對(duì)元類的總結(jié)。
簡(jiǎn)單來說,單例模式的原理就是通過在類屬性中添加一個(gè)單例判定位ins_flag,通過這個(gè)flag判斷是否已經(jīng)被實(shí)例化過了,如果被實(shí)例化過了就返回該實(shí)例。
1)__new__方法實(shí)現(xiàn)單例:
class Singleton(object):def __new__(cls, *args, **kwargs):if not hasattr(cls,"_instance"):cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)return cls._instances1 = Singleton() s2 = Singleton()print s1 is s2?
因?yàn)橹貙慱_new__方法,所以繼承至Singleton的類,在不重寫__new__的情況下都將是單例模式。
2)元類實(shí)現(xiàn)單例
當(dāng)初我也很疑惑為什么我們是從寫使用元類的__init__方法,而不是使用__new__方法來初為元類增加一個(gè)屬性。其實(shí)我只是上面那一段關(guān)于元類中__new__方法迷惑了,它主要用于我們需要對(duì)類的結(jié)構(gòu)進(jìn)行改變的時(shí)候我們才要重寫這個(gè)方法。
class Singleton(type):def __init__(self, *args, **kwargs):print "__init__"self.__instance = Nonesuper(Singleton,self).__init__(*args, **kwargs)def __call__(self, *args, **kwargs):print "__call__"if self.__instance is None:self.__instance = super(Singleton,self).__call__(*args, **kwargs)return self.__instanceclass Foo(object):__metaclass__ = Singleton #在代碼執(zhí)行到這里的時(shí)候,元類中的__new__方法和__init__方法其實(shí)已經(jīng)被執(zhí)行了,而不是在Foo實(shí)例化的時(shí)候執(zhí)行。且僅會(huì)執(zhí)行一次。foo1 = Foo() foo2 = Foo() print Foo.__dict__ #_Singleton__instance': <__main__.Foo object at 0x100c52f10> 存在一個(gè)私有屬性來保存屬性,而不會(huì)污染Foo類(其實(shí)還是會(huì)污染,只是無法直接通過__instance屬性訪問)print foo1 is foo2 # True# 輸出 # __init__ # __call__ # __call__ # {'__module__': '__main__', '__metaclass__': <class '__main__.Singleton'>, '_Singleton__instance': <__main__.Foo object at 0x100c52f10>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None} # True?
基于這個(gè)例子:
- 我們知道元類(Singleton)生成的實(shí)例是一個(gè)類(Foo),而這里我們僅僅需要對(duì)這個(gè)實(shí)例(Foo)增加一個(gè)屬性(__instance)來判斷和保存生成的單例。想想也知道為一個(gè)類添加一個(gè)屬性當(dāng)然是在__init__中實(shí)現(xiàn)了。
- 關(guān)于__call__方法的調(diào)用,因?yàn)镕oo是Singleton的一個(gè)實(shí)例。所以Foo()這樣的方式就調(diào)用了Singleton的__call__方法。不明白就回頭看看上一節(jié)中的__call__方法介紹。
假如我們通過元類的__new__方法來也可以實(shí)現(xiàn),但顯然沒有通過__init__來實(shí)現(xiàn)優(yōu)雅,因?yàn)槲覀儾粫?huì)為了為實(shí)例增加一個(gè)屬性而重寫__new__方法。所以這個(gè)形式不推薦。
class Singleton(type):def __new__(cls, name,bases,attrs):print "__new__"attrs["_instance"] = Nonereturn super(Singleton,cls).__new__(cls,name,bases,attrs)def __call__(self, *args, **kwargs):print "__call__"if self._instance is None:self._instance = super(Singleton,self).__call__(*args, **kwargs)return self._instanceclass Foo(object):__metaclass__ = Singletonfoo1 = Foo() foo2 = Foo() print Foo.__dict__ print foo1 is foo2 # True# 輸出 # __new__ # __call__ # __call__ # {'__module__': '__main__', '__metaclass__': <class '__main__.Singleton'>, '_instance': <__main__.Foo object at 0x103e07ed0>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None} # True?
轉(zhuǎn)載于:https://www.cnblogs.com/tkqasn/p/6524879.html
總結(jié)
以上是生活随笔為你收集整理的深刻理解Python中的元类(metaclass)以及元类实现单例模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WebAssembly 系列(五)为什么
- 下一篇: 线程常用方法