一問搞懂python的__init__和__new__方法

YZXnuaa發表於2019-04-14

一、文字概述
__new__是在例項建立之前被呼叫的,因為它的任務就是建立例項然後返回該例項,是個靜態方法。

__init__是當例項物件建立完成後被呼叫的,然後設定物件屬性的一些初始值。

故而“ 本質上 ”來說,__new__()方法負責建立例項,而__init__()僅僅是負責例項屬性相關的初始化而已,執行順序是,先new後init。

二、new和init的執行順序
1、當定義類的時候,不定義__new__()方法,這也是我們平時定義類的時候常見的方式。程式碼如下:

class Student(object):
   
    def __init__(self,name,age):
        self.name=name
        self.age=age
        print('我是init')
 
    def study(self):
        print('我愛學習!')
 
if __name__=='__main__':
    s=Student('張三',25)
    print(s.name)
    print(s.age)
    s.study()
執行結果為:

我是init
張三
25
我愛學習!

-------------------------------------------------------------------------------------------------------------------------------------------------------------

2、當定義類的時候自己定義了__new__()方法,

程式碼如下:

class Student(object):
    def __new__(cls,*args,**kwargs):
        print('我是new函式!')   #這是為了追蹤new的執行過程
        print(type(cls))        #這是為了追蹤new的執行過程
        return object.__new__(cls)  #呼叫父類的(object)的new方法,返回一個Student例項,這個例項傳遞給init的self引數
 
    def __init__(self,name,age):
        self.name=name
        self.age=age
        print('我是init')
 
    def study(self):
        print('我愛學習!')
 
if __name__=='__main__':
    s=Student('張三',25)
    print(s.name)
    print(s.age)
    s.study()
執行結果為:

我是new函式!
<class 'type'>
我是init
張三
25
我愛學習!

由此可見的確是先執行new,再執行init。

3、從自定義的類繼承

前面的Student類是從object繼承的,現在從自定義繼承,程式碼如下:

class Parent:
    def __new__(cls,*args,**kwargs):
        print('我是父親類的new')
        print(type(cls))
        return object.__new__(cls)
    def __init__(self):
        print('我是父親類的init')
 
class Children(Parent):
    def __init__(self,name,age):
        self.name=name
        self.age=age
        print('我是子類的init')
 
if __name__=='__main__':
    child=Children('李四',30)
    print(child.name)
    print(child.age)
執行結果為:

我是父親類的new
<class 'type'>
我是子類的init
李四
30

從上面可以看出,如果某個類自己未定義new,則執行的是父類的new函式。

三、__new__()一定要有返回值
前面說到,new函式實際上就是建立當前類的例項的,該例項然後傳遞給init的self引數,進行例項的初始化,故而如果new函式不返回值,則無法構建例項,如下:

class Student(object):
    def __new__(cls,*args,**kwargs):
        print('我是new函式!')   #這是為了追蹤new的執行過程
        print(type(cls))        #這是為了追蹤new的執行過程
 
    def __init__(self,name,age):
        self.name=name
        self.age=age
        print('我是init')
 
    def study(self):
        print('我愛學習!')
 
 
if __name__=='__main__':
    s=Student('張三',25)
    print(s.name)
    print(s.age)
    s.study()
執行結果為:

我是new函式!
<class 'type'>
AttributeError: 'NoneType' object has no attribute 'name'

從上面可見,雖然在建立s例項的過程中,呼叫了new函式,列印出了相關的值,但是因為沒有返回值,故而沒能夠建立例項s,那麼在初始化init的時候自然會出錯,這就是為什麼會顯示沒有name屬性的原因了,因為根本沒建立成功!

四、new函式到底定義在哪裡?
       實際上,new函式並不是定義在object中,它定義在 元類 type裡面,可以通過檢視 help(type) 或者是help(type.__new__)

進行檢視,__new__是type的成員,而python中所有的類都是type的例項,包括object,故而通過object,Student.__new__()的形式初始化就在正常不過了,這不就是“ 例項.方法 ”嗎?
 

      new的定義如下:

__new__(*args, **kwargs) method of builtins.type instance
    Create and return a new object.  See help(type) for accurate signature. 

這就是為什麼很多人在自定義new的時候寫成,如下形式,也就不足為怪了:

def __new__(cls,*args,**kwargs):
        print('我是new函式!')   #這是為了追蹤new的執行過程
        print(type(cls))        #這是為了追蹤new的執行過程
        return object.__new__(cls)  #呼叫父類的(object)的new方法,返回一個Student例項,這個例項傳遞給init的self引數
因為這不就是和定義一樣嗎?雖然多了一個cls,實際上因為是*args的關係,這並不會有影響,這個引數實際上就是要建立的那個類的例項所屬的型別,如Student。

 

五、new的主要應用
   1、__new__方法主要是當你繼承一些不可變的class時(比如int, str, tuple), 提供給你一個自定義這些類的例項化過程的途徑

   2、還有就是實現自定義的metaclass

   3、用__new__來實現單例

詳細使用會在後面詳細說明!

5.1 __new__函式的本質

注意事項:

(1)__new__方法是定義在元類type裡面的,作用就是專門建立例項的。

(2)__new__的本質上是一個“類方法”,故而第一個引數為cls,但是因為系統知道它是類方法,所以有不需要顯式新增@classmethod

(3)__new__必須具有返回值,否則無法建立物件,因為__init__函式需要這個返回值

(4)自己在定義__new__的時候,引數要與__init__函式的引數匹配,我可以不用到這些引數,但一定要匹配。或者可以使用*arg和**args的形式。

例子1:

class Parent:
    def __new__(cls,*arg):   #這裡是正確的,因為*arg可以匹配任意多個未知引數
        return object.__new__(cls)
 
    def __init__(self,age,name):
        self.age=age
        self.name=name
 
p=Parent(23,'feifei')
print(p.age)
print(p.name)
例子2:

class Parent:
    def __new__(cls,age,name):   #正確,雖然沒用age和name,但也要匹配
        return object.__new__(cls)
 
    def __init__(self,age,name):
        self.age=age
        self.name=name
 
p=Parent(23,'feifei')
print(p.age)
print(p.name)
 上面的兩個例子產生的結果為

23

feifei

例子3:

class Parent:
    def __new__(cls):   #這是不行的,因為引數不匹配
        return object.__new__(cls)
    def __init__(self,age,name):
        self.age=age
        self.name=name
 
p=Parent(23,'feifei')
print(p.age)
print(p.name)
下面詳細解釋建立物件p的過程,

第一步:python會呼叫Parent.__new__(Parent,23,‘name') 函式,它會返回一個物件,即object.__new__(Parent) 物件,這裡的age和name都沒有使用。為什麼呢?因為__new__方法是定義在type元類中的,而object本身又是type元類的一個物件,(不明白這裡的可以看我的文章,關於python元類)這不就是一個簡單的   物件.方法(引數)  的簡單呼叫嗎?

第二步:再呼叫__init__函式進行初始化。

 

六、總結
  1、建立例項的時候,先new後init

  2、new定義在type元類中,必須具有返回值,

  3、new的作用就是建立例項,然後將建立的例項傳遞給init進行初始化
 

相關文章