這篇文章主要介紹了Python中的__init__()方法,__init__()方法是Python學習當中重要的基礎知識,需要的朋友可以參考下
__init__()方法意義重大的原因有兩個。第一個原因是在對象生命周期中初始化是最重要的一步;每個對象必須正確初始化後才能正常工作。第二個原因是__init__()參數值可以有多種形式。
因為有很多種方式為__init__()提供參數值,對於對象創建有大量的用例,我們可以看看其中的幾個。我們想盡可能的弄清楚,因此我們需要定義一個初始化來正確的描述問題區域。
在我們接觸__init__()方法之前,無論如何,我們都需要粗略、簡單地看看在Python中隱含的object類的層次結構。
在這一章,我們看看不同形式的簡單對象的初始化(例如:打牌)。在這之後,我們還可以看看更復雜的對象,就像包含集合的hands對象以及包含策略和狀態的players。
隱含的超類——object
每一個Python類都隱含了一個超類:object。它是一個非常簡單的類定義,幾乎不做任何事情。我們可以創建object的實例,但是我們不能用它做太多,因為許多特殊的方法容易拋出異常。
當我們自定義一個類,object則為超類。下面是一個類定義示例,它使用新的名稱簡單的繼承了object:
?
1 2 class X: pass下面是和自定義類的一些交互:
?
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.widthRectangle類有一個使用兩個屬性來返回一個值的方法。這些屬性沒有初始化。這是合法的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首先創建一個空對象,然後為那個新對象調用__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的程序中提供合適的診斷信息來展示如何實現不變性。