Mastering Object——oriented Python(python 面向?qū)ο缶幊讨改希?筆記

第一部分 用特殊方法實現(xiàn)Python風(fēng)格的類

為了實現(xiàn)更好的可擴展性,Python語言提供了大量的特殊方法,它們大致分為以下幾類。

  • 特性訪問
  • 可調(diào)用對象
  • 集合
  • 數(shù)字
  • 上下文
  • 迭代器

第一章 使用__init()__方法

Python中一切事物皆對象!!?。。?!
__init__()方法記住兩點:

  • __init()__(初始化)是對象生命周期的開始,每個對象必須正確初始化才能夠正常的工作。
  • __init__()可以賦值
    對象的生命周期主要是有創(chuàng)建、初始化、銷毀。
    ‘顯示而非隱式’:對于每個__init__()方法,都應(yīng)當(dāng)顯示的制定要初始化的變量。
    每當(dāng)創(chuàng)建一個對象,python會縣床架一個空對象,然后調(diào)用該對象的init()函數(shù),提供了初始化的操作。
# 以21點為例作為說明。
class Card(object):
    def __init__(self, suit, rank):
        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
   
class Suit(object):
    def __init__(self,name,symbol):
        self.name = name
        self.symbol = symbol

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

通過工廠函數(shù)來調(diào)用__init__():


def card(rank, suit):
    if rank == 1:
        return AceCard('A', suit)
    elif 2 <= rank < 11:
        return NumberCard(str(rank), suit)
    elif 11 <= rank < 14:
        name = {11: 'J', 12: 'Q', 13: 'K'}[rank]
        return FaceCard(name, suit)
    else:
        raise Exception("rank out of range")
 這個函數(shù)通過傳入牌面值rank 和花色值suit來創(chuàng)建card對象.
deck = [card(rank, suit) for rank in range(1, 14) for suit in (Club, Diamond, Heart, Spade)]
print(deck[0].rank,deck[0].suit.symbol)

這段代碼完成了52張牌對象的創(chuàng)建.

使用映射和類來簡化設(shè)計.

由于類是第一級別的對象,從rank參數(shù)射到對象是很容易的事情.
下面的Card類工廠就是使用映射實現(xiàn)的版本.

def card4(rank,suit):
    class_ = {1:AceCard,11:FaceCard,12:FaceCard,13:FaceCard}.get(rank,NumberCard)
    return class_(rank,suit)

需要修改映射邏輯,除了提供Card子類,還需要提供rank對象的字符串結(jié)果.如何實現(xiàn)這兩部分映射,有四種常見方案.

  • 可以建立兩個并行映射

  • 可以映射為一個二元組.

  • 可以映射為partial()函數(shù).

  • 可以考慮修改類定義的完成映射邏輯.

    1.并行映射

def card5(rank,suit):
    class_ = {1:AceCard,11:FaceCard,12:FaceCard,13:FaceCard}.get(rank,NumberCard)
    rank_str = {1:'A',11: 'J', 12: 'Q', 13: 'K'}.get(rank,str(rank))
    return class_(rank_str,suit)

這樣是不值得做的,帶來映射鍵1,11,12,13的邏輯重復(fù).

不要使用并行結(jié)構(gòu),并行結(jié)構(gòu)應(yīng)該被元祖或者一些更好的組合所代替

  1. 映射到一個牌面值的元組
def card6(rank,suit):
   class_,rank_str= {
       1:(AceCard,'A'),
       11:(FaceCard,'J'),
       12:(FaceCard,'Q'),
       13:(FaceCard,'K')
   }.get(rank,(NumberCard,str(rank)))
   
   return class_(rank_str,suit)

從rank值映射到類對象時很少見的,而且兩個參數(shù)只有一個用于對象的初始化.從rank映射到一個相對簡單的類或者是函數(shù)對象,而不必提供目的不明確的參數(shù),這才是明智的選擇.

