詳細解讀Python中的__init__()方法


init()方法意義重大的原因有兩個。第一個原因是在對象生命周期中初始化是最重要的一步;每個對象必須正確初始化后才能正常工作。第二個原因是init()參數值可以有多種形式。

因為有很多種方式為init()提供參數值,對於對象創建有大量的用例,我們可以看看其中的幾個。我們想盡可能的弄清楚,因此我們需要定義一個初始化來正確的描述問題區域。

在我們接觸init()方法之前,無論如何,我們都需要粗略、簡單地看看在Python中隱含的object類的層次結構。

在這一章,我們看看不同形式的簡單對象的初始化(例如:打牌)。在這之后,我們還可以看看更復雜的對象,就像包含集合的hands對象以及包含策略和狀態的players。

隱含的超類——object

每一個Python類都隱含了一個超類:object。它是一個非常簡單的類定義,幾乎不做任何事情。我們可以創建object的實例,但是我們不能用它做太多,因為許多特殊的方法容易拋出異常。

當我們自定義一個類,object則為超類。下面是一個類定義示例,它使用新的名稱簡單的繼承了object:

class X: pass

下面是和自定義類的一些交互:

>>> X.__class__
<class 'type'>
>>> X.__class__.__base__
<class 'object'>

我們可以看到該類是type類的一個對象,且它的基類為object。

就像在每個方法中看到的那樣,我們也看看從object繼承的默認行為。在某些情況下,超類特殊方法的行為是我們所想要的。在其他情況下,我們需要覆蓋這個特殊方法。

基類對象的init()方法

對象生命周期的基礎是創建、初始化和銷毀。我們將創建和銷毀的高級特殊方法推遲到后面的章節中,目前只關注初始化。

所有類的超類object,有一個默認包含pass的init()實現,我們不需要去實現init()。如果不實現它,則在對象創建后就不會創建實例變量。在某些情況下,這種默認行為是可以接受的。

我們總是給對象添加屬性,該對象為基類object的子類。思考以下類,需要兩個實例變量但不初始化它們:

class Rectangle: def area(self): return self.length * self.width

 

Rectangle類有一個使用兩個屬性來返回一個值的方法。這些屬性沒有初始化。這是合法的Python代碼。它可以有效的避免專門設置屬性,雖然感覺有點奇怪,但是有效。

下面是於Rectangle類的交互:

>>> r = Rectangle() >>> r.length, r.width = 13, 8
>>> r.area()

 

顯然這是合法的,但也是容易混淆的根源,所以也是我們需要避免的原因。

無論如何,這個設計給予了很大的靈活性,這樣有時候我們不用在init()方法中設置所有屬性。至此我們走的很順利。一個可選屬性其實就是一個子類,只是沒有真正的正式聲明為子類。我們創建多態在某種程度上可能會引起混亂以及if語句的不恰當使用所造成的盤繞。雖然未初始化的屬性可能是有用的,但很有可能是糟糕設計的前兆。

《Python之禪》中的建議:

"顯式比隱式更好。"

一個init()方法應該讓實例變量顯式。

可憐的多態

靈活和愚蠢就在一念之間。

當我們覺得需要像下面這樣寫的時候,我們正從靈活的邊緣走向愚蠢:

if 'x' in self.__dict__:

或者:

try: self.x except AttributeError:

 

(就是使用隱示變量還要對變量的值進行判斷)
是時候重新考慮API並添加一個通用的方法或屬性。重構比添加if語句更明智。

在超類中實現init()

我們通過實現init()方法來初始化對象。當一個對象被創建,Python首先創建一個空對象,然后為那個新對象調用init()方法。這個方法函數通常用來創建對象的實例變量並執行任何其他一次性處理。

下面是Card類示例定義的層次結構。我們將定義Card超類和三個子類,這三個子類是Card的變種。兩個實例變量直接由參數值設置,兩個變量通過初始化方法計算:

class Card: def __init__(self, rank, suit): self.suit = suit self.rank = rank self.hard, self.soft = self._points() class NumberCard(Card): def _points(self): return int(self.rank), int(self.rank) class AceCard(Card): def _points(self): return 1, 11

