[CS61A-Fall-2020]學習記錄五 Project1 The Game of Hog 中有意思的點

陆爻齐發表於2024-07-18

首先,本文不是總結歸納,只是記錄一些有趣的知識點罷了

此外,觀前提示,請最好在嘗試獨立完成該任務後再看本文,否則就很可能失去了體驗本專案精華的機會

函式引數前的*

在專案原檔案中的dice.py 裡的 make_test_dice函式採用這樣的方式傳參

def make_test_dice(*outcomes):
  # 省略內容
  pass

我記得python中沒像c那樣的指標,於是查詢資料,小結如下
函式引數前一顆*會識別為元組,兩顆則識別為字典

比如我要是傳 make_test_dice(1, 2, 4, 3),outcomes就是元組(1, 2, 4, 3)

舉一個稍微複合一點點的例子

def foo(a, b=10, *args, **kwargs):
    print (a)
    print (b)
    print (args)
    print (kwargs)

foo(1, 2, 3, 4, e=5, f=6, g=7)

上述例子來自菜鳥教程,執行結果為

1
2
(3, 4)
{'e': 5, 'f': 6, 'g':7}

可發現除了定好的a、b,c和d都納入元組中,而efg有=賦值,作為字典看待

上面的例子都發生在定義函式引數列表,如果在呼叫時有*說明什麼呢?

再看一個例子

def func(num1, num2):
  print(num1, num2)

num = [1, 2]
func(*num)

執行結果為

1 2

一個引數變兩,*在這裡起到了解壓引數列表的作用
但筆者尚未想到該功能應用的方便之處

Higher Order Function 高階函式

這是一個讓陸爻齊覺得十分精妙的功能,以函式為引數並返回函式,從 C 初學者的角度來說,太抽象了

就是在 Problem 7 的答案程式碼


def announce_highest(who, last_score=0, running_high=0):
    """Return a commentary function that announces when WHO's score
    increases by more than ever before in the game.

    NOTE: the following game is not possible under the rules, it's just
    an example for the sake of the doctest

    >>> f0 = announce_highest(1) # Only announce Player 1 score gains
    >>> f1 = f0(12, 0)
    >>> f2 = f1(12, 9)
    9 point(s)! The most yet for Player 1
    >>> f3 = f2(20, 9)
    >>> f4 = f3(20, 30)
    21 point(s)! The most yet for Player 1
    >>> f5 = f4(20, 47) # Player 1 gets 17 points; not enough for a new high
    >>> f6 = f5(21, 47)
    >>> f7 = f6(21, 77)
    30 point(s)! The most yet for Player 1
    """
    assert who == 0 or who == 1, 'The who argument should indicate a player.'
    # BEGIN PROBLEM 7
    "*** YOUR CODE HERE ***"
    def say(new_score_0, new_score_1, last_running_high = running_high):
        if who == 0:
            new_score = new_score_0
            new_running_high = new_score_0 - last_score
        else:
            new_score = new_score_1
            new_running_high = new_score_1 - last_score

        if new_running_high > last_running_high:
            last_running_high = new_running_high
            print(str(new_running_high),"point(s)! The most yet for Player " + str(who))
        
        new_run_high = last_running_high
        
        return announce_highest(who, new_score, new_run_high)
        
    return say
    # END PROBLEM 7

這段程式碼就是記錄玩家 1 或 2 分數變化的幅度,並在最大幅度記錄更新時 print 的函式。

傳統函式(指 C/C++ 這種)要實現這種記錄更新,必須在外部儲存,用類或者其它外部變數什麼的,因為函式一旦執行完畢,內部空間將全部釋放。

下面對該程式碼做點拆解,忽略 say 的內容、斷言、測試和註釋,可看成


def announce_highest(who, last_score=0, running_high=0):
    # BEGIN PROBLEM 7
    "*** YOUR CODE HERE ***"
    def say(new_score_0, new_score_1, last_running_high = running_high):
        pass
        
    return say
    # END PROBLEM 7

可見,呼叫 announce_highest 的本質是獲取到一個 say 函式,每次呼叫 say 函式,我們都期待它能檢測是否要更新記錄,而不斷地更新最大幅度,則要不斷地獲取並呼叫 say 函式,那讓 say 返回獲取 say 函式的函式就解決了這個問題,即下面程式碼


def announce_highest(who, last_score=0, running_high=0):
    # BEGIN PROBLEM 7
    "*** YOUR CODE HERE ***"
    def say(new_score_0, new_score_1, last_running_high = running_high):
 
        pass

        return announce_highest(who, new_score, new_run_high)
        
    return say
    # END PROBLEM 7

再看測試程式碼就清晰多了

>>> f0 = announce_highest(1) # Only announce Player 1 score gains 這裡的 f0 實際上是 who 為 1 時的 say 函式

>>> f1 = f0(12, 0) f1 的本質是代入 new_score_0 = 12 和 new_score_1 = 0 的 say 函式執行後,返回的新的 say 函式。你問 announce_highest 去哪了?announce_highest 也返回的 say 函式,只是沒有引數代入的 say 函式而已

其它測試語句以上述類推

後面在 geek for geek 進行了更深入的學習,發現此前在 LALC 用的迭代器就是高階函式的運用