3.partial 函數(shù)設(shè)計

def card7(rank,suit):
  from  functools import partial
  part_class = {
      1:partial(AceCard,'A'),
      11:partial(FaceCard,'J'),
      12:partial(FaceCard,'Q'),
      13:partial(FaceCard,'K')
  }.get(rank,partial(NumberCard,str(rank)))

  return part_class(suit)

通過調(diào)用partial()函數(shù)然后復(fù)制給part_class,完成于rank對象的管的關(guān)聯(lián),可以使用同樣的方式來創(chuàng)建suit對象,并且完成最終的Card對象的創(chuàng)建.partial()函數(shù)的使用在函數(shù)時編程中是很常見的.當(dāng)時用的是函數(shù)而非對象方法的時候就可以考慮使用.

大致上,partial()函數(shù)在面向?qū)ο缶幊讨胁皇呛艹S?我們可以簡單的的提供構(gòu)造函數(shù)不同版本來做相同的事情.partial()函數(shù)和構(gòu)造對象時的流暢接口很類似.

  1. 工廠模式的流暢的API設(shè)計

    有時候我們定義類中的方法必須按照特定的順序來調(diào)用.這種順序調(diào)用的方法和創(chuàng)建 partial() 函數(shù)的方式非常類似.

我們可以在流暢接口函數(shù)中設(shè)置可以返回self值的rank對象,然后傳入花色類從而創(chuàng)建Card實例/

以下是Card工廠流暢接口的定義,包含兩個函數(shù),他們必須按照順序調(diào)用.

class CardFactory(object):
    def rank(self,rank):
        self.class_,self.rank_str = {
            1:(AceCard,'A'),
            11:(FaceCard,'J'),
            12:(FaceCard,'Q'),
            13:(FaceCard,'K')
        }.get(rank,(NumberCard,str(rank)))
    
    def suit(self,suit):
        return self.class_(self.rank_str,suit)

先使用rank()函數(shù)更新了構(gòu)造函數(shù)的狀態(tài),然后通過suit()函數(shù)創(chuàng)造了 最終的Card對象.

def A (rank):

    a,b ={                      # 本身為一個字典的傳遞值.返回對應(yīng)的值.是dict的get方法
        1: (AceCard, 'A'),
        11: (FaceCard, 'J'),
        12: (FaceCard, 'Q'),
        13: (FaceCard, 'K')
    }.get(rank, (NumberCard, str(rank)))
    return a,b # 返回的是一個tuple(),a 為 <class '__main__.NumberCard'> , b 為'3'

a = A(3)
print(a)

我們先實例化一個工廠對象,然然后在創(chuàng)建Card實例,這用方式?jīng)]有利用__init__() 在Card類層級結(jié)構(gòu)的作用,改變的是調(diào)用者創(chuàng)建創(chuàng)建對象的方式.

在每個子類中實現(xiàn)__init__()方法

以下代碼演示了如何把__init__()方法提到基類Card中實現(xiàn)的過程.然后在子類中可以重用基類的實現(xiàn).


class Card(object):
    def __init__(self, rank, suit, hard, soft):
        self.rank = rank
        self.suit = suit
        self.hard = hard
        self.soft = soft


class NumberCard(Card):
    def __init__(self, rank, suit):
        super().__init__(str(rank), suit, rank, rank)


class AceCard(Card):
    def __init__(self, rank, suit):
        super(AceCard, self).__init__("A", suit, 1, 11)


class FaceCard(Card):
    def __init__(self, rank, suit):
        super(FaceCard, self).__init__({11: 'J',
                                        12: 'Q',
                                        13: 'K'}[rank], suit, 10, 10)
        

def card10(rank,suit):
    if rank == 1:
        return AceCard(rank,suit)
    elif 2<= rank < 11:
        return NumberCard(rank,suit)
    elif 11<= rank <14:
        return FaceCard(rank,suit)
    
    else:
        raise  Exception('Rank out of range')

