Python面試進階問題,__init__和__new__的區別是什麼?

本文始發於我的公衆號:TechFlow,原創不易,求個關注web


今天這篇是Python專題的第17篇文章,咱們來聊聊Python當中一個新的默認函數__new__。面試

上一篇當中咱們講了如何使用type函數來動態建立Python當中的類,除了type能夠完成這一點以外,還有另一種用法叫作metaclass。本來這一篇應該是繼續元類的內容,講解metaclass的使用。可是metaclass當中用到了一個新的默認函數__new__,關於這個函數你們可能會比較陌生,因此在咱們研究metaclass以前,咱們先來看看__new__這個函數的用法。設計模式

真假構造函數

若是你去面試Python工程師的崗位,面試官問你,請問Python當中的類的構造函數是什麼?併發

你不假思索,固然是__init__啦!若是你這麼回答,頗有可能你就和offer無緣了。由於在Python當中__init__並非構造函數,__new__纔是。是否是有點蒙,多西得(日語:爲何)?咱們不是一直將__init__方法當作構造函數來用的嗎?怎麼又冒出來一個__new__,若是__new__纔是構造函數,那麼爲何咱們建立類的時候歷來不用它呢?編輯器

彆着急,咱們慢慢來看。首先咱們回顧一下__init__的用法,咱們隨便寫一段代碼:函數

class Student:
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
複製代碼

咱們一直都是這麼用的,對不對,毫無問題。可是咱們換一個問題,咱們在Python當中怎麼實現單例(Singleton)的設計模式呢?怎麼樣實現工廠呢?url

從這個問題出發,你會發現只使用__init__函數是不可能完成的,由於__init__並非構造函數,它只是初始化方法。也就是說在調用__init__以前,咱們的實例就已經被建立好了,__init__只是爲這個實例賦上了一些值。若是咱們把建立實例的過程比喻成作一個蛋糕,__init__方法並非烘焙蛋糕的,只是點綴蛋糕的。那麼顯然,在點綴以前必須先烘焙出一個蛋糕來才行,那麼這個烘焙蛋糕的函數就是__new__。spa

__new__函數

咱們來看下__new__這個函數的定義,咱們在使用Python面向對象的時候,通常都不會重構這個函數,而是使用Python提供的默認構造函數,Python默認構造函數的邏輯大概是這樣的:線程

def __new__(cls, *args, **kwargs):
    return super().__new__(cls, *args, **kwargs)
複製代碼

從代碼能夠看得出來,函數當中基本上什麼也沒作,就原封不動地調用了父類的構造函數。這裏隱藏着Python當中類的建立邏輯,是根據繼承關係一級一級建立的。根據邏輯關係,咱們能夠知道,當咱們建立一個實例的時候,其實是先調用的__new__函數建立實例,而後再調用__init__對實例進行的初始化。咱們能夠簡單作個實驗:設計

class Test:
    def __new__(cls):
        print('__new__')
        return object().__new__(cls)
    def __init__(self):
        print('__init__')
複製代碼

當咱們建立Test這個類的時候,經過輸出的順序就能夠知道Python內部的調用順序。

從結果上來看,和咱們的推測徹底同樣。

單例模式

那麼咱們重載__new__函數能夠作什麼呢?通常都是用來完成__init__沒法完成的事情,好比前面說的單例模式,經過__new__函數就能夠實現。咱們來簡單實現一下:

class SingletonObject:
    def __new__(cls, *args, **kwargs):
        if not hasattr(SingletonObject, "_instance"):
            SingletonObject._instance = object.__new__(cls)
        return SingletonObject._instance
    
    def __init__(self):
        pass
複製代碼

固然,若是是在併發場景當中使用,還須要加上線程鎖防止併發問題,但邏輯是同樣的。

除了能夠實現一些功能以外,還能夠控制實例的建立。由於Python當中是先調用的__new__再調用的__init__,因此若是當調用__new__的時候返回了None,那麼最後獲得的結果也是None。經過這個特性,咱們能夠控制類的建立。好比設置條件,只有在知足條件的時候才能正確建立實例,不然會返回一個None。

好比咱們想要建立一個類,它是一個int,可是不能爲0值,咱們就能夠利用__new__的這個特性來實現:

class NonZero(int):
    def __new__(cls, value):
        return super().__new__(cls, value) if value != 0 else None
複製代碼

那麼當咱們用0值來建立它的時候就會獲得一個None,而不是一個實例。

工廠模式

理解了__new__函數的特性以後,咱們就能夠靈活運用了。咱們能夠用它來實現許多其餘的設計模式,好比大名鼎鼎常用的工廠模式

所謂的工廠模式是指經過一個接口,根據參數的取值來建立不一樣的實例。建立過程的邏輯對外封閉,用戶沒必要關係實現的邏輯。就比如一個工廠能夠生產多種零件,用戶並不關心生產的過程,只須要告知須要零件的種類。也所以稱爲工廠模式。

好比說咱們來建立一系列遊戲的類:

class Last_of_us:
    def play(self):
        print('the Last Of Us is really funny')
        
        
class Uncharted:
    def play(self):
        print('the Uncharted is really funny')
        

class PSGame:
    def play(self):
        print('PS has many games')
複製代碼

而後這個時候咱們但願能夠經過一個接口根據參數的不一樣返回不一樣的遊戲,若是不經過__new__,這段邏輯就只能寫成函數而不能經過面向對象來實現。經過重載__new__咱們就能夠很方便地用參數來獲取不一樣類的實例:

class GameFactory:
    games = {'last_of_us': Last_Of_us, 'uncharted': Uncharted}
    def __new__(cls, name):
        if name in cls.games:
            return cls.games[name]()
        else:
            return PSGame()
        

uncharted = GameFactory('uncharted')
last_of_us = GameFactory('last_of_us')
複製代碼

總結

相信看到這裏,關於__new__這個函數的用法應該都能理解了。通常狀況下咱們是用不到這個函數的,只會在一些特殊的場景下使用。雖然如此,咱們學會它並不僅是用來實現設計模式,更重要的是能夠加深咱們對於Python面向對象的理解。

除此以外,另外一個常用__new__場景是元類。因此今天的這篇文章其實也是爲了後面介紹元類的其餘用法打基礎。

若是喜歡本文,能夠的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

相關文章
相關標籤/搜索