class FaceCard(Card): def _points(self): return 10, 10

在這個示例中,我們提取init()方法到超類,這樣在Card超類中的通用初始化可以適用於三個子類NumberCard、AceCard和FaceCard。

這是一種常見的多態設計。每一個子類都提供一個唯一的_points()方法實現。所有子類都有相同的簽名:有相同的方法和屬性。這三個子類的對象在一個應用程序中可以交替使用。

如果我們為花色使用簡單的字符,我們可以創建Card實例,如下所示:

cards = [AceCard('A', '?'), NumberCard('2','?'), NumberCard('3','?'),]

我們在列表中枚舉出一些牌的類、牌值和花色。從長遠來說,我們需要更智能的工廠函數來創建Card實例;用這個方法枚舉52張牌無聊且容易出錯。在我們接觸工廠函數之前,我們看一些其他問題。

使用init()創建顯式常量

可以給牌定義花色類。在二十一點中,花色無關緊要,簡單的字符串就可以。

我們使用花色構造函數作為創建常量對象的示例。在許多情況下,我們應用中小部分對象可以通過常量集合來定義。小部分的靜態對象可能是實現策略模式或狀態模式的一部分。

在某些情況下,我們會有一個在初始化或配置文件中創建的常量對象池,或者我們可以基於命令行參數創建常量對象。我們會在第十六章《通過命令進行復制》中獲取初始化設計和啟動設計的詳細信息。

Python沒有簡單正式的機制來定義一個不可變對象,我們將在第三章《屬性訪問、方法屬性和描述符》看看保證不可變性的相關技術。在本示例中,花色不可變是有道理的。

下面這個類,我們將用於創建兩個個顯式常量:

class Suit: def __init__(self, name, symbol): self.name= name self.symbol= symbol

下面是通過這個類創建的常量:

Club, Diamond, Heart, Spade = Suit('Club','?'), Suit('Diamond','?'), Suit('Heart','?'), Suit('Spade','?')

現在我們可以通過下面展示的代碼片段創建cards:

cards = [AceCard('A', Spade), NumberCard('2', Spade), NumberCard('3', Spade),]

這個小示例,這種方法對於單個特性的花色代碼來說並不是一個巨大的進步。在更復雜的情況下,會有一些策略或狀態對象通過這個方式創建。通過從小的、靜態的常量對象中復用可以使策略或狀態設計模式更有效率。

我們必須承認,在Python中這些對象並不是技術上一成不變的,它是可變的。進行額外的編碼使得這些對象真正不變可能會有一些好處。

無關緊要的不變性

不變性很有吸引力但卻容易帶來麻煩。有時候被神話般的“惡意程序員”在他們的應用程序中通過修改常量值進行調整。從設計上考慮,這是非常愚蠢的。這些神話般的、惡意的程序員不會停止這樣做,因為已經沒有更好的方法去更簡潔簡單的在Python中編碼。惡意程序員訪問到源碼並且修改它僅僅是希望盡可能輕松地編寫代碼來修改一個常數。

在定義不可變對象的類的時候最好不要掙扎太久。在第三章《屬性訪問、方法屬性和描述符》中,我們將通過在有bug的程序中提供合適的診斷信息來展示如何實現不變性。

 

 

__init__方法使用

 

 1.使用demo

初始化 。注意,這個名稱的開始和結尾都是雙下划線
使用__init__方法
代碼例子

#!/usr/bin/python # Filename: class_init.py
class Person: def __init__(self, name): self.name = name def sayHi(self): print Hello, my name is, self.name p = Person(Swaroop) p.sayHi() # This short example can also be written as Person(Swaroop).sayHi()

 

輸出

$ python class_init.py Hello, my name is Swaroop

 

它如何工作
這里,我們把__init__方法定義為取一個參數name(以及普通的參數self)。在這個__init__里,我們只是創建一個新的域,也稱為name。注意它們是兩個不同的變量,盡管它們有相同的名字。點號使我們能夠區分它們。

最重要的是,我們沒有專門調用__init__方法,只是在創建一個類的新實例的時候,把參數包括在圓括號內跟在類名后面,從而傳遞給__init__方法。這是這種方法的重要之處。