在這里重構(gòu)了基類中的__init__,雖然將它復(fù)雜化,但是這樣的權(quán)衡是正常的.

使用工廠函數(shù)封裝的復(fù)雜性
__init__()方法和工廠函數(shù)之間存在一些權(quán)衡,通常直接調(diào)動比'程序員友好'的__init__()函數(shù)并把復(fù)雜性分發(fā)給工廠函數(shù)更好.當(dāng)需要封裝復(fù)雜的構(gòu)造函數(shù)邏輯時,考慮使用工廠函數(shù)則更好.

簡單的組合對象

一個組合對象也可以稱作容器.

如果業(yè)務(wù)邏輯相對簡單,為什么定義新類?

類的定義的一個優(yōu)勢是:

  • 類給對象提供了簡單的,不需要實現(xiàn)的接口.

設(shè)計集合類,通常是下面三種策略:

  • 封裝:這個實際是基于現(xiàn)有集合類來定義一個新類,屬于外觀模式的一個使用場景.
  • 擴展:這個設(shè)計是對現(xiàn)有集合類進(jìn)行擴展,通常使用定義子類的方式來實現(xiàn).
  • 創(chuàng)建:即重新設(shè)計.
    以上是面向?qū)ο笤O(shè)計的核心.
封裝集合類

以下是對內(nèi)部集合進(jìn)行封裝設(shè)計.

import random
class Deck(object):
   def __init__(self):
       self._cards = [card6(r+1,s) for r in range(13) for s in (Club,Diamond,Heart,Spade)]
       random.shuffle(self._cards)
   def pop(self):
       return self._cards.pop()

d = Deck()

hand = [d.pop(),d.pop()]

一般來說買外觀模式或者封裝類中的方法實現(xiàn)只是對底層對象相應(yīng)函數(shù)的代理調(diào)用.

class Desk3(list):
    def __init__(self, decks=1):
       super(Desk3, self).__init__()
       for i in range(decks):
           self.extend(card6(r + 1, s) for r in range(13) for s in (Club, Diamond, Heart, Spade))
           random.shuffle(self)
           burn = random.random(1,52)
           for i in range(burn):
               self.pop()

這里我們使用了基類的 __init__()函數(shù)來創(chuàng)建了一個空集合,然后調(diào)用了 self.extrend()來吧多副牌加載到發(fā)牌機中.

復(fù)雜的組合對象

模擬打牌策略

class Hand:
    def __init__(self,dealer_card):
        self.dealer_card = dealer_card
        self.cards = []
        
    def hard_total(self):
        return sum(c.hard for c in self.cards)
    
    def soft_total(self):
        return sum(c.soft for c in self.cards)
d = Deck()
h = Hand(d.pop())
h.cards.append(d.pop())
h.cards.append(d.pop())

需要一個一個的添加非常不方便

完成組合對象的初始化

__init__()初始化方法應(yīng)當(dāng)返回一個完成的對象,當(dāng)然這個是理想的情況.而這樣也帶來復(fù)雜性,因為要創(chuàng)建的對象內(nèi)部可能包含了集合,集合里面又包含了其他對象.

通??紤]使用一個流暢的接口來完成逐個講對象添加到集合的操作,同時將集合對象作為構(gòu)造函數(shù)來完成初始化.例如:

class Hand2:
    def __init__(self, dealer_card, *cards):
        self.dealer_card = dealer_card
        self.cards = list(cards)

    def hard_total(self):
        return sum(c.hard for c in self.cards)

    def soft_total(self):
        return sum(c.soft for c in self.cards)

d = Deck()
h = Hand2(d.pop(),d.pop(),d.pop(),d.pop())
print(h.cards)

不帶__init__方法的無狀態(tài)對象

