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