實戰(zhàn):用pygame鞏固編程基礎,打造第一款小游戲)
用Python寫一個貪吃蛇小游戲最大的價值不是把游戲做完而是把Python的基礎語法串起來跑一遍。輸入處理、列表操作、條件判斷、循環(huán)控制、函數(shù)封裝這些平時單獨寫很容易懂但一旦放進一個不斷變化的游戲循環(huán)里很多隱藏問題就會冒出來。這個練手項目尤其適合剛學完Python基礎、想找一個能完整運行的小項目的人。我最近又按這個思路用pygame把貪吃蛇重新寫了一遍結論是只要環(huán)境裝好按“窗口創(chuàng)建 - 蛇移動 - 食物生成 - 碰撞判斷”的順序拆解新手也能在幾個小時內(nèi)跑出第一版。下面直接進入正題。我會先說明這個項目到底在練什么再給環(huán)境準備和完整源碼最后把常見的報錯、邊界和擴展思路一起整理出來。如果你只是想復制代碼可以直接跳到第四節(jié)如果你想理解每一步為什么這么寫建議從頭順序看。1. 先搞懂這個練手項目到底在練什么1.1 它適合哪類Python學習者這個項目適合的并不是完全沒寫過代碼的人而是已經(jīng)認識if、for、while、列表和函數(shù)的讀者。如果這些概念還沒接觸過我建議先花半天把基礎語法過一遍否則看代碼時會卡在“為什么這里用列表存蛇身”這類問題上。貪吃蛇的代碼量不大一套完整可運行版本通常也就一兩百行。但它的邏輯鏈路比普通練習復雜很多窗口要持續(xù)刷新鍵盤要實時響應蛇的位置每幀都要更新食物消失后要重新生成。這些東西組合在一起正好能把零散的語法點變成一套完整程序。更重要的是它能讓初學者感受到“寫小游戲”和“寫腳本”的區(qū)別。腳本往往是順序執(zhí)行一遍就結束游戲需要在一個while循環(huán)里反復處理輸入、更新狀態(tài)、繪制畫面。這種循環(huán)思想是很多圖形程序和游戲程序的基礎。1.2 一次游戲要用到的核心語法點先列一下這個項目里會反復用到的內(nèi)容列表蛇身用列表存儲蛇頭是第一個元素蛇尾是最后一個元素。元組坐標用(x, y)表示字典用方向鍵映射方向向量。while循環(huán)游戲主循環(huán)每一幀執(zhí)行一次更新。for循環(huán)遍歷蛇身繪制矩形或者遍歷事件列表。if / elif / else判斷鍵盤事件、判斷是否撞墻、是否吃到食物。函數(shù)把初始化、生成食物、繪制、檢查碰撞分別封裝成函數(shù)。模塊導入import pygame、import random。常量定義窗口大小、格子大小、顏色、游戲速度用大寫常量統(tǒng)一管理。這些點覆蓋了Python基礎中最常用的一部分。把它們放進一個項目的上下文里理解比單獨背語法記得更牢。1.3 為什么游戲比打印九九乘法表更能鞏固基礎打印九九乘法表確實能練習循環(huán)但它的缺點是無論循環(huán)多少次外部狀態(tài)幾乎不變。貪吃蛇不一樣蛇身列表會越來越長食物的位置會不斷變化游戲速度會隨著得分提高。每一步都要關心“當前狀態(tài)是什么”和“狀態(tài)如何遷移”這其實是編程里非常重要的思維訓練。另外游戲項目天然有可視化反饋。代碼出錯時你能直觀看到畫面是否正常、蛇是否亂跑、碰撞是否誤判。這種即時反饋能大大提高調(diào)試效率也能讓人更有動力繼續(xù)改下去。對新手來說能看到自己寫的程序變成一個能動的小游戲比起單純在終端里打印結果要有成就感得多。2. 搭建運行環(huán)境Python和pygame缺一不可2.1 檢查Python版本和pip命令我默認你已經(jīng)裝好了Python。判斷方式很簡單打開終端或命令行輸入python --version如果提示不是內(nèi)部或外部命令再試python3 --versionWindows下一般用pythonmacOS和Linux下可能要用python3。安裝Python時建議勾選“Add Python to PATH”否則后面在命令行里找不到python命令會很麻煩。接著檢查pip是否可用pip --version如果pip也沒找到可以試python -m pip --version。這一步的目的不是做表面檢查而是避免后面安裝pygame時出現(xiàn)“找不到pip”或“把包裝到了另一個Python環(huán)境”這種問題。2.2 安裝pygame并確認導入成功貪吃蛇最常用的圖形庫是pygame。它專門用來做2D小游戲已經(jīng)封裝了窗口、事件、圖片和聲音等常用功能。安裝命令很簡單pip install pygame或者使用Python模塊方式python -m pip install pygame安裝完成后在Python交互環(huán)境或腳本里檢查import pygame print(pygame.ver)如果打印出版本號說明安裝成功。如果報ModuleNotFoundError先確認你執(zhí)行python和pip的環(huán)境是不是同一個。很多時候用戶裝了多個Python版本pip裝到了3.8但命令運行的是3.11兩邊自然對不上。2.3 如果沒有pygame還能用tkinter嗎可以。Python自帶tkinter不需要額外安裝圖形庫。用tkinter寫貪吃蛇也完全可以只是事件處理方式不同pygame用pygame.event.get()輪詢事件tkinter用bind綁定鍵盤事件。從學習目的看兩者都能鞏固基礎。不過我個人建議第一次做這個項目時直接使用pygame。原因是pygame的坐標系、幀率和矩形繪制更符合游戲開發(fā)直覺網(wǎng)上參考資料也多。如果你在安裝pygame時遇到網(wǎng)絡問題可以換國內(nèi)鏡像源安裝pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple安裝完成后再用import pygame驗證一次。環(huán)境問題解決了后面寫代碼才不會一頭霧水。3. 拆解貪吃蛇的四個核心模塊3.1 初始化窗口和游戲參數(shù)第一件事是定義游戲窗口的大小和格子大小。貪吃蛇的移動是離散的每次走一個格子所以需要把窗口分成很多小格子。我一般這樣設定CELL_SIZE 20 GRID_WIDTH 30 GRID_HEIGHT 20 WINDOW_WIDTH CELL_SIZE * GRID_WIDTH WINDOW_HEIGHT CELL_SIZE * GRID_HEIGHT這里的CELL_SIZE是每個格子的邊長單位是像素。GRID_WIDTH和GRID_HEIGHT分別是橫向和縱向的格子數(shù)量。窗口總寬等于格子大小乘數(shù)量這樣蛇頭坐標可以精確落在格子里不會出現(xiàn)畫出來對不齊的情況。為什么不讓窗口寬度直接寫600再讓CELL_SIZE隨便定因為后面判斷邊界和生成食物時都要依賴格子坐標。如果窗口尺寸不是格子大小的整數(shù)倍蛇走到邊緣時會出現(xiàn)半格偏移碰撞判斷就會變得很麻煩。先把窗口和格子解耦后續(xù)改起來也容易。游戲速度用一個FPS常量控制也就是每秒刷新的幀數(shù)。FPS越低蛇移動越慢FPS越高蛇移動越快。初版建議從FPS 10開始這個速度對新手比較友好。3.2 蛇的移動和數(shù)據(jù)存儲方式蛇身可以用一個列表存儲每個元素是一個坐標元組。比如snake [(15, 5), (14, 5), (13, 5)]這里(15, 5)是蛇頭后面兩個是身體。坐標含義是“第幾列、第幾行”不是像素位置。畫圖的時候再根據(jù)坐標乘以CELL_SIZE得到左上角的像素位置。蛇的移動分為四步根據(jù)當前方向dir計算新的蛇頭坐標。把新蛇頭插入列表最前面。如果沒有吃到食物就把列表最后一個元素彈出。如果吃到了食物就不彈出蛇身長度自然增加一格。這個做法的關鍵是“彈出尾巴”。如果不彈出蛇每幀都會變長最終填滿整個窗口。如果彈出的位置不對蛇看起來就像拖著一條斷尾在跑。代碼里要保證彈出的順序先插入新頭再刪除舊尾。順序反了可能吃到食物時丟掉尾巴或者沒吃到食物時蛇頭跑到錯誤位置。方向控制上需要做“禁止反向”的判斷。否則蛇正在向右走時按左鍵蛇頭會直接穿進身體。常見寫法是新方向與當前方向相加不等于零向量時才允許更新方向。例如當前方向是(1, 0)新方向(-1, 0)相加等于(0, 0)說明是反向此時忽略按鍵。3.3 食物生成邏輯食物可以用一個坐標元組表示例如food (5, 5)。每次生成食物時需要保證食物不在蛇身上。最簡單的做法是while True: food (random.randint(0, GRID_WIDTH - 1), random.randint(0, GRID_HEIGHT - 1)) if food not in snake: break這里random.randint(0, GRID_WIDTH - 1)生成的是0到29之間的整數(shù)對應橫向格子索引。為什么不從1開始因為蛇頭坐標也允許在0到29之間如果從1開始邊緣的一圈就永遠不會有食物看起來不夠自然。食物生成后要判斷是否被蛇吃到。判斷條件通常是蛇頭坐標等于食物坐標。如果相等就把一個“需要增長”的標記設為True。在下一次移動時不彈出蛇尾蛇就增長了一格。還有一個細節(jié)如果蛇很長幾乎占滿了整個窗口while循環(huán)可能會嘗試很多次才找到空位。對于貪吃蛇來說這種概率很低可以忽略。但如果要追求嚴謹可以把所有空格子收集到一個列表里再從中隨機選一個這樣每次生成效率都穩(wěn)定。初版可以不用糾結先跑通再說。3.4 碰撞檢測和死亡判定貪吃蛇的死亡條件有兩個撞墻和撞自己。撞墻的判斷很簡單if new_head[0] 0 or new_head[0] GRID_WIDTH or new_head[1] 0 or new_head[1] GRID_HEIGHT: running False注意使用而不是。因為合法下標是0到GRID_WIDTH-1一旦等于GRID_WIDTH就已經(jīng)超出邊界。撞自己的判斷稍微復雜一點。常見寫法是判斷新蛇頭是否在蛇身列表里。但這里有個坑如果新蛇頭是用切片或復制生成的要小心列表長度。更穩(wěn)妥的方式是if new_head in snake_body: running False這里的snake_body是去掉舊蛇尾后的身體。因為如果食物在尾巴的位置可能允許蛇頭移動到尾巴位置時尾巴剛好移開從而不算碰撞。但初版可以不考慮這個細節(jié)點直接用整個蛇身判斷也夠用。等你想做“完美”版本時再根據(jù)移動順序調(diào)整。死亡后常見處理是彈出一段文字“Game Over”然后pygame.quit()退出。也可以改成等待按鍵重新開始后面第五節(jié)會講。4. 完整源碼從啟動到游戲結束4.1 完整代碼下面是我寫過的一版完整代碼基于pygame實現(xiàn)去掉了復雜裝飾保留了核心邏輯。代碼可以直接保存為snake.py運行。import pygame import random # 初始化 pygame.init() # 基礎參數(shù) CELL_SIZE 20 GRID_WIDTH 30 GRID_HEIGHT 20 WINDOW_WIDTH CELL_SIZE * GRID_WIDTH WINDOW_HEIGHT CELL_SIZE * GRID_HEIGHT FPS 10 # 顏色 BLACK (0, 0, 0) WHITE (255, 255, 255) RED (200, 30, 30) GREEN (30, 180, 60) # 方向映射 DIRECTIONS { pygame.K_UP: (0, -1), pygame.K_DOWN: (0, 1), pygame.K_LEFT: (-1, 0), pygame.K_RIGHT: (1, 0), } # 創(chuàng)建窗口 screen pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption(貪吃蛇) clock pygame.time.Clock() # 初始狀態(tài) snake [(GRID_WIDTH // 2, GRID_HEIGHT // 2)] snake.append((snake[0][0] - 1, snake[0][1])) snake.append((snake[0][0] - 2, snake[0][1])) direction (1, 0) # 初始向右移動 score 0 font pygame.font.Font(None, 36) def random_food(): while True: pos (random.randint(0, GRID_WIDTH - 1), random.randint(0, GRID_HEIGHT - 1)) if pos not in snake: return pos food random_food() def draw(): screen.fill(BLACK) # 畫蛇 for i, (x, y) in enumerate(snake): rect pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE) # 蛇頭用亮色蛇身用深色 if i 0: pygame.draw.rect(screen, GREEN, rect) else: pygame.draw.rect(screen, (0, 120, 40), rect) # 畫食物 pygame.draw.rect( screen, RED, pygame.Rect(food[0] * CELL_SIZE, food[1] * CELL_SIZE, CELL_SIZE, CELL_SIZE) ) # 顯示得分 score_surface font.render(fScore: {score}, True, WHITE) screen.blit(score_surface, (10, 10)) # 游戲主循環(huán) running True game_over False while running: # 事件處理 for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.KEYDOWN: if event.key in DIRECTIONS: new_dir DIRECTIONS[event.key] # 禁止反向 if new_dir[0] direction[0] ! 0 or new_dir[1] direction[1] ! 0: direction new_dir elif event.key pygame.K_SPACE and game_over: # 重新開始 snake [(GRID_WIDTH // 2, GRID_HEIGHT // 2)] snake.append((snake[0][0] - 1, snake[0][1])) snake.append((snake[0][0] - 2, snake[0][1])) direction (1, 0) score 0 food random_food() game_over False if not game_over: # 計算新蛇頭 head_x, head_y snake[0] dx, dy direction new_head (head_x dx, head_y dy) # 撞墻判斷 if (new_head[0] 0 or new_head[0] GRID_WIDTH or new_head[1] 0 or new_head[1] GRID_HEIGHT): game_over True else: snake.insert(0, new_head) # 撞自己判斷 if new_head in snake[1:]: game_over True else: # 是否吃到食物 if new_head food: score 1 food random_food() else: snake.pop() # 繪制 draw() # 游戲結束提示 if game_over: over_surface font.render(Game Over! Press SPACE to restart, True, WHITE) screen.blit(over_surface, (WINDOW_WIDTH // 2 - 200, WINDOW_HEIGHT // 2)) pygame.display.flip() clock.tick(FPS) pygame.quit()這份代碼里按空格可以重新開始方便測試時不反復關閉窗口。如果你不需要這個功能可以把K_SPACE分支刪掉留下QUIT處理即可。4.2 代碼里幾個容易改錯的地方第一處是顏色元組的范圍。pygame里RGB每個分量通常取0到255如果寫成(0, 0, 256)有些環(huán)境會直接報溢出錯誤。建議保持在0到255之間。第二處是蛇的初始長度和方向。我定義了3個格子初始向右。如果你改成初始向左要注意后面的身體坐標也要保持一條直線否則蛇在啟動時會扭成奇怪形狀。第三處是撞自己的判斷位置。代碼里先snake.insert(0, new_head)再判斷new_head in snake[1:]。因為新蛇頭已經(jīng)插入列表判斷時要用snake[1:]排除掉新蛇頭本身否則永遠會認為撞到自己游戲一開局就結束。第四處是畫食物時使用pygame.Rect它的四個參數(shù)是(x, y, width, height)不是(x1, y1, x2, y2)。寫代碼時要注意不要把第四參數(shù)寫成結束坐標。4.3 如何驗證代碼是否正常第一次運行前我會做三個層面的驗證。第一啟動不報錯。雙擊運行或命令行執(zhí)行python snake.py后窗口能正常彈出標題欄顯示“貪吃蛇”。第二初始畫面正常。窗口里有黑色背景綠色蛇身一個紅色食物。蛇頭位置在窗口偏中間蛇身向左延伸。第三操作邏輯正確。按方向鍵蛇能移動按反向鍵不會立刻掉頭吃掉食物后得分增加蛇身變長撞墻或撞自己后畫面出現(xiàn)“Game Over”。如果這三步都通過說明核心邏輯沒問題。之后再去擴展功能才有意義。5. 單局跑通后如何擴展成更像樣的游戲5.1 增加計分和速度遞增初版得分只是數(shù)字變化玩久了會嫌單調(diào)。更常見的做法是得分越高移動速度越快。實現(xiàn)方式很簡單把clock.tick(FPS)中的FPS改成動態(tài)變量例如current_speed min(FPS score // 5, 30) clock.tick(current_speed)每吃5個食物速度加1上限設為30。這里設置上限是因為速度超過30后畫面刷新很快鍵盤操作難度急劇上升反而不適合普通玩家。如果想讓速度變化更平滑可以記錄吃到食物的數(shù)量而不是直接依賴score。因為score可能因為擴展功能而出現(xiàn)加分項容易和速度掛鉤混亂。5.2 增加重新開始和暫停初版代碼已經(jīng)加了空格重新開始。暫停功能也很簡單在游戲循環(huán)里增加一個暫停標記paused False按P鍵切換暫停。暫停時只處理事件和繪制提示文字不更新蛇的位置。注意事件處理部分要單獨寫一個狀態(tài)判斷避免玩家在暫停時還能移動蛇。重新開始時需要把所有狀態(tài)重置蛇身恢復初始長度方向恢復向右分數(shù)歸零食物重新隨機生成游戲結束標記設為False。這些代碼可以封裝成一個reset_game()函數(shù)避免在按鍵分支里寫一大串重復代碼。5.3 增加簡單排行榜或最高分記錄如果你想練一下文件讀寫可以把最高分保存到一個txt文件里。每次游戲結束時判斷當前得分是否大于歷史最高分如果大于則寫入文件。try: with open(high_score.txt, r) as f: high_score int(f.read().strip()) except FileNotFoundError: high_score 0這里使用try except是為了處理文件不存在的情況。第一次運行時沒有歷史記錄直接讓最高分為0。當游戲結束時再判斷一次更新分數(shù)。這里要注意文件編碼和換行符。讀取時最好用strip()去掉空白否則int()可能會因為換行符報錯。寫入時用str(high_score)寫入整數(shù)。這個擴展能幫你練習異常處理和文件操作算是從游戲邏輯跳到通用編程能力的過渡。6. 常見報錯和排查順序6.1 啟動時報 ModuleNotFoundError最常見的原因是pygame沒有安裝成功。先執(zhí)行pip show pygame如果顯示W(wǎng)ARNING: Package(s) not found說明確實沒裝上。接著執(zhí)行pip install pygame。如果已經(jīng)裝上了還是報錯就要懷疑Python環(huán)境不一致。比如你用pip安裝但運行腳本時用的是另一個解釋器。可以這樣驗證python -c import pygame; print(pygame.ver)如果這個命令能正常打印版本號但運行snake.py卻報錯檢查腳本文件是不是被命名成了pygame.py。如果腳本名字和庫名相同Python導入時會優(yōu)先導入當前目錄下的同名文件導致找不到真實的pygame。這個問題很隱蔽我見過不少人踩坑。6.2 窗口一閃而過窗口出現(xiàn)后立刻關閉多半是腳本拋出了異常。因為pygame窗口模式下如果代碼運行到pygame.quit()或拋出未捕獲異常控制臺會顯示錯誤但窗口可能已經(jīng)退出。解決辦法是不要直接雙擊運行而是在命令行里執(zhí)行腳本python snake.py這樣能看到完整的報錯信息。常見錯誤包括未初始化pygame就使用相關函數(shù)、坐標越界、函數(shù)名拼寫錯誤。排查時先看終端里的Traceback定位到具體行再根據(jù)錯誤類型修正。6.3 方向鍵沒反應或蛇反向移動方向鍵沒反應先確認事件類型是不是pygame.KEYDOWN。有些例子用KEYUP那就可能只在按鍵彈起時觸發(fā)手感會很奇怪。本項目使用KEYDOWN按下即觸發(fā)。反向移動的原因是方向更新邏輯沒有過濾掉180度掉頭。檢查代碼里是否寫了下述判斷if new_dir[0] direction[0] ! 0 or new_dir[1] direction[1] ! 0: direction new_dir如果你直接寫成direction new_dir那么快速連按“上、左”時蛇可能瞬間掉頭并撞進自己身體。注意這個判斷要在同一幀事件處理中反復執(zhí)行時仍然有效如果玩家在同一個幀內(nèi)按了多個鍵最終方向會以最后一個合法方向為準這是正常現(xiàn)象。6.4 蛇撞到身體卻沒有結束這個現(xiàn)象通常是“允許蛇頭移動到蛇尾原位置”造成的。因為蛇在移動時如果尾部會同時移開那么蛇頭可以安全地移動到尾巴之前的位置。但判斷碰撞如果用整體蛇身在新蛇頭等于舊尾巴時會被誤判為碰撞于是游戲提前結束。解決辦法是先判斷是否吃到食物。如果吃到食物尾巴不彈出此時蛇頭撞到尾巴就是真撞如果沒吃到食物尾巴即將彈出新蛇頭等于舊尾巴不算撞。具體實現(xiàn)可以這樣if new_head food: # 增長不彈出尾巴所以舊尾巴還在撞到就算撞 snake.insert(0, new_head) if new_head in snake[1:]: game_over True else: score 1 food random_food() else: # 先彈出尾巴再判斷碰撞 tail snake[-1] snake.pop() snake.insert(0, new_head) # 注意此時舊尾巴已經(jīng)不在蛇身里 if new_head in snake[1:]: game_over True不過初版直接用整體蛇身判斷也能玩只是偶爾會在靠近尾巴時提前判定。如果你想做出“完美”手感就需要關注這個細節(jié)。7. 容易被忽略的邊界和性能細節(jié)7.1 窗口大小的取整問題pygame窗口大小可以是任意整數(shù)但貪吃蛇這種格子游戲最好保證總寬和總高是格子大小的整數(shù)倍。不然你設置GRID_WIDTH 29、CELL_SIZE 20窗口寬580沒問題但如果GRID_WIDTH取小數(shù)pygame會直接報錯。另外格子數(shù)量也不要設置太大。比如橫向100格、縱向80格窗口會非常大普通屏幕根本放不下。我建議橫向30到40格縱向20到30格配合20像素格子大小窗口在600x400到800x600之間比較合適。7.2 速度并非越快越好初版FPS 10意味著每秒刷新10幀蛇每秒移動10格。這個速度適合第一次測試。如果你開局就把FPS調(diào)到60蛇會像飛一樣方向鍵根本來不及反應過來。實際測試時我建議把速度增長門檻設得高一點。比如每吃10個食物速度加1上限為20。這樣新手能玩一兩分鐘又不會覺得太慢。如果想要挑戰(zhàn)模式可以單獨做一個變量控制難度不要把速度寫死在多個地方。7.3 蛇身列表的順序千萬不能搞反很多變形bug都出在列表順序上。蛇頭必須是列表第一個元素蛇尾是最后一個。繪制蛇時如果遍歷順序錯了蛇頭可能被畫在蛇身下面看起來像身體蓋住了頭。計算移動時如果pop()寫成pop(0)會刪掉蛇頭蛇身瞬間亂套。我可以給一個簡單的自我檢查方法打印snake[0]和snake[-1]看看是否分別對應蛇頭和蛇尾。如果蛇頭坐標不是當前方向應該到達的位置說明移動邏輯有問題。7.4 批量測試和自動運行思路調(diào)試游戲時每次手動操作很慢。如果想驗證碰撞邏輯可以讓蛇自動朝某個方向移動并在特定坐標觸發(fā)食物。做法是臨時把鍵盤事件注釋掉用一套預設的方向序列讓蛇自動走觀察是否在預期位置game over。這種自動化測試思路對以后寫更復雜項目很有用。游戲雖然是交互式的但核心狀態(tài)更新函數(shù)可以獨立測試。比如把移動、碰撞、食物生成拆成純函數(shù)就可以編寫測試腳本驗證邊界條件。這里的經(jīng)驗是任何項目只要邏輯足夠清晰都可以把自動測試加進來。小游戲也不例外。最后一個建議當代碼跑通后不要急著加各種炫酷功能。先保存一份初始版本作為對照。后面改動過程中如果出現(xiàn)新bug可以快速回到初始版本確認是“原有邏輯問題”還是“新擴展引入的問題”。我在實際寫這個項目時就吃過虧一開始直接加音效、動畫和排行榜結果一個bug查了很久最后發(fā)現(xiàn)只是蛇身列表順序寫反了。基礎邏輯不穩(wěn)功能堆得越多排查越難。貪吃蛇雖然只是一個練手項目但它把Python編程里最實用的幾個部分都過了一遍。如果你能不看源碼自己獨立寫出一版能跑的貪吃蛇說明你已經(jīng)具備了用Python處理簡單狀態(tài)變化的能力。這個基礎上再去看Web開發(fā)、數(shù)據(jù)分析或自動化腳本都會順很多。