摘要:同時(shí),有多個(gè)類級(jí)別的靜態(tài)構(gòu)造函數(shù)的方法。這個(gè)累贅,無論如何,是被傳遞到每個(gè)多帶帶的對(duì)象構(gòu)造函數(shù)表達(dá)式中。我們可能只有幾個(gè)特定的擔(dān)憂,提供額外關(guān)鍵字參數(shù)給構(gòu)造函數(shù)。
沒有__init__()的無狀態(tài)對(duì)象注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python
下面這個(gè)示例,是一個(gè)簡(jiǎn)化去掉了__init__()的類。這是一個(gè)常見的Strategy設(shè)計(jì)模式對(duì)象。策略對(duì)象插入到主對(duì)象來實(shí)現(xiàn)一種算法或者決策。它可能依賴主對(duì)象的數(shù)據(jù),策略對(duì)象自身可能沒有任何數(shù)據(jù)。我們經(jīng)常設(shè)計(jì)策略類來遵循Flyweight設(shè)計(jì)模式:我們避免在Strategy對(duì)象內(nèi)部進(jìn)行存儲(chǔ)。所有提供給Strategy的值都是作為方法的參數(shù)值。Strategy對(duì)象本身可以是無狀態(tài)的。這更多是為了方法函數(shù)的集合而非其他。
在本例中,我們?yōu)?b>Player實(shí)例提供了游戲策略。下面是一個(gè)抓牌和減少其他賭注的策略示例(比較笨的策略):
class GameStrategy: def insurance(self, hand): return False def split(self, hand): return False def double(self, hand): return False def hit(self, hand): return sum(c.hard for c in hand.cards) <= 17
每個(gè)方法都需要當(dāng)前的Hand作為參數(shù)值。決策是基于可用信息的,也就是指莊家的牌和閑家的牌。
我們可以使用不同的Player實(shí)例來構(gòu)建單個(gè)策略實(shí)例,如下面代碼片段所示:
dumb = GameStrategy()
我們可以想象創(chuàng)造一組相關(guān)的策略類,在21點(diǎn)中玩家可以針對(duì)各種決策使用不同的規(guī)則。
一些額外的類定義如前所述,一個(gè)玩家有兩個(gè)策略:一個(gè)用于下注,一個(gè)用于出牌。每個(gè)Player實(shí)例都與模擬計(jì)算執(zhí)行器有一序列的交互。我們稱計(jì)算執(zhí)行器為Table類。
Table類需要Player實(shí)例提供以下事件:
玩家必須基于下注策略來設(shè)置初始賭注。
玩家將得到一手牌。
如果手牌是可分離的,玩家必須決定是分離或不基于出牌策略。這可以創(chuàng)建額外的Hand實(shí)例。在一些賭場(chǎng),額外的一手牌也是可分離的。
對(duì)于每個(gè)Hand實(shí)例,玩家必須基于出牌策略來決定是要牌、加倍或停牌。
玩家會(huì)獲得獎(jiǎng)金,然后基于輸贏情況調(diào)整下注策略。
從這,我們可以看到Table類有許多API方法來獲得賭注,創(chuàng)建Hand對(duì)象提供分裂、分解每一手牌、付清賭注。這個(gè)對(duì)象跟蹤了一組Players的出牌狀態(tài)。
以下是處理賭注和牌的Table類:
class Table: def __init__(self): self.deck = Deck() def place_bet(self, amount): print("Bet", amount) def get_hand(self): try: self.hand = Hand2(d.pop(), d.pop(), d.pop()) self.hole_card = d.pop() except IndexError: # Out of cards: need to shuffle. self.deck = Deck() return self.get_hand() print("Deal", self.hand) return self.hand def can_insure(self, hand): return hand.dealer_card.insure
Player使用Table類來接收賭注,創(chuàng)建一個(gè)Hand對(duì)象,出牌時(shí)根據(jù)這手牌來決定是否買保險(xiǎn)。使用額外方法去獲取牌并決定償還。
在get_hand()中展示的異常處理不是一個(gè)精確的賭場(chǎng)玩牌模型。這可能會(huì)導(dǎo)致微小的統(tǒng)計(jì)誤差。更精確的模擬需要編寫一副牌,當(dāng)空的時(shí)候可以重新洗牌,而不是拋出異常。
為了正確地交互和模擬現(xiàn)實(shí)出牌,Player類需要一個(gè)下注策略。下注策略是一個(gè)有狀態(tài)的對(duì)象,決定了初始賭注。各種下注策略調(diào)整賭注通常都是基于游戲的輸贏。
理想情況下,我們渴望有一組下注策略對(duì)象。Python的裝飾器模塊允許我們創(chuàng)建一個(gè)抽象超類。一個(gè)非正式的方法創(chuàng)建策略對(duì)象引發(fā)的異常必須由子類實(shí)現(xiàn)。
我們定義了一個(gè)抽象超類,此外還有一個(gè)具體子類定義了固定下注策略,如下所示:
class BettingStrategy: def bet(self): raise NotImplementedError("No bet method") def record_win(self): pass def record_loss(self): pass class Flat(BettingStrategy): def bet(self): return 1
超類定義了帶有默認(rèn)值的方法。抽象超類中的基本bet()方法拋出異常。子類必須覆蓋bet()方法。其他方法可以提供默認(rèn)值。這里給上一節(jié)的游戲策略添加了下注策略,我們可以看看Player類周圍更復(fù)雜的__init__()方法。
我們可以利用abc模塊正式化抽象超類的定義。就像下面的代碼片段那樣:
import abc class BettingStrategy2(metaclass=abc.ABCMeta): @abstractmethod def bet(self): return 1 def record_win(self): pass def record_loss(self): ? pass
這樣做的優(yōu)勢(shì)在于創(chuàng)建了BettingStrategy2的實(shí)例,不會(huì)造成任何子類bet()的失敗。如果我們?cè)噲D通過未實(shí)現(xiàn)的抽象方法來創(chuàng)建這個(gè)類的實(shí)例,它將引發(fā)一個(gè)異常來替代創(chuàng)建對(duì)象。
是的,抽象方法有一個(gè)實(shí)現(xiàn)。它可以通過super().bet()來訪問。
多策略的__init__()我們可從各種來源創(chuàng)建對(duì)象。例如,我們可能需要復(fù)制一個(gè)對(duì)象作為創(chuàng)建備份或凍結(jié)一個(gè)對(duì)象的一部分,以便它可以作為字典的鍵或被置入集合中;這是內(nèi)置類set和frozenset背后的想法。
有幾個(gè)總體設(shè)計(jì)模式,它們有多種方法來構(gòu)建一個(gè)對(duì)象。一個(gè)設(shè)計(jì)模式就是一個(gè)復(fù)雜的__init__(),稱為多策略初始化。同時(shí),有多個(gè)類級(jí)別的(靜態(tài))構(gòu)造函數(shù)的方法。
這些都是不兼容的方法。他們有完全不同的接口。
避免克隆方法
在Python中,一個(gè)克隆方法沒必要復(fù)制一個(gè)不需要的對(duì)象。使用克隆技術(shù)表明可能是未能理解Python中的面向?qū)ο笤O(shè)計(jì)原則。
克隆方法封裝了在錯(cuò)誤的地方創(chuàng)建對(duì)象的常識(shí)。被克隆的源對(duì)象不能了解通過克隆建立的目標(biāo)對(duì)象的結(jié)構(gòu)。然而,如果源對(duì)象提供了一個(gè)合理的、得到了良好封裝的接口,反向(目標(biāo)對(duì)象有源對(duì)象相關(guān)的內(nèi)容)是可以接受的。
我們這里展示的例子是有效的克隆,因?yàn)樗鼈兒芎?jiǎn)單。我們將在下一章展開它們。然而,展示這些基本技術(shù)是用來做更多的事情,而不是瑣碎的克隆,我們看看將可變對(duì)象Hand凍結(jié)為不可變對(duì)象。
下面可以通過兩種方式創(chuàng)建Hand對(duì)象的示例:
class Hand3: def __init__(self, *args, **kw): if len(args) == 1 and isinstance(args[0], Hand3): # Clone an existing hand; often a bad idea other = args[0] self.dealer_card = other.dealer_card self.cards = other.cards else: # Build a fresh, new hand. dealer_card, *cards = args self.dealer_card = dealer_card self.cards = list(cards)
第一種情況,從現(xiàn)有的Hand3對(duì)象創(chuàng)建Hand3實(shí)例。第二種情況,從多帶帶的Card實(shí)例創(chuàng)建Hand3對(duì)象。
與frozenset對(duì)象的相似之處在于可由多帶帶的項(xiàng)目或現(xiàn)有set對(duì)象創(chuàng)建。我們將在下一章學(xué)習(xí)創(chuàng)建不可變對(duì)象。使用像下面代碼片段這樣的構(gòu)造,從現(xiàn)有的Hand創(chuàng)建一個(gè)新的Hand使得我們可以創(chuàng)建一個(gè)Hand對(duì)象的備份:
h = Hand(deck.pop(), deck.pop(), deck.pop()) memento = Hand(h)
我們保存Hand對(duì)象到memento變量中。這可以用來比較最后處理的牌與原來手牌,或者我們可以在集合或映射中使用時(shí)凍結(jié)它。
1. 更復(fù)雜的初始化選擇為了編寫一個(gè)多策略初始化,我們經(jīng)常被迫放棄特定的命名參數(shù)。這種設(shè)計(jì)的優(yōu)點(diǎn)是靈活,但缺點(diǎn)是不透明的、毫無意義的參數(shù)命名。它需要大量的用例文檔來解釋變形。
我們還可以擴(kuò)大我們的初始化來分裂Hand對(duì)象。分裂Hand對(duì)象的結(jié)果是只是另一個(gè)構(gòu)造函數(shù)。下面的代碼片段說明了如何分裂Hand對(duì)象:
class Hand4: def __init__(self, *args, **kw): if len(args) == 1 and isinstance(args[0], Hand4): # Clone an existing handl often a bad idea other = args[0] self.dealer_card = other.dealer_card self.cards= other.cards elif len(args) == 2 and isinstance(args[0], Hand4) and "split" in kw: # Split an existing hand other, card = args self.dealer_card = other.dealer_card self.cards = [other.cards[kw["split"]], card] elif len(args) == 3: # Build a fresh, new hand. dealer_card, *cards = args self.dealer_card = dealer_card self.cards = list(cards) else: raise TypeError("Invalid constructor args={0!r} kw={1!r}".format(args, kw)) def __str__(self): return ", ".join(map(str, self.cards))
這個(gè)設(shè)計(jì)包括獲得額外的牌來建立合適的、分裂的手牌。當(dāng)我們從一個(gè)Hand4對(duì)象創(chuàng)建一個(gè)Hand4對(duì)象,我們提供一個(gè)分裂的關(guān)鍵字參數(shù),它從原Hand4對(duì)象使用Card類索引。
下面的代碼片段展示了我們?nèi)绾问褂帽环至训氖峙疲?/p>
d = Deck() h = Hand4(d.pop(), d.pop(), d.pop()) s1 = Hand4(h, d.pop(), split=0) s2 = Hand4(h, d.pop(), split=1)
我們創(chuàng)建了一個(gè)Hand4初始化的h實(shí)例并分裂到兩個(gè)其他Hand4實(shí)例,s1和s2,并處理額外的Card類。21點(diǎn)的規(guī)則只允許最初的手牌有兩個(gè)牌值相等。
雖然這個(gè)__init__()方法相當(dāng)復(fù)雜,它的優(yōu)點(diǎn)是可以并行的方式從現(xiàn)有集創(chuàng)建fronzenset。缺點(diǎn)是它需要一個(gè)大文檔字符串來解釋這些變化。
2. 初始化靜態(tài)方法當(dāng)我們有多種方法來創(chuàng)建一個(gè)對(duì)象時(shí),有時(shí)會(huì)更清晰的使用靜態(tài)方法來創(chuàng)建并返回實(shí)例,而不是復(fù)雜的__init__()方法。
也可以使用類方法作為替代初始化,但是有一個(gè)實(shí)實(shí)在在的優(yōu)勢(shì)在于接收類作為參數(shù)的方法。在凍結(jié)或分裂Hand對(duì)象的情況下,我們可能需要?jiǎng)?chuàng)建兩個(gè)新的靜態(tài)方法凍結(jié)或分離對(duì)象。使用靜態(tài)方法作為代理構(gòu)造函數(shù)是一個(gè)小小的語法變化,但當(dāng)組織代碼的時(shí)候它擁有巨大的優(yōu)勢(shì)。
下面是一個(gè)有靜態(tài)方法的Hand,可用于從現(xiàn)有的Hand實(shí)例構(gòu)建新的Hand實(shí)例:
class Hand5: def __init__(self, dealer_card, *cards): self.dealer_card = dealer_card self.cards = list(cards) @staticmethod def freeze(other): hand = Hand5(other.dealer_card, *other.cards) return hand @staticmethod def split(other, card0, card1 ): hand0 = Hand5(other.dealer_card, other.cards[0], card0) hand1 = Hand5(other.dealer_card, other.cards[1], card1) return hand0, hand1 def __str__(self): return ", ".join(map(str, self.cards))
一個(gè)方法凍結(jié)或創(chuàng)建一個(gè)備份。另一個(gè)方法分裂Hand5實(shí)例來創(chuàng)建兩個(gè)Hand5實(shí)例。
這更具可讀性并保存參數(shù)名的使用來解釋接口。
下面的代碼片段展示了我們?nèi)绾瓮ㄟ^這個(gè)版本分裂Hand5實(shí)例:
d = Deck() h = Hand5(d.pop(), d.pop(), d.pop()) s1, s2 = Hand5.split(h, d.pop(), d.pop())
我們創(chuàng)建了一個(gè)初始的Hand5的h實(shí)例,分裂成兩個(gè)手牌,s1和s2,處理每一個(gè)額外的Card類。split()靜態(tài)方法比__init__()簡(jiǎn)單得多。然而,它不遵循從現(xiàn)有的set對(duì)象創(chuàng)建fronzenset對(duì)象的模式。
更多的__init__()技巧我們會(huì)看看一些其他更高級(jí)的__init__()技巧。在前面的部分這些不是那么普遍有用的技術(shù)。
下面是Player類的定義,使用了兩個(gè)策略對(duì)象和table對(duì)象。這展示了一個(gè)看起來并不舒服的__init__()方法:
class Player: def __init__(self, table, bet_strategy, game_strategy): self.bet_strategy = bet_strategy self.game_strategy = game_strategy self.table = table def game(self): self.table.place_bet(self.bet_strategy.bet()) self.hand = self.table.get_hand() if self.table.can_insure(self.hand): if self.game_strategy.insurance(self.hand): self.table.insure(self.bet_strategy.bet()) # Yet more... Elided for now
Player的__init__()方法似乎只是統(tǒng)計(jì)。只是簡(jiǎn)單傳遞命名好的參數(shù)到相同命名的實(shí)例變量。如果我們有大量的參數(shù),簡(jiǎn)單地傳遞參數(shù)到內(nèi)部變量會(huì)產(chǎn)生過多看似冗余的代碼。
我們可以如下使用Player類(和相關(guān)對(duì)象):
table = Table() flat_bet = Flat() dumb = GameStrategy() p = Player(table, flat_bet, dumb) p.game()
我們可以通過簡(jiǎn)單的傳遞關(guān)鍵字參數(shù)值到內(nèi)部實(shí)例變量來提供一個(gè)非常短的和非常靈活的初始化。
下面是使用關(guān)鍵字參數(shù)值構(gòu)建Player類的示例:
class Player2: def __init__(self, **kw): """Must provide table, bet_strategy, game_strategy.""" self.__dict__.update(kw) def game(self): self.table.place_bet(self.bet_strategy.bet()) self.hand= self.table.get_hand() if self.table.can_insure(self.hand): if self.game_strategy.insurance(self.hand): self.table.insure(self.bet_strategy.bet()) # etc.
為了簡(jiǎn)潔而犧牲了大量可讀性。它跨越到一個(gè)潛在的默默無聞的領(lǐng)域。
因?yàn)?b>__init__()方法減少到一行,它消除了某種程度上“累贅”的方法。這個(gè)累贅,無論如何,是被傳遞到每個(gè)多帶帶的對(duì)象構(gòu)造函數(shù)表達(dá)式中。我們必須將關(guān)鍵字添加到對(duì)象初始化表達(dá)式中,因?yàn)槲覀儾辉偈褂梦恢脜?shù),如下面代碼片段所示:
p2 = Player2(table=table, bet_strategy=flat_bet, game_strategy=dumb)
為什么這樣做呢?
它有一個(gè)潛在的優(yōu)勢(shì)。這樣的類定義是相當(dāng)易于擴(kuò)展的。我們可能只有幾個(gè)特定的擔(dān)憂,提供額外關(guān)鍵字參數(shù)給構(gòu)造函數(shù)。
下面是預(yù)期的用例:
>>> p1 = Player2(table=table, bet_strategy=flat_bet, game_strategy=dumb) >>> p1.game()
下面是一個(gè)額外的用例:
>>> p2 = Player2(table=table, bet_strategy=flat_bet, game_strategy=dumb, log_name="Flat/Dumb") >>> p2.game()
我們添加了一個(gè)與類定義無關(guān)的log_name屬性。也許,這可以被用作統(tǒng)計(jì)分析的一部分。Player2.log_name屬性可以用來注釋日志或其他數(shù)據(jù)的收集。
我們能添加的東西是有限的;我們只能添加沒有與內(nèi)部使用的命名相沖突的參數(shù)。類實(shí)現(xiàn)的常識(shí)是需要的,用于創(chuàng)建沒有濫用已在使用的關(guān)鍵字的子類。由于**kw參數(shù)提供了很少的信息,我們需要仔細(xì)閱讀。在大多數(shù)情況下,比起檢查實(shí)現(xiàn)細(xì)節(jié)我們寧愿相信類是正常工作的。
在超類的定義中是可以做到基于關(guān)鍵字的初始化的,對(duì)于使用超類來實(shí)現(xiàn)子類會(huì)變得稍微的簡(jiǎn)單些。我們可以避免編寫一個(gè)額外的__init__()方法到每個(gè)子類,當(dāng)子類的唯一特性包括了簡(jiǎn)單新實(shí)例變量。
這樣做的缺點(diǎn)是,我們已經(jīng)模糊了沒有正式通過子類定義記錄的實(shí)例變量。如果只是一個(gè)小變量,整個(gè)子類可能有太多的編程開銷用于給一個(gè)類添加單個(gè)變量。然而,一個(gè)小變量常常會(huì)導(dǎo)致第二個(gè)、第三個(gè)。不久,我們將會(huì)認(rèn)識(shí)到一個(gè)子類會(huì)比一個(gè)極其靈活的超類還要更智能。
我們可以(也應(yīng)該)通過混合的位置和關(guān)鍵字實(shí)現(xiàn)生成這些,如下面的代碼片段所示:
class Player3(Player): def __init__(self, table, bet_strategy, game_strategy, **extras): self.bet_strategy = bet_strategy self.game_strategy = game_strategy self.table= table self.__dict__.update(extras)
這比完全開放定義更明智。我們已經(jīng)取得了所需的位置參數(shù)。我們留下任何非必需參數(shù)作為關(guān)鍵字。這個(gè)闡明了__init__()給出的任何額外的關(guān)鍵字參數(shù)的使用。
這種靈活的關(guān)鍵字初始化取決于我們是否有相對(duì)透明的類定義。這種開放的態(tài)度面對(duì)改變需要注意避免調(diào)試名稱沖突,因?yàn)殛P(guān)鍵字參數(shù)名是開放式的。
1. 初始化類型驗(yàn)證類型驗(yàn)證很少是一個(gè)合理的要求。在某種程度上,是沒有對(duì)Python完全理解。名義目標(biāo)是驗(yàn)證所有參數(shù)是否是一個(gè)合適的類型。試圖這樣做的原因主要是因?yàn)?em>適當(dāng)的定義往往是過于狹隘以至于沒有什么真正的用途。
這不同于確認(rèn)對(duì)象滿足其他條件。數(shù)字范圍檢查,例如,防止無限循環(huán)的必要。
我們可以制造問題去試圖做些什么,就像下面__init__()方法中那樣:
class ValidPlayer: def __init__(self, table, bet_strategy, game_strategy): assert isinstance(table, Table) assert isinstance(bet_strategy, BettingStrategy) assert isinstance(game_strategy, GameStrategy) self.bet_strategy = bet_strategy self.game_strategy = game_strategy self.table = table
isinstance()方法檢查、規(guī)避Python的標(biāo)準(zhǔn)鴨子類型。
我們寫一個(gè)賭場(chǎng)游戲模擬是為了嘗試不斷變化的GameStrategy。這些很簡(jiǎn)單(僅僅四個(gè)方法),幾乎沒有從超類的繼承中得到任何幫助。我們可以獨(dú)立的定義缺乏整體的超類。
這個(gè)示例中所示的初始化錯(cuò)誤檢查,將迫使我們通過錯(cuò)誤檢查的創(chuàng)建子類。沒有可用的代碼是繼承自抽象超類。
最大的一個(gè)鴨子類型問題就圍繞數(shù)值類型。不同的數(shù)值類型將工作在不同的上下文中。試圖驗(yàn)證類型的爭(zhēng)論可能會(huì)阻止一個(gè)完美合理的數(shù)值類型正常工作。當(dāng)嘗試驗(yàn)證時(shí),我們有以下兩個(gè)選擇在Python中:
我們編寫驗(yàn)證,這樣一個(gè)相對(duì)狹窄的集合類型是允許的,總有一天代碼會(huì)因?yàn)槁斆鞯男骂愋捅唤苟袛唷?/p>
我們避開驗(yàn)證,這樣一個(gè)相對(duì)廣泛的集合類型是允許的,總有一天代碼會(huì)因?yàn)椴宦斆鞯仡愋捅皇褂枚袛唷?/p>
注意,兩個(gè)本質(zhì)上是相同的。代碼可能有一天被中斷。要么因?yàn)榻故褂眉词顾锹斆?,要么因?yàn)椴宦斆鞯氖褂谩?/p>
讓它
一般來說,更好的Python風(fēng)格就是簡(jiǎn)單地允許使用任何類型的數(shù)據(jù)。
我們將在第4章《一致設(shè)計(jì)的基本知識(shí)》回到這個(gè)問題。
這個(gè)問題是:為什么限制未來潛在的用例?
通?;卮鹗?,沒有理由限制未來潛在的用例。
比起阻止一個(gè)聰明的,但可能是意料之外的用例,我們可以提供文檔、測(cè)試和調(diào)試日志幫助其他程序員理解任何可以處理的限制類型。我們必須提供文檔、日志和測(cè)試用例,這樣額外的工作開銷最小。
下面是一個(gè)示例文檔字符串,它提供了對(duì)類的預(yù)期:
class Player: def __init__(self, table, bet_strategy, game_strategy): """Creates a new player associated with a table, and configured with proper betting and play strategies :param table: an instance of :class:`Table` :param bet_strategy: an instance of :class:`BettingStrategy` :param game_strategy: an instance of :class:`GameStrategy` """ self.bet_strategy = bet_strategy self.game_strategy = game_strategy self.table = table
程序員使用這個(gè)類已經(jīng)被警告了限制類型是什么。其他類型的使用是被允許的。如果類型不符合預(yù)期,執(zhí)行會(huì)中斷。理想情況下,我們將使用unittest和doctest來發(fā)現(xiàn)bug。
2. 初始化、封裝和私有一般Python關(guān)于私有的政策可以總結(jié)如下:我們都是成年人了。
面向?qū)ο蟮脑O(shè)計(jì)有顯式接口和實(shí)現(xiàn)之間的區(qū)別。這是封裝的結(jié)果。類封裝了數(shù)據(jù)結(jié)構(gòu)、算法、一個(gè)外部接口或者一些有意義的事情。這個(gè)想法是從實(shí)現(xiàn)細(xì)節(jié)封裝分離基于類的接口。
但是,沒有編程語言反映了每一個(gè)設(shè)計(jì)細(xì)節(jié)。Python中,通常情況下,并沒有考慮都用顯式代碼實(shí)現(xiàn)所有設(shè)計(jì)。
類的設(shè)計(jì),一方面是沒有完全在代碼中有私有(實(shí)現(xiàn))和公有(接口)方法或?qū)傩詫?duì)象的區(qū)別。私有的概念主要來自(c++或Java)語言,這已經(jīng)很復(fù)雜了。這些語言設(shè)置包括如私有、保護(hù)、和公有以及“未指定”,這是一種半專用的。私有關(guān)鍵字的使用不當(dāng),通常使得子類定義產(chǎn)生不必要的困難。
Python私有的概念很簡(jiǎn)單,如下
本質(zhì)上都是公有的。源代碼是可用的。我們都是成年人。沒有什么可以真正隱藏的。
一般來說,我們會(huì)把一些名字的方式公開。他們普遍實(shí)現(xiàn)細(xì)節(jié),如有變更,恕不另行通知,但是沒有正式的私有的概念。
在部分Python中,命名以_開頭的一般是非公有的。help()函數(shù)通常忽略了這些方法。Sphinx等工具可以從文檔隱藏這些名字。
Python的內(nèi)部命名是以__開始(結(jié)束)的。這就是Python保持內(nèi)部不與應(yīng)用程序的命名起沖突。這些內(nèi)部的集合名稱完全是由語言內(nèi)部參考定義的。此外,在我們的代碼中嘗試使用__試圖創(chuàng)建“超級(jí)私人”屬性或方法是沒有任何好處的。一旦Python的發(fā)行版本開始使用我們選擇內(nèi)部使用的命名,會(huì)造成潛在的問題。同樣,我們使用這些命名很可能與內(nèi)部命名發(fā)生沖突。
Python的命名規(guī)則如下:
大多數(shù)命名是公有的。
以_開頭的都是非公有的。使用它們來實(shí)現(xiàn)細(xì)節(jié)是真正可能發(fā)生變化的。
以__開頭或結(jié)尾的命名是Python內(nèi)部的。我們不能這樣命名;我們使用語言參考定義的名稱。
一般情況下,Python方法使用文檔和好的命名來表達(dá)一個(gè)方法(或?qū)傩?的意圖。通常,接口方法會(huì)有復(fù)雜的文檔,可能包括doctest的示例,而實(shí)現(xiàn)方法將有更多的簡(jiǎn)寫文檔,很可能沒有doctest示例。
新手Python程序員,有時(shí)奇怪私有沒有得到更廣泛的使用。而經(jīng)驗(yàn)豐富的Python程序員,卻驚訝于為了整理并不實(shí)用的私有和公有聲明去消耗大腦的卡路里,因?yàn)閺姆椒ǖ拿臀臋n中就能知道變量名的意圖。
總結(jié)在本章中,我們回顧了__init__()方法的各種設(shè)計(jì)方案。在下一章,我們將看一看特別的以及一些高級(jí)的方法。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/45375.html
摘要:簡(jiǎn)單復(fù)合對(duì)象復(fù)合對(duì)象也可被稱為容器。它難以序列化對(duì)象并像這樣初始化來重建。接口仍然會(huì)導(dǎo)致多種方法計(jì)算。還要注意一些不完全遵循點(diǎn)規(guī)則的方法功能。逐步增加項(xiàng)目的方法和一步加載所有項(xiàng)目的方法是一樣的。另一個(gè)方法就是之前那樣的類定義。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python 在各個(gè)子類中實(shí)現(xiàn)__init_...
摘要:工廠類的函數(shù)就是包裝一些目標(biāo)類層次結(jié)構(gòu)和復(fù)雜對(duì)象的構(gòu)造。連貫的工廠類接口在某些情況下,我們?cè)O(shè)計(jì)的類在方法使用上定義好了順序,按順序求方法的值很像函數(shù)。這個(gè)工廠類可以像下面這樣使用首先,我們創(chuàng)建一個(gè)工廠實(shí)例,然后我們使用那個(gè)實(shí)例創(chuàng)建實(shí)例。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python 通過工廠函數(shù)對(duì) __init_...
摘要:第一是在對(duì)象生命周期中初始化是最重要的一步每個(gè)對(duì)象必須正確初始化后才能正常工作。第二是參數(shù)值可以有多種形式?;悓?duì)象的方法對(duì)象生命周期的基礎(chǔ)是它的創(chuàng)建初始化和銷毀。在某些情況下,這種默認(rèn)行為是可以接受的。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python __init__()方法意義重大的原因有兩個(gè)。第一是在對(duì)象生命...
摘要:提議以下的新的生成器語法將被允許在生成器的內(nèi)部使用其中表達(dá)式作用于可迭代對(duì)象,從迭代器中提取元素。子迭代器而非生成器的語義被選擇成為生成器案例的合理泛化。建議如果關(guān)閉一個(gè)子迭代器時(shí),引發(fā)了帶返回值的異常,則將該值從調(diào)用中返回給委托生成器。 導(dǎo)語: PEP(Python增強(qiáng)提案)幾乎是 Python 社區(qū)中最重要的文檔,它們提供了公告信息、指導(dǎo)流程、新功能的設(shè)計(jì)及使用說明等內(nèi)容。對(duì)于學(xué)習(xí)...
閱讀 3134·2021-09-28 09:42
閱讀 3464·2021-09-22 15:21
閱讀 1136·2021-07-29 13:50
閱讀 3589·2019-08-30 15:56
閱讀 3378·2019-08-30 15:54
閱讀 1205·2019-08-30 13:12
閱讀 1188·2019-08-29 17:03
閱讀 1211·2019-08-29 10:59