摘要:中的類都是單例模式一天,一同事問我這樣一個(gè)問題。與方法屬于新式類,即屬于類。方法在實(shí)例被創(chuàng)建之后被調(diào)用,該方法僅僅是對(duì)方法創(chuàng)建的實(shí)例進(jìn)行一些初始化操作。需要注意的是,在重寫方法與方法的參數(shù)應(yīng)該保持一致,否則會(huì)有發(fā)生。
“Python 中的類都是單例模式?” 一天,一同事問我這樣一個(gè)問題。這是一個(gè)奇怪的問題,可能你也這么認(rèn)為。這里先不做解釋,我們先來看看 __new__ 和 __init__ 方法。
new 與 init__new__ 方法屬于新式類,即屬于 object 類。它是一個(gè)靜態(tài)方法,但是其第一個(gè)參數(shù)必須是一個(gè)類(cls),這有點(diǎn)像一個(gè) classmethod,其實(shí)將其看成是一個(gè)類方法也可以。該特殊方法被調(diào)用時(shí),會(huì)創(chuàng)建類(cls)的一個(gè)新實(shí)例并返回,實(shí)例被創(chuàng)建后解釋器會(huì)將該實(shí)例以及其它的參數(shù)傳遞給該實(shí)例的初始化函數(shù) __init__,以對(duì)實(shí)例進(jìn)行初始化。
所以,__new__ 方法是一個(gè)類方法,用于創(chuàng)建一個(gè)實(shí)例,而 __init__ 方法是一個(gè)實(shí)例方法,用于初始化一個(gè)實(shí)例。
__new__ 方法在實(shí)例化一個(gè)類時(shí)被調(diào)用,重寫該方法應(yīng)該像如下的形式:
class A(object): def __new__(cls, *args, **kwargs) return super(A, cls).__new__(cls, *args, **kwargs)
如果 __new__ 方法不返回 cls 的一個(gè)實(shí)例,那么新的實(shí)例的 __init__ 方法不會(huì)被調(diào)用。需要注意的是,在 Python 3.3 之后,new 方法不再接收額外的參數(shù),否則會(huì)有異常 TypeError: object() takes no parameters。
__init__ 方法在實(shí)例被創(chuàng)建之后被調(diào)用,該方法僅僅是對(duì) __new__ 方法創(chuàng)建的實(shí)例進(jìn)行一些初始化操作。注意,如果 new 方法返回實(shí)例,則 init 方法總是會(huì)被調(diào)用(這一點(diǎn)在用 new 方法實(shí)現(xiàn)單例時(shí)要特別注意)
可以來做一下驗(yàn)證:
class Foo(object): def __new__(cls, m, n): print "__new__ is called" return super(Foo, cls).__new__(cls, m, n) def __init__(self, m, n): print "__init__ is called" self.m = m self.n = n def __repr__(self): return "Foo(m={self.m}, n={self.n})".format(self=self) def __str__(self): return self.__repr__() if __name__ == "__main__": f = Foo(1, 2) print f
輸出結(jié)果:
__new__ is called __init__ is called Foo(m=1, n=2)
于是可以得出結(jié)論:
1、 __new__ 屬于類級(jí)別的方法,即使沒有被 classmethod 裝飾,其決定生成實(shí)例的過程。
2、 __init__ 屬于實(shí)例級(jí)別的方法,決定實(shí)例初始化的過程,比如添加屬性,對(duì)初始化參數(shù)進(jìn)行判斷,轉(zhuǎn)換等。
需要注意的是,在重寫 __new__ 方法與 __init__ 方法的參數(shù)應(yīng)該保持一致,否則會(huì)有 TypeError 發(fā)生。如果直接調(diào)用 object.__new__() 則在 Python 3.3 及以后的版本中不再支持傳入?yún)?shù),這一點(diǎn)參考自:https://stackoverflow.com/questions/34777773/typeerror...
__init__ 方法,在定義一個(gè) class 的時(shí)候一般都會(huì)涉及到,也是比較常用。而 __new__ 方法則很少會(huì)用到,那么它到底有什么用途呢?
new 方法作用__new__ 方法比較常用的作用大概是:
1、 繼承內(nèi)建不可變類型時(shí)(比如int, str, tuple),提供自定義實(shí)例化過程。因?yàn)槿绻?__init__ 方法中做都寫操作可能會(huì)無效。例如:
class CustomInt(int): def __init__(self, v): super(CustomInt, self).__init__(self, abs(v)) print CustomInt(-1) # 輸出:-1
這可能沒有達(dá)到期望的效果。但可以通過重寫 __new__ 方法來實(shí)現(xiàn):
class CustomInt(int): def __new__(cls, v): return super(CustomInt, cls).__new__(cls, abs(v)) print CustomInt(-1) # 輸出:1
2、 實(shí)現(xiàn)自定義的元類(metaclass)。元類就是用來定義如何創(chuàng)建類,它的概念可能稍微復(fù)雜些,這里不做詳細(xì)討論。
3、 實(shí)現(xiàn)單例。由于類產(chǎn)生實(shí)例的過程是通過 __new__ 方法來控制的,因此重寫該方法來單例模式是非常方便的:
class Singleton(object): def __new__(cls): if not hasattr(cls, "_instance"): cls._instance = super(Singleton, cls).__new__(cls) return cls._instance assert Singleton() is Singleton() # 斷言成功
所謂單例模式就是每次初始化都返回同一個(gè)實(shí)例,所以兩次初始化得到的對(duì)象的內(nèi)存地址應(yīng)該是一樣的:
print Singleton(), Singleton()
結(jié)果應(yīng)該是顯而易見的:
<__main__.Singleton object at 0x10d698650> <__main__.Singleton object at 0x10d698650>裝飾器實(shí)現(xiàn)單例
說到單例模式,除了用 __new__ 方法實(shí)現(xiàn)外,還有一些其他的方式,如裝飾器、元類等。不同方式的實(shí)現(xiàn)有不同的作用,元類屬于 Python 中更為高級(jí)的特性,本文不做討論,我們來看一下用裝飾器實(shí)現(xiàn)單例的方法。
裝飾器(decorator)可以動(dòng)態(tài)地修改一個(gè)類或函數(shù)的功能,即類也可以被裝飾器裝飾。因此可以使用裝飾器來裝飾某個(gè)類,使其被初始化時(shí)只生成一個(gè)實(shí)例:
from functools import wraps def singleton(cls): instances = {} @wraps(cls) def getinstance(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return getinstance @singleton class MyClass(object): def __init__(self): pass
需要注意的是,使用裝飾器實(shí)現(xiàn)單例時(shí),類已經(jīng)變成了一個(gè)函數(shù),而不再是類。 如上用上例中 MyClass 初始化實(shí)例時(shí),實(shí)際上調(diào)用的是被裝飾后返回的 getinstance 函數(shù)。
用 __new__ 實(shí)現(xiàn)單例和用裝飾實(shí)現(xiàn)單例的區(qū)別是,前者前者都是會(huì)調(diào)用 __init__ 方法,這就意味著每次初始化時(shí)用不同的參數(shù),雖然返回的實(shí)例時(shí)同一個(gè),但是實(shí)例的屬性卻被重新設(shè)置了;而后者則總是返回第一次初始化創(chuàng)建的示例和設(shè)置的屬性,即使后面?zhèn)魅肓瞬煌膮?shù)。
奇怪現(xiàn)象接著,我們?cè)賮砜匆粋€(gè) “奇怪” 的現(xiàn)象:
>>> class A(object): ... pass ... >>> print A(), A() <__main__.A object at 0x104765450> <__main__.A object at 0x104765450> >>> print A(), A() <__main__.A object at 0x104765450> <__main__.A object at 0x104765450> >>> print A(), A() <__main__.A object at 0x104765450> <__main__.A object at 0x104765450>
是不是感覺有些難以置信,print 語句后兩次創(chuàng)建的對(duì)象應(yīng)該是不一樣的,而他們卻莫名奇妙的一樣。這就是我討論本文內(nèi)容的原因。
一次同事問我,Python 中的類都是單例模式?我當(dāng)時(shí)一臉懵逼,聽了他的描述,我自己也試了下,果然存在如上所示的“奇怪”現(xiàn)象。于是我就去了解了 Python 單例模式的實(shí)現(xiàn),在了解到 __new__ 的實(shí)現(xiàn)方式時(shí),就想對(duì) __new__ 和 __init__ 有一個(gè)更加深入的了解。于是就有了本文所討論的內(nèi)容。
然后,我想著用 is 來判斷下他們是否真的是同一個(gè)實(shí)例:
>>> A() is A() False
我沒有對(duì) CPython 的源碼進(jìn)行過全面的閱讀,所以對(duì)其很多內(nèi)部的實(shí)現(xiàn)機(jī)制不是太了解。我猜 Python 解釋器在內(nèi)部可能做了優(yōu)化,像 print A(), A() 這樣的語句,解釋器認(rèn)為沒有必要?jiǎng)?chuàng)建不同的對(duì)象,直接返回同一個(gè)實(shí)例的引用得了。是不是覺得解釋器有些自作聰明!而當(dāng) A() is A() 這樣的表達(dá)式出現(xiàn)時(shí),解釋器想,我不能再自作聰明,那樣可能會(huì)誤導(dǎo)別人。可是,在 print 語句那樣的用法時(shí),就已經(jīng)誤導(dǎo)我了,我都差點(diǎn)開始懷疑人生了!
從語法來看,大家應(yīng)該知道,我在測試時(shí)使用的 Python 2。我后來也試了下 Python 3:
>>> class A(): ... pass ... >>> print(A(), A()) <__console__.A object at 0x10fe7afd0> <__console__.A object at 0x10fed79e8> >>> print(A(), A()) <__console__.A object at 0x10fec0cc0> <__console__.A object at 0x10feda160> >>> print(A(), A()) <__console__.A object at 0x10fe7afd0> <__console__.A object at 0x10fed7940> >>> A() is A() False
我想,這樣的結(jié)果才是不會(huì)讓人困惑的??赡苁?Python 社區(qū)意識(shí)到了這個(gè)問題并在 Python3 中進(jìn)行了修正。這樣的修正是好的,否則對(duì)于像我同事那樣初次使用 Python 的人來說是很困惑的。
個(gè)人認(rèn)為,Python3 對(duì)過去的一些“錯(cuò)誤”的修正是好的。例如將 print 改為函數(shù),提供了豐富的參數(shù)來控制輸出的樣式;對(duì)編碼的調(diào)整等等。
Python 中還有很多令人“匪夷所思”的“奇怪”現(xiàn)象,感興趣可以看看這篇博客:Python Oddity。例如其中提到的對(duì)整數(shù)的對(duì)比,其就是因?yàn)?Python 對(duì)小整數(shù)對(duì)象([-5,256])進(jìn)行了緩存,對(duì)于這個(gè)問題也有人進(jìn)行了詳細(xì)的描述:Python解惑:整數(shù)比較。
參考資料https://docs.python.org/2/reference/datamodel.html
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/43100.html
摘要:最近在學(xué)習(xí)設(shè)計(jì)模式而開發(fā)的語言中比較中意的就是了在這里總結(jié)下設(shè)計(jì)模式一般分為三大類構(gòu)造型結(jié)構(gòu)型行為型先從創(chuàng)建型設(shè)計(jì)模式開始創(chuàng)建型設(shè)計(jì)模式包括單例模式抽象工廠模式工廠方法模式生成器模式惰性初始化模式對(duì)象池模式原型模式單例模式單例模式的定義是保 最近在學(xué)習(xí)設(shè)計(jì)模式,而開發(fā)的語言中比較中意的就是python了,在這里總結(jié)下. 設(shè)計(jì)模式一般分為三大類:構(gòu)造型,結(jié)構(gòu)型,行為型 先從創(chuàng)建型設(shè)計(jì)模式...
摘要:實(shí)現(xiàn)實(shí)現(xiàn)單例模式有多種方案使用提供了非常易用的類,只要繼承它,就會(huì)成為單例。參考鏈接單例模式最后,感謝女朋友支持。 問題:現(xiàn)代化的巧克力工廠具備計(jì)算機(jī)控制的巧克力鍋爐。鍋爐做的事情就是把巧克力和牛奶融在一起,然后送到下一個(gè)階段,以制成巧克力棒。下邊是一個(gè)巧克力公司鍋爐控制器的代碼,仔細(xì)觀察一下,這段代碼有什么問題? class ChocolateBoiler(object): ...
摘要:看一下中這兩者以及的實(shí)現(xiàn)前面提到類相當(dāng)于元類的實(shí)例化,再聯(lián)系創(chuàng)建單例模式時(shí)使用的函數(shù),用的是,其實(shí)用三種中任何一種都是可以的,來看一下使用元類時(shí)各方法的調(diào)用情況結(jié)果元類的和只在創(chuàng)建類調(diào)用了一次,而創(chuàng)建的實(shí)例時(shí),每次都會(huì)調(diào)用元類的方法 單例模式的實(shí)現(xiàn)方式 將類實(shí)例綁定到類變量上 class Singleton(object): _instance = None def ...
摘要:本篇文章總結(jié)了目前主流的實(shí)現(xiàn)單例模式的方法供讀者參考。使用實(shí)現(xiàn)單例模式同樣,我們?cè)陬惖膭?chuàng)建時(shí)進(jìn)行干預(yù),從而達(dá)到實(shí)現(xiàn)單例的目的。 很多初學(xué)者喜歡用 全局變量 ,因?yàn)檫@比函數(shù)的參數(shù)傳來傳去更容易讓人理解。確實(shí)在很多場景下用全局變量很方便。不過如果代碼規(guī)模增大,并且有多個(gè)文件的時(shí)候,全局變量就會(huì)變得比較混亂。你可能不知道在哪個(gè)文件中定義了相同類型甚至重名的全局變量,也不知道這個(gè)變量在程序的某...
摘要:前言單例模式是設(shè)計(jì)模式中最簡單最容易理解的一種,維基百科的定義如下單例模式,也叫單子模式,是一種常用的軟件設(shè)計(jì)模式。 前言 單例模式是設(shè)計(jì)模式(Design Pattern)中最簡單、最容易理解的一種,維基百科[1]的定義如下: 單例模式,也叫單子模式,是一種常用的軟件設(shè)計(jì)模式。在應(yīng)用這個(gè)模式時(shí),單例對(duì)象的類 類 (計(jì)算機(jī)科學(xué)))必須保證只有一個(gè)實(shí)例存在。許多時(shí)候整個(gè)系統(tǒng)只需要擁有一...
閱讀 2141·2023-04-26 00:50
閱讀 2514·2021-10-13 09:39
閱讀 2257·2021-09-22 15:34
閱讀 1648·2021-09-04 16:41
閱讀 1367·2019-08-30 15:55
閱讀 2465·2019-08-30 15:53
閱讀 1734·2019-08-30 15:52
閱讀 779·2019-08-29 16:19