現在,我們能夠在我們的方法中使用self.name域。這在sayHi方法中得到了驗證。
       __init__方法類似於C 、C#和Java中的 constructor 

2.注意點

 

 

 

注意1、__init__並不相當於C#中的構造函數,執行它的時候,實例已構造出來了。

class A(object): def __init__(self,name): self.name=name def getName(self): return 'A '+self.name

 

當我們執行

a=A('hello')

 

時,可以理解為

a=object.__new__(A) A.__init__(a,'hello')

 

即__init__作用是初始化已實例化后的對象。

 

注意2、子類可以不重寫__init__,實例化子類時,會自動調用超類中已定義的__init__

class B(A): def getName(self): return 'B '+self.name if __name__=='__main__': b=B('hello') print b.getName()

 

但如果重寫了__init__,實例化子類時,則不會隱式的再去調用超類中已定義的__init__

class C(A):     def __init__(self):         pass     def getName(self):         return 'C
 '+self.name
   if __name__=='__main__':     c=C()     print c.getName() 

 

則會報"AttributeError: 'C' object has no attribute 'name'”錯誤,所以如果重寫了__init__,為了能使用或擴展超類中的行為,最好顯式的調用超類的__init__方法

class C(A):     def __init__(self,name):         super(C,self).__init__(name)     def getName(self):         return 'C
 '+self.name
   if __name__=='__main__':     c=C('hello')        print c.getName()
 

 

1.使用demo

初始化 。注意,這個名稱的開始和結尾都是雙下划線
使用__init__方法
代碼例子
#!/usr/bin/python
# Filename: class_init.py
class Person:
    def __init__(self, name):
        self.name = name
    def sayHi(self):
        print Hello, my name is, self.name

p = Person(Swaroop)
p.sayHi()
# This short example can also be written as Person(Swaroop).sayHi()

輸出
$ python class_init.py
Hello, my name is Swaroop

它如何工作
這里,我們把__init__方法定義為取一個參數name(以及普通的參數self)。在這個__init__里,我們只是創建一個新的域,也稱為name。注意它們是兩個不同的變量,盡管它們有相同的名字。點號使我們能夠區分它們。

最重要的是,我們沒有專門調用__init__方法,只是在創建一個類的新實例的時候,把參數包括在圓括號內跟在類名后面,從而傳遞給__init__方法。這是這種方法的重要之處。

現在,我們能夠在我們的方法中使用self.name域。這在sayHi方法中得到了驗證。
       __init__方法類似於C 、C#和Java中的 constructor 

 

 

 

2.注意點

 

 

注意1、__init__並不相當於C#中的構造函數,執行它的時候,實例已構造出來了。

1
2
3
4
5
class A(object):
    def __init__(self,name):
        self.name=name
    def getName(self):
        return 'A '+self.name

當我們執行

1
a=A('hello')

時,可以理解為

1
2
a=object.__new__(A)
A.__init__(a,'hello')

即__init__作用是初始化已實例化后的對象。

注意2、子類可以不重寫__init__,實例化子類時,會自動調用超類中已定義的__init__

1
2
3
4
5
6
7
class B(A):
    def getName(self):
        return 'B '+self.name
 
if __name__=='__main__':
    b=B('hello')
    print b.getName()

但如果重寫了__init__,實例化子類時,則不會隱式的再去調用超類中已定義的__init__

1
2
3
4
5
6
7
8
9
class C(A):
    def __init__(self):
        pass
    def getName(self):
        return 'C '+self.name
 
if __name__=='__main__':
    c=C()
    print c.getName()

則會報"AttributeError: 'C' object has no attribute 'name'”錯誤,所以如果重寫了__init__,為了能使用或擴展超類中的行為,最好顯式的調用超類的__init__方法

1
2
3
4
5
6
7
8
9
class C(A):
    def __init__(self,name):
        super(C,self).__init__(name)
    def getName(self):
        return 'C '+self.name
 
if __name__=='__main__':
    c=C('hello')   
    print c.getName()

--------------------- 本文來自 jiesa 的CSDN 博客 ,全文地址請點擊:https://blog.csdn.net/JIESA/article/details/50525309?utm_source=copy 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM