首頁 >後端開發 >Python教學 >Python中new類別方法和init 實例方法以及單例模式的介紹(附範例)

Python中new類別方法和init 實例方法以及單例模式的介紹(附範例)

不言
不言轉載
2019-01-28 11:18:552254瀏覽

本篇文章帶給大家的內容是關於Python中new類別方法和init 實例方法以及單例模式的介紹(附範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

「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中new類別方法和init 實例方法以及單例模式的介紹(附範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除