昨天上傳回 Github 後,如果接下來要繼續在本地端開發,會建議先 checkout
回 main
,然後再從 origin
把昨天更新的從 Github 拉回本地端,所以指令就會從 git pull
變成 git pull origin main
,這樣就能把本地端的程式碼更新成最新版的 main
。
今天繼續更新我們的 functions,在 Day 18 - 用 requests 取得 Raw Data 後使用 pandas 轉成 DataFrame 的最後,我有提到在處理 params
的時候目前還太粗糙,要有更精確的值如果寫在同一個 function 又會讓該 function 太過龐大,所以這時候就會另外開 utils
資料夾存放這些處理 params
的 functions。這有另外一個好處就是在我們開發 Minor Statcast Search,也就是小聯盟的 statcast 數據搜尋的時候也可以使用到這些 utils。
season
首先處理得多選是 season
,關於這些參數的敘述可以參考 Day 15 - Statcast Search Filters Part 1 跟 Day 16 - Statcast Search Filters Part 2。
因為是多選的關係,所以會需要的型態是 str
跟 list
,我們可以先做型態的檢查,如果不是這兩個的話就可以用 raise 來回傳錯誤給使用者。寫法會像是:
def get_season_param_str(season: str | list[str]) -> str:
if (type(season) != str and type(season) != list):
raise ValueError(f"Invalid type for season: {season}")
接下來處理如果是傳入 list
的情況,因為可以使用的球季只有 2008 - 現在,我們可以寫個常數紀錄總共有哪些年份,再來會檢查我們傳入的 list
裡的輸入年份是否存在在規定的範圍內。會用 any 這個函數來判斷,any
的用法就是判斷一個 list
裡面,除非是全部 True
,不然都會回傳 False
。所以我們可以用 List Comprehensions 跟 in
來判斷我們傳入的 list
裡的 element
是否在範圍內,得到各個 element
的 True/False
之後用 any
判斷這個 list
是否合法,如果是合法的就用 join
拼好 params
,否的話就回傳錯誤
CURRENT_SEASON = 2024
START_SEASON = 2008
ALL_SEASONS = [str(year) for year in range(START_SEASON, CURRENT_SEASON)]
def get_season_param_str(season: str | list[str]) -> str:
if (type(season) != str and type(season) != list):
raise ValueError(f"Invalid type for season: {season}")
if (type(season) == list):
if any(season not in ALL_SEASONS for season in season):
raise ValueError(f"Invalid seasons: {season}")
return '|'.join(season)
list
處理完,再來處理 str
型態的值,在前面文章有提到,我們可以用 all
跟 statcast
來一次多選,因此也許要用 if
來處理這兩個情況。statcast
是從 2015 年開始的,會另外處理,最後整個 util function 會寫成:
def get_season_param_str(season: str | list[str]) -> str:
if (type(season) != str and type(season) != list):
raise ValueError(f"Invalid type for season: {season}")
if (type(season) == list):
if any(season not in ALL_SEASONS for season in season):
raise ValueError(f"Invalid seasons: {" | ".join(season)}")
return '|'.join(season)
if (season == "all"):
return '|'.join(ALL_SEASONS)
if (season == "statcast"):
return '|'.join(STATCAST_SEASONS)
if (season not in ALL_SEASONS):
raise ValueError(f"Invalid season: {season}")
return season
game_type
再來是 game_type
這個參數,這個在 list
處理前面都一樣,唯一要注意的是,他需要最後面再加一個 |
才會搜尋的到結果,所以就會用 f-string 在最後面接,會寫成 f"{'|'.join(game_type)}|"
,剩下就變得差不多,一樣用常數儲存所有的可能值:
GAME_TYPES = ["R", "PO", "F", "D", "L", "W", "S", "A"]
def get_game_type_param_str(game_type: str | list[str]) -> str:
if (type(game_type) != str and type(game_type) != list):
raise ValueError(f"Invalid type for game_type: {game_type}")
if (type(game_type) == list):
if any(game_type not in GAME_TYPES for game_type in game_type):
raise ValueError(f"Invalid game types: {" | ".join(game_type)}")
return f"{'|'.join(game_type)}|"
if (game_type == "all"):
return f"{'|'.join(GAME_TYPES)}|"
if (game_type not in GAME_TYPES):
raise ValueError(f"Invalid game type: {game_type}")
return f"{game_type}|"
剩下的參數的處理方式都差不多,不過可以發現,有些數值的代號可能會沒那麼清楚,可能之後會需要用到 Enum 的方式來呈現,使用者在使用的時候也可以用 Enum 來選擇自己要使用的選項。這個就留到後面的天數介紹了。
最後一樣感謝耐心地看完今天的文章,有任何問題或是有更好的寫法歡迎留言給我(我自己覺得可能用太多 if
),明天見,掰掰。