注:原書做者 Steven F. Lott,原書名爲 Mastering Object-oriented Pythonpython
__init__()
方法意義重大的緣由有兩個。第一是在對象生命週期中初始化是最重要的一步;每一個對象必須正確初始化後才能正常工做。第二是__init__()
參數值能夠有多種形式。程序員
由於有不少種方式爲__init__()
提供參數值,因此對於對象建立有大量的使用案例,咱們能夠看看其中的幾個。咱們想盡量的弄清楚,所以咱們須要定義一個初始化來正確的描述問題域。設計模式
在咱們接觸__init__()
方法以前,不管如何,咱們都須要簡單粗略地看看Python中隱含的object
類的層次結構。函數
在這一章,咱們看看不一樣形式的簡單對象的初始化(例如:打牌)。在這以後,咱們還能夠看看更復雜的對象,就像包含集合的hands
以及包含策略和狀態的players
。ui
每個Python類都隱含了一個超類:object。它是一個很是簡單的類定義,幾乎不作任何事情。咱們能夠建立object
的實例,可是咱們不能用它作太多,由於許多特殊的方法容易拋出異常。編碼
當咱們自定義一個類,object
則爲超類。下面是一個類定義示例,它使用新的名稱簡單的繼承了object
:命令行
class X: pass
下面是和自定義類的一些交互:設計
>>> X.__class__ <class 'type'> >>> X.__class__.__base__ <class 'object'>
咱們能夠看到該類是type
類的一個對象,且它的基類爲object
。code
就像在每一個方法中看到的那樣,咱們也看看從object
繼承的默認行爲。在某些狀況下,超類的特殊方法是咱們想要的。而在其餘狀況下,咱們又須要覆蓋這個特殊方法。對象
__init__()
方法對象生命週期的基礎是它的建立、初始化和銷燬。咱們將建立和銷燬推遲到後面章節的高級特殊方法中講,目前只關注初始化。
全部類的超類object
,有一個默認包含pass
的__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() 104
顯然這是合法的,但這也是容易混淆的根源,因此也是咱們須要避免的緣由。
不管如何,這個設計給予了很大的靈活性,這樣有時候咱們不用在__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的程序中提供合適的診斷信息來展現如何實現不變性。