這篇文章主要介紹了Python中的__init__()方法,__init__()方法是Python學習當中重要的基礎知識,需要的朋友可以參考下
__init__()方法意義重大的原因有兩個。第一個原因是在對象生命週期中初始化是最重要的一步;每個對象必須正確初始化後才能正常工作。第二個原因是__init__()參數值可以有多種形式。
因為有很多種方式為__init__()提供參數值,對於對象建立有大量的用例,我們可以看看其中的幾個。我們想儘可能的弄清楚,因此我們需要定義一個初始化來正確的描述問題地區。
在我們接觸__init__()方法之前,無論如何,我們都需要粗略、簡單地看看在Python中隱含的object類的階層。
在這一章,我們看看不同形式的簡單對象的初始化(例如:打牌)。在這之後,我們還可以看看更複雜的對象,就像包含集合的hands對象以及包含策略和狀態的players。
隱含的超類——object
每一個Python類都隱含了一個超類:object。它是一個非常簡單的類定義,幾乎不做任何事情。我們可以建立object的執行個體,但是我們不能用它做太多,因為許多特殊的方法容易拋出異常。
當我們自訂一個類,object則為超類。下面是一個類定義樣本,它使用新的名稱簡單的繼承了object:
?
下面是和自訂類的一些互動:
?
| 1 2 3 4 |
>>> X.__class__ <class 'type'> >>> X.__class__.__base__ <class 'object'> |
我們可以看到該類是type類的一個對象,且它的基類為object。
就像在每個方法中看到的那樣,我們也看看從object繼承的預設行為。在某些情況下,超類特殊方法的行為是我們所想要的。在其他情況下,我們需要覆蓋這個特殊方法。
基類對象的init()方法
對象生命週期的基礎是建立、初始化和銷毀。我們將建立和銷毀的進階特殊方法延遲到後面的章節中,目前只關注初始化。
所有類的超類object,有一個預設包含pass的__init__()實現,我們不需要去實現__init__()。如果不實現它,則在對象建立後就不會建立執行個體變數。在某些情況下,這種預設行為是可以接受的。
我們總是給對象添加屬性,該對象為基類object的子類。思考以下類,需要兩個執行個體變數但不初始化它們:
?
| 1 2 3 |
class Rectangle: def area(self): return self.length * self.width |
Rectangle類有一個使用兩個屬性來返回一個值的方法。這些屬性沒有初始化。這是合法的Python代碼。它可以有效避免專門設定屬性,雖然感覺有點奇怪,但是有效。
下面是於Rectangle類的互動:
?
| 1 2 3 4 |
>>> r = Rectangle() >>> r.length, r.width = 13, 8 >>> r.area() 104 |
顯然這是合法的,但也是容易混淆的根源,所以也是我們需要避免的原因。
無論如何,這個設計給予了很大的靈活性,這樣有時候我們不用在__init__()方法中設定所有屬性。至此我們走的很順利。一個可選屬性其實就是一個子類,只是沒有真正的正式聲明為子類。我們建立多態在某種程度上可能會引起混亂以及if語句的不恰當使用所造成的盤繞。雖然未初始化的屬性可能是有用的,但很有可能是糟糕設計的前兆。
《Python之禪》中的建議:
"顯式比隱式更好。"
一個__init__()方法應該讓執行個體變數顯式。
可憐的多態
靈活和愚蠢就在一念之間。
當我們覺得需要像下面這樣寫的時候,我們正從靈活的邊緣走向愚蠢:
?
| 1 |
if 'x' in self.__dict__: |
或者:
?
| 1 2 3 |
try: self.x except AttributeError: |
是時候重新考慮API並添加一個通用的方法或屬性。重構比添加if語句更明智。
在超類中實現init()
我們通過實現__init__()方法來初始化對象。當一個對象被建立,Python首先建立一個Null 物件,然後為那個新對象調用__init__()方法。這個方法函數通常用來建立對象的執行個體變數並執行任何其他一次性處理。
下面是Card類樣本定義的階層。我們將定義Card超類和三個子類,這三個子類是Card的變種。兩個執行個體變數直接由參數值設定,兩個變數通過初始化方法計算:
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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執行個體,如下所示:
?
| 1 |
cards = [AceCard('A', '?'), NumberCard('2','?'), NumberCard('3','?'),] |
我們在列表中枚舉出一些牌的類、牌值和花色。從長遠來說,我們需要更智能的工廠函數來建立Card執行個體;用這個方法枚舉52張牌無聊且容易出錯。在我們接觸工廠函數之前,我們看一些其他問題。
使用init()建立顯式常量
可以給牌定義花色類。在二十一點中,花色無關緊要,簡單的字串就可以。
我們使用花色建構函式作為建立常量對象的樣本。在許多情況下,我們應用中小部分對象可以通過常量集合來定義。小部分的靜態對象可能是實現策略模式或狀態模式的一部分。
在某些情況下,我們會有一個在初始化或設定檔中建立的常量對象池,或者我們可以基於命令列參數建立常量對象。我們會在第十六章《通過命令進行複製》中擷取初始化設計和啟動設計的詳細資料。
Python沒有簡單正式的機制來定義一個不可變對象,我們將在第三章《屬性訪問、方法屬性和描述符》看看保證不可變性的相關技術。在本樣本中,花色不可變是有道理的。
下面這個類,我們將用於建立四個顯式常量:
?
| 1 2 3 4 |
class Suit: def __init__(self, name, symbol): self.name= name self.symbol= symbol |
下面是通過這個類建立的常量:
?
| 1 |
Club, Diamond, Heart, Spade = Suit('Club','?'), Suit('Diamond','?'), Suit('Heart','?'), Suit('Spade','?') |
現在我們可以通過下面展示的程式碼片段建立cards:
?
| 1 |
cards = [AceCard('A', Spade), NumberCard('2', Spade), NumberCard('3', Spade),] |
這個小樣本,這種方法對於單個特性的花色代碼來說並不是一個巨大的進步。在更複雜的情況下,會有一些策略或狀態物件通過這個方式建立。通過從小的、靜態常量對象中複用可以使策略或狀態設計模式更有效率。
我們必須承認,在Python中這些對象並不是技術上一成不變的,它是可變的。進行額外的編碼使得這些對象真正不變可能會有一些好處。
無關緊要的不變性
不變性很有吸引力但卻容易帶來麻煩。有時候被神話般的“惡意程式員”在他們的應用程式中通過修改常量值進行調整。從設計上考慮,這是非常愚蠢的。這些神話般的、惡意的程式員不會停止這樣做,因為已經沒有更好的方法去更簡潔簡單的在Python中編碼。惡意程式員訪問到源碼並且修改它僅僅是希望儘可能輕鬆地編寫代碼來修改一個常數。
在定義不可變對象的類的時候最好不要掙紮太久。在第三章《屬性訪問、方法屬性和描述符》中,我們將通過在有bug的程式中提供合適的診斷資訊來展示如何?不變性。