一個策略對象以插件的形式復(fù)合在主對象上來完成一種算法或邏輯.它或許以來主對象中的數(shù)據(jù),策略對象自身并不攜帶任何數(shù)據(jù).通常策略類會和亨元設(shè)計模式一起使用:在策略對象中避免內(nèi)部存儲.所需要的值都從策略對象方法參數(shù)傳入.策略對象自身是無狀態(tài)的.可以把它看做是一系列函數(shù)的集合.

這里定義了一個類給Player實例提供了游戲的選擇模式,以下這個策略包括拿牌和下注.

class GameStrategy:
    def insurnace(self, hand):
        return False

    def split(self, hand):
        return False

    def double(self, hand):
        return False

    def hit(self, hand):
        return False

每個函數(shù)需要傳入已有的Hand對象,函數(shù)邏輯所需要的數(shù)據(jù)基于現(xiàn)有的可用信息.意味著數(shù)據(jù)來自于莊家跟玩家的手牌.

一起其他的類定義

玩家有兩張策略:打牌和下注.每個Player實例回合模擬器進(jìn)行很多次交互.我們這里把這個模擬器命名為Table

Table類的職責(zé)需要配合Player實例完成以下事件:

  • 玩家必須要基于玩牌策略初始化一個牌局.
  • 隨后玩家會得到一手牌
  • 如果

以下是Table類中投注和牌的邏輯處理相關(guān)的代碼


class Table:
    def __init__(self):
        # 生成52張牌
        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() 書上是這么寫的我認(rèn)為不對,改為下面寫法
            self.hand = Hand2(self.deck.pop(), self.deck.pop(), self.deck.pop())
            self.hole_card = self.deck.pop()
        except IndexError:
            # Out of cards: need to shuffle
            self.deck = Deck()
            return self.get_hand()
        print('Deal', self.hand)
        return self.hand

    # 沒有看明白hand從何而來,所以也未找到insure的方法。估計是寫錯了。
    def can_insure(self, hand):
        return hand.dealer_card.insure

class BettingStrategy:
    def bet(self):
        raise NotImplementedError('No bet method')
    
    def record_win(self):
        pass
    
    def record_lose(self):
        pass
    
class Flat(BettingStrategy):
    def bet(self):
        return 1

上面的那一段代碼還未看懂需要以后再來看一遍.

多策略的__init__()方法

class Hand4:
    def __init__(self, *args, **kwargs):
        print(len(args),args,kwargs)
        if len(args) == 1 and isinstance(args[0], Hand4):
            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 kwargs:
            # Split an existing hand
            other, card = args
            self.dealer_card = other.dealer_card
            self.cards = [other.cards[kwargs['split']], card]
        elif len(args) == 3:
            # Bulid a fresh ,new hand
            dealer_card,*cards = args
            self.dealer_card = dealer_card
            self.cards = list(cards)

        else:
            raise TypeError('Invaild constructor args= {0!r} kw={1!r}'.format(args,kwargs))

    def __str__(self):
        return ','.join(map(str,self.cards))


d = Deck()

h = Hand4(d.pop(),d.pop(),d.pop())
print(h)

# s1 = Hand4(h,d.pop(),split = 0)
# s2 = Hand4(h,d.pop(),split = 1)
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))

d = Deck()

h = Hand5(d.pop(),d.pop(),d.pop())
s1,s2 = Hand5.split(h,d.pop(),d.pop())

上面這段代碼實現(xiàn)了:當(dāng)?shù)谝惠啺l(fā)完牌是,dealer手牌有一張,Player手牌有兩張,當(dāng)手牌的兩張牌相同的時候玩家可以選擇分牌,將手中的的兩張牌分為兩組牌,繼續(xù)進(jìn)行游戲.然后發(fā)牌器會給Palyer每組牌中個發(fā)一張牌

更多的__init__()技術(shù)

一下是Player類的定義,初始化使用兩個策略對象和一個table對象

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內(nèi)容