[{"data":1,"prerenderedAt":5369},["ShallowReactive",2],{"blog-articles":3},[4,367,1125,1221,1459,1560,2174,3645,4039],{"id":5,"title":6,"author":7,"body":8,"category":347,"date":348,"description":349,"draft":350,"extension":351,"image":352,"meta":353,"navigation":354,"path":355,"seo":356,"stem":357,"tags":358,"__hash__":366},"blog/blog/20260304-troublemaker.md","關於我一天在公司 AWS 開發帳號花掉 14000 鎂的那回事","Ting Zhang",{"type":9,"value":10,"toc":326},"minimark",[11,20,23,28,35,38,49,58,65,68,84,87,92,95,98,106,109,112,115,120,123,127,130,135,144,157,160,168,173,176,179,184,187,195,200,204,207,218,222,225,232,239,242,245,248,251,254,257,260,263,267,270,273,279,283,286,297,301,304,311,314,317,320,323],[12,13,14,15,19],"p",{},"身為工程師，你可能聽過同事不小心把 Production 資料庫砍了、或是忘記關 EC2 多燒了幾百塊。但你有聽過",[16,17,18],"strong",{},"用雲服務一天燒掉 14,000 美金","的嗎？",[12,21,22],{},"沒錯，這件事發生在我身上...嗎？",[24,25,27],"h2",{"id":26},"tldr","TL;DR",[12,29,30,31,34],{},"在公司開發帳號 survey AWS Bedrock AgentCore Policy 功能，試用了 Cedar Policy Generator。幾天後帳單突然出現一筆 ",[16,32,33],{},"$14,000+ USD 的單日費用","。經過 CloudTrail 排查確認是 Cedar Generator 觸發的異常計費，最終 AWS 承認是 Bedrock 端的計費 bug，修復並校正了帳單。",[24,36,37],{"id":37},"事發經過",[12,39,40,41,48],{},"3/4 我因為看到 ",[42,43,47],"a",{"href":44,"rel":45},"https://aws.amazon.com/bedrock/agentcore/",[46],"nofollow","AgentCore"," 新 Feature - Policy 剛上線，所以需要 Survey 一下。",[12,50,51,52,57],{},"AgentCore Policy 讓你可以用 ",[42,53,56],{"href":54,"rel":55},"https://www.cedarpolicy.com/",[46],"Cedar"," 語言來定義 Agent 的授權策略，控制 Agent 能存取哪些工具和資源。Cedar 是 AWS 開源的授權語言，語法可讀性很高，設計上是要讓非工程師也能看得懂的那種。",[12,59,60,61,64],{},"而 AgentCore 很貼心地提供了一個 ",[16,62,63],{},"Natural Language Policy Generator","——你用自然語言描述你要的權限規則，它就幫你生成對應的 Cedar Policy。聽起來很 User-friendly 對吧？",[12,66,67],{},"我就照著文件走了一遍流程：",[69,70,71,75,78,81],"ol",{},[72,73,74],"li",{},"建了一個 Policy Engine",[72,76,77],{},"試了幾次 Cedar Policy Generator（用自然語言描述轉成 Cedar）",[72,79,80],{},"確認功能可以正常運作",[72,82,83],{},"收工，覺得這功能還不錯",[12,85,86],{},"整個過程大概就一兩個小時，正常的 survey 流程。",[12,88,89],{},[16,90,91],{},"然後就沒有然後了——直到五天後。",[24,93,94],{"id":94},"帳單爆炸",[12,96,97],{},"後來請假看完中華隊在 WBC 的比賽，打贏韓國那場實在很感動，回來上班的第一天，3/9 下午，Slack突然被狂 tag，我被我們公司的 Billing Manager & Team Lead 問為何會有一筆費用產生，我也趕緊地打開CE，一個讓我愣住的數字：",[99,100,101],"blockquote",{},[12,102,103],{},[16,104,105],{},"3/4 單日Agentcore產生了 $14,000+ USD的費用",[12,107,108],{},"我先是以為自己眼花了。重新整理頁面，數字還在那裡。",[12,110,111],{},"完了... 哪裡搞錯了吧？",[24,113,114],{"id":114},"排查過程",[116,117,119],"h3",{"id":118},"step-1先開-support-ticket","Step 1：先開 Support Ticket",[12,121,122],{},"不管三七二十一，先開 AWS Support Ticket 回報異常帳單。把時間範圍、帳號資訊、異常金額都附上去，讓 Support 那邊開始調查。",[116,124,126],{"id":125},"step-2自己也同步排查","Step 2：自己也同步排查",[12,128,129],{},"等 Support 回覆的同時，我也開始自己排查紀錄。",[12,131,132],{},[16,133,134],{},"確認 AgentCore Policy 的收費機制",[12,136,137,138,143],{},"根據 ",[42,139,142],{"href":140,"rel":141},"https://aws.amazon.com/bedrock/agentcore/pricing/",[46],"AgentCore 定價頁面","，Policy 的計費主要是：",[145,146,147,154],"ul",{},[72,148,149,150,153],{},"Cedar Policy Generator：",[16,151,152],{},"按 input token 數量計費","（每 1,000 tokens）",[72,155,156],{},"Policy Engine 的 Authorization 請求：按請求數計費",[12,158,159],{},"稍微思考一下：",[69,161,162,165],{},[72,163,164],{},"我當天的使用量根本不可能撐到這個金額。就算我瘋狂打 Generator 也打不出 $14,000，同事跟我說 Billing 那邊顯示 Policy 使用了 109 1M Tokens (接近 1.1 億，我甚至不知道一天要怎麼用掉那麼多 Tokens...)。",[72,166,167],{},"我的 Policy Engine 並沒有 Attach Gateway，所以也沒有真正的使用 Policy 這個 Feature。",[12,169,170],{},[16,171,172],{},"確認 Runtime 沒有產生額外費用",[12,174,175],{},"AgentCore Runtime 是按 CPU 和記憶體的秒級消耗來計費的。我需要確保沒有遺留的 Runtime 在背景持續運作，或者跟 Gateway 掛鉤產生連鎖費用。",[12,177,178],{},"檢查結果：沒有任何遺留資源在跑。",[12,180,181],{},[16,182,183],{},"CloudTrail",[12,185,186],{},"我拉出了那段時間的 CloudTrail 紀錄，逐筆檢查跟 AgentCore 相關的 API 呼叫。",[12,188,189,190,194],{},"最終鎖定是 ",[191,192,193],"code",{},"StartPolicyGeneration"," 這個 API 呼叫。從紀錄上看，我確實只有呼叫了3次，請求量完全不合理對應到那個帳單金額。",[12,196,197],{},[16,198,199],{},"結論：應該不是我的使用量有問題，是計費那邊有問題。",[116,201,203],{"id":202},"step-3跟-support-同步","Step 3：跟 Support 同步",[12,205,206],{},"我把 CloudTrail 的排查結果整理好，回覆到 Support Ticket 上。附上了：",[145,208,209,212,215],{},[72,210,211],{},"明確的 API 呼叫時間和次數",[72,213,214],{},"計費金額的不合理性說明",[72,216,217],{},"我這邊已經確認沒有遺留資源",[116,219,221],{"id":220},"step-4aws-確認是計費-bug","Step 4：AWS 確認是計費 Bug",[12,223,224],{},"AWS Support 將 case 轉給了 Bedrock 團隊。Bedrock 團隊調查後確認：",[99,226,227],{},[12,228,229],{},[16,230,231],{},"Cedar Policy Generator 存在計費問題，導致實際計費金額遠超正常使用量應有的費用。",[12,233,234,235,238],{},"他們修復了這個計費 bug，並且",[16,236,237],{},"校正了帳單","。",[12,240,241],{},"結案。",[24,243,244],{"id":244},"心理狀態",[12,246,247],{},"看到 $14,000 這個數字的時候，腦袋裡跑過各種最壞的劇本——會不會要自己賠？這是我好幾個月的薪水總和，光想就覺得可怕。",[12,249,250],{},"即使理性上告訴自己先查原因再說，甚至基本上確定不是我這邊的問題，但焦慮感還是壓不住。",[12,252,253],{},"這幾天嚴重睡眠不足，每天躺在床上腦袋還在轉「到底是哪裡出問題」。腸胃也跟著出狀況，一直拉肚子。壓力對身體的影響比我想像中來得直接。",[12,255,256],{},"最後當 CloudTrail 的證據越來越明確指向計費問題，Support 也確認 Bedrock 團隊發現問題並正在進行修復，我的心情才開始慢慢平復。",[24,258,259],{"id":259},"事後反思",[12,261,262],{},"雖然這次最後證實是 AWS 的計費 bug，但整個事件讓我學到了很多。",[116,264,266],{"id":265},"_1-工作紀錄真的很重要","1. 工作紀錄真的很重要",[12,268,269],{},"當下會感到慌張的原因，很大一部分來自 Context 不足，一來我不確定 Agentcore Policy 是如何計費，為何可以用到那麼貴？二來我不知道我5天前具體做了什麼操作。",[12,271,272],{},"如果我當初 survey 的時候沒有留下操作紀錄，排查的時候會更加困難。CloudTrail 能幫你查到 API 呼叫，但你自己當時在做什麼、為什麼做，這些 context 只有你自己知道。",[12,274,275,278],{},[16,276,277],{},"養成習慣：每次操作不熟的雲服務時，簡單記錄一下你做了什麼。"," 不需要多詳細，一個簡單的筆記或是 Slack 訊息就夠了。關鍵時刻這些紀錄可以救你一命。",[116,280,282],{"id":281},"_2-使用前搞懂計費方式","2. 使用前搞懂計費方式",[12,284,285],{},"這聽起來像廢話，但真的很多人（包括我）在 survey 新服務的時候會直接跳進去玩，不會先仔細看定價頁面。",[145,287,288,291,294],{},[72,289,290],{},"開始 survey 前，先看過定價頁面",[72,292,293],{},"特別注意按量計費的服務，搞清楚「量」是怎麼定義的",[72,295,296],{},"開發帳號最好設定 Budget Alert，超過閾值自動通知",[116,298,300],{"id":299},"_3-先了解公司的處理流程","3. 先了解公司的處理流程",[12,302,303],{},"事發的時候，我其實不太確定公司對這種事情的態度和處理方式。後來跟主管報告時，主管第一句話就是：",[99,305,306],{},[12,307,308],{},[16,309,310],{},"「不可能叫你賠的，先搞清楚狀況就好。」",[12,312,313],{},"這句話讓我放下了很大的心理負擔。每間公司的文化不同，但我想大部分公司都不會因為合理操作導致的意外費用而讓員工賠償。",[24,315,316],{"id":316},"結語",[12,318,319],{},"回頭看這件事，蠻慶幸最後不是我的問題，也慶幸公司的主管很 Nice",[12,321,322],{},"這次經歷讓我建立起了面對雲端成本異常的 SOP。以前覺得帳單管理是 FinOps 團隊的事，現在覺得每個會碰到雲服務的工程師都應該有基本的成本意識。",[12,324,325],{},"題外話，這讓我想到我大學時期第一次買股票的時候，我買了3股台積電花了大概 2000塊吧，當時每天看著損益正負三四百塊就能很影響我的心情，影響我做事與學習的效率，直到今天可能每天的損益就是一個月的薪水，我還是照常做著自己的事情。我想有了這次經驗，肯定能讓我在未來遇到類似的事情的時候心態更穩健吧。",{"title":327,"searchDepth":328,"depth":328,"links":329},"",2,[330,331,332,333,340,341,346],{"id":26,"depth":328,"text":27},{"id":37,"depth":328,"text":37},{"id":94,"depth":328,"text":94},{"id":114,"depth":328,"text":114,"children":334},[335,337,338,339],{"id":118,"depth":336,"text":119},3,{"id":125,"depth":336,"text":126},{"id":202,"depth":336,"text":203},{"id":220,"depth":336,"text":221},{"id":244,"depth":328,"text":244},{"id":259,"depth":328,"text":259,"children":342},[343,344,345],{"id":265,"depth":336,"text":266},{"id":281,"depth":336,"text":282},{"id":299,"depth":336,"text":300},{"id":316,"depth":328,"text":316},"技術","2026-03-18","Survey AWS AgentCore Policy完，過了幾天以後才發現帳單噴了14000...",false,"md","/images/blog/20260304-troublemaker/banner.png",{},true,"/blog/20260304-troublemaker",{"title":6,"description":349},"blog/20260304-troublemaker",[359,360,361,362,363,364,365],"AWS","雲端","成本控管","經驗分享","踩雷","DevOps","雲服務","6QyxVCpq8RjfovhyvoS0butlFVU7bQKJ1pfTGyL4M1Q",{"id":368,"title":369,"author":7,"body":370,"category":1111,"date":1112,"description":1113,"draft":350,"extension":351,"image":1114,"meta":1115,"navigation":354,"path":1116,"seo":1117,"stem":1118,"tags":1119,"__hash__":1124},"blog/blog/internal-signoz.md","架設公司內部使用的 SigNoz 服務的紀錄隨筆",{"type":9,"value":371,"toc":1091},[372,375,378,389,392,395,398,456,461,465,470,688,691,694,699,702,707,710,715,718,723,726,731,736,740,754,757,760,763,781,790,821,836,839,846,866,871,875,880,883,886,910,913,917,922,928,953,968,971,995,999,1008,1014,1018,1021,1037,1041,1048,1058,1061],[24,373,374],{"id":374},"前言",[12,376,377],{},"如果有分散式服務，目前可能遇到的問題：",[145,379,380,383,386],{},[72,381,382],{},"有錯的時候需要各自看service log，沒有統一的 trace 可以串起來",[72,384,385],{},"效能瓶頸要靠「感覺」和「經驗」來猜",[72,387,388],{},"CloudWatch 的 console 分散在好幾個頁面，排查一個問題要在 Logs Insights、X-Ray、Metrics 之間跳來跳去",[12,390,391],{},"目標：建一套統一的 Observability 平台，讓 Metrics / Traces / Logs 都在同一個地方查看。",[24,393,394],{"id":394},"方案",[12,396,397],{},"我們支援兩種 Observability Backend，依客戶的需求和預算擇一部署：",[399,400,401,420],"table",{},[402,403,404],"thead",{},[405,406,407,411,414,417],"tr",{},[408,409,394],"th",{"align":410},"left",[408,412,413],{"align":410},"Collector",[408,415,416],{"align":410},"Backend",[408,418,419],{"align":410},"場景",[421,422,423,440],"tbody",{},[405,424,425,431,434,437],{},[426,427,428],"td",{"align":410},[16,429,430],{},"SigNoz",[426,432,433],{"align":410},"SigNoz OTel Collector (EKS)",[426,435,436],{"align":410},"SigNoz + ClickHouse",[426,438,439],{"align":410},"需要完整 APM + Trace 分析，預算可控",[405,441,442,447,450,453],{},[426,443,444],{"align":410},[16,445,446],{},"CloudWatch",[426,448,449],{"align":410},"AWS ADOT Collector (本地 Docker)",[426,451,452],{"align":410},"CloudWatch + X-Ray",[426,454,455],{"align":410},"已深度使用 AWS 生態系，不想額外維運",[99,457,458],{},[12,459,460],{},"關鍵設計：兩種方案都使用 OTLP HTTP (:4318)，切換只需改 OTEL_EXPORTER_OTLP_ENDPOINT 一個環境變數。應用端的程式碼完全不用改，未來要換 vendor 也零成本。",[24,462,464],{"id":463},"cloudwatch-vs-signoz功能比較","CloudWatch vs SigNoz：功能比較",[99,466,467],{},[12,468,469],{},"AI gen 的, 一些常見功能是對的，但是比較深入的功能 (ex: Cloudwatch AI Operations) 沒特別驗證過",[399,471,472,484],{},[402,473,474],{},[405,475,476,479,482],{},[408,477,478],{},"功能領域",[408,480,481],{},"AWS CloudWatch",[408,483,430],{},[421,485,486,499,512,525,537,550,563,576,588,601,614,627,639,651,664,676],{},[405,487,488,493,496],{},[426,489,490],{},[16,491,492],{},"Traces",[426,494,495],{},"X-Ray（單次查詢限 6 小時）",[426,497,498],{},"Traces Explorer（任意時間範圍）",[405,500,501,506,509],{},[426,502,503],{},[16,504,505],{},"Metrics",[426,507,508],{},"CloudWatch Metrics",[426,510,511],{},"ClickHouse + PromQL",[405,513,514,519,522],{},[426,515,516],{},[16,517,518],{},"Logs",[426,520,521],{},"Logs Insights（專有語法）",[426,523,524],{},"Logs Explorer（ClickHouse SQL）",[405,526,527,532,535],{},[426,528,529],{},[16,530,531],{},"Service Map",[426,533,534],{},"Application Map",[426,536,531],{},[405,538,539,544,547],{},[426,540,541],{},[16,542,543],{},"Dashboard",[426,545,546],{},"超過 3 個需付費",[426,548,549],{},"無限免費",[405,551,552,557,560],{},[426,553,554],{},[16,555,556],{},"Alerts",[426,558,559],{},"CloudWatch Alarms",[426,561,562],{},"可對 Metrics/Traces/Logs 設警報",[405,564,565,570,573],{},[426,566,567],{},[16,568,569],{},"AI 根因分析",[426,571,572],{},"Investigations（生成式 AI）",[426,574,575],{},"無",[405,577,578,583,586],{},[426,579,580],{},[16,581,582],{},"GenAI 監控",[426,584,585],{},"Bedrock AgentCore 整合",[426,587,575],{},[405,589,590,595,598],{},[426,591,592],{},[16,593,594],{},"合成監控 / RUM",[426,596,597],{},"Synthetics Canaries + RUM",[426,599,600],{},"無（需外部工具）",[405,602,603,608,611],{},[426,604,605],{},[16,606,607],{},"Container",[426,609,610],{},"Container Insights（深度 ECS/EKS 整合）",[426,612,613],{},"透過 OTel Collector 收集",[405,615,616,621,624],{},[426,617,618],{},[16,619,620],{},"Lambda / DB",[426,622,623],{},"Lambda Insights / Database Insights",[426,625,626],{},"無原生支援",[405,628,629,634,637],{},[426,630,631],{},[16,632,633],{},"網路監控",[426,635,636],{},"Flow / Internet / Synthetic Monitors",[426,638,575],{},[405,640,641,646,649],{},[426,642,643],{},[16,644,645],{},"Log 異常偵測",[426,647,648],{},"ML 自動偵測",[426,650,575],{},[405,652,653,658,661],{},[426,654,655],{},[16,656,657],{},"例外監控",[426,659,660],{},"無原生功能",[426,662,663],{},"Exceptions Monitoring（自動從 trace 擷取）",[405,665,666,671,673],{},[426,667,668],{},[16,669,670],{},"Messaging Queue",[426,672,660],{},[426,674,675],{},"Kafka / Celery 監控",[405,677,678,683,685],{},[426,679,680],{},[16,681,682],{},"Log Pipeline",[426,684,575],{},[426,686,687],{},"支援解析、轉換、trace 關聯",[116,689,690],{"id":690},"選型邏輯",[12,692,693],{},"怎麼幫客戶選？在 APM 和分散式追蹤這個場景，SigNoz 明顯更適合：",[12,695,696],{},[16,697,698],{},"1. 統一介面 vs 四散的 Console",[12,700,701],{},"CloudWatch 排查一個問題，需要在 Logs Insights、Metrics console、X-Ray、Application Signals 之間跳轉，每個的查詢語法還不一樣。SigNoz 把 Metrics / Traces / Log 放在同一個介面，從延遲高的 trace 直接點進去看對應的 log，不需要切 console。",[12,703,704],{},[16,705,706],{},"2. X-Ray 的 6 小時查詢限制",[12,708,709],{},"X-Ray 雖然保留 30 天的追蹤資料，但 UI 上一次只能查 6 小時的範圍。想看一整天的 trace？要手動跑 4 次查詢再拼起來。SigNoz 沒有這個限制。",[12,711,712],{},[16,713,714],{},"3. OpenTelemetry 原生",[12,716,717],{},"SigNoz 直接基於 OpenTelemetry 打造，資料進來就能用。CloudWatch 需要透過 ADOT（AWS Distro for OpenTelemetry）做中間轉換，多了一層 mapping 就多了一層可能出問題的地方。",[12,719,720],{},[16,721,722],{},"4. 成本結構",[12,724,725],{},"自建 SigNoz 只需要 EKS 上的計算資源，費用可控且可預測。CloudWatch 的計費項目多且分散：自訂指標、Dashboard、GetMetricData API 呼叫、Log 儲存和查詢、X-Ray trace 記錄⋯每一項都獨立計費，帳單很難預估。",[99,727,728],{},[12,729,730],{},"但如果客戶已經重度使用 AWS 生態系（Lambda、ECS、RDS 等），CloudWatch 的 Lambda Insights、Container Insights、Database Insights、AI Investigations 這些整合是 SigNoz 目前無法替代的。",[12,732,733],{},[16,734,735],{},"選型建議：以 APM + 分散式追蹤為主要需求 → SigNoz。以 AWS 基礎設施監控為主且不想額外維運 → CloudWatch。根據客戶的實際需求和預算，選擇其中一套。",[24,737,739],{"id":738},"signoz-部署-資源配置-驗證","SigNoz 部署 - 資源配置 & 驗證",[12,741,742,743,749,750,753],{},"SigNoz 用官方 Helm chart 部署到 EKS 上，具體的 YAML 和指令都在 ",[42,744,748],{"href":745,"rel":746,"title":747},"https://github.com/%7Brepo_address%7D/tree/main/opentelemetry",[46],"https://github.com/{repo_address}/tree/main/opentelemetry","repo"," 裡的 ",[191,751,752],{},"signoz/"," 目錄，這邊只講幾個需要思考的設計決策。",[116,755,756],{"id":756},"驗證",[12,758,759],{},"這次 gc-host 上部署的 SigNoz 不使用 ip whitelist 限制存取",[12,761,762],{},"有兩個對外端點，目前透過以下方式驗證：",[12,764,765,773,776,777,780],{},[42,766,770],{"href":767,"rel":768,"title":769},"https://signoz.internal.xx.ai/",[46],"https://signoz.internal.xx.ai",[16,771,772],{},"UI",[16,774,775],{},"（"," ",[191,778,779],{},"signoz.internal.xx.ai","）— 直接使用 SigNoz 內建的 SSO，接 Google OAuth 登入。團隊成員用公司 Google 帳號登入即可，不需要額外管理帳號密碼。",[12,782,783,776,786,789],{},[16,784,785],{},"OTLP Collector（",[191,787,788],{},"signoz-otel.internal.xx.ai","）— 這是機器對機器的端點，不適合走 SSO。做了兩層防護：",[69,791,792,815],{},[72,793,794,797,798,801,802,805,806],{},[16,795,796],{},"Bearer Token 驗證"," — nginx ingress 的 ",[191,799,800],{},"configuration-snippet"," 檢查 ",[191,803,804],{},"Authorization"," header，不帶正確 token 直接回 401\n",[69,807,808],{},[72,809,810,811,814],{},"Token 放在 1Password 的 ",[191,812,813],{},"Internal Signoz Collector"," 內",[72,816,817,820],{},[16,818,819],{},"Rate Limiting"," — 100 req/s per IP，burst 500，防止意外或惡意的大量灌入",[12,822,823,824,827,828,831,832,835],{},"Token 存在 ",[191,825,826],{},".env","（gitignored），部署時透過 ",[191,829,830],{},"envsubst"," 注入到 ingress yaml。服務端透過 ",[191,833,834],{},"OTEL_EXPORTER_OTLP_HEADERS"," 環境變數帶入，不需要改任何程式碼。",[116,837,838],{"id":838},"資源配置策略",[12,840,841,842,845],{},"因為是內部 dev 環境，整體策略是",[16,843,844],{},"壓到最小夠用"," ：",[145,847,848,854,860],{},[72,849,850,853],{},[16,851,852],{},"ClickHouse"," ：1Gi request / 3Gi limit，20Gi gp3 磁碟",[72,855,856,859],{},[16,857,858],{},"TTL"," ：Traces 和 Metrics 都只保留 3 天",[72,861,862,865],{},[16,863,864],{},"關掉不需要的元件"," ：alertmanager、zookeeper 在 dev 環境沒有用",[99,867,868],{},[12,869,870],{},"所有元件都明確設了 resource requests 和 limits。不設 limits 的話，ClickHouse 非常容易吃光整個 node 的記憶體。",[116,872,874],{"id":873},"clickhouse-磁碟管理","ClickHouse 磁碟管理",[99,876,877],{},[12,878,879],{},"之前架設 Signoz 還在測試的時候有發現記憶體資源不足導致 Service CrashLoop, 目前猜這個解法可以解決，但還要擺一陣子看看",[12,881,882],{},"20Gi 的 EBS 搭配 3 天 TTL，理論上不會爆。但 ClickHouse 的 TTL 清理是惰性的（不會主動跑），如果突然灌入大量 trace，磁碟可能來不及釋放。",[12,884,885],{},"所以加了一個 K8s CronJob，每 6 小時檢查磁碟使用率：",[145,887,888,894,904],{},[72,889,890,893],{},[16,891,892],{},"\u003C 75%"," ：正常，不做任何事",[72,895,896,899,900,903],{},[16,897,898],{},">= 75%"," ：強制 ",[191,901,902],{},"OPTIMIZE TABLE ... FINAL","，觸發 TTL 清理",[72,905,906,909],{},[16,907,908],{},">= 90%"," ：緊急模式，直接 drop 最舊的 partition",[24,911,912],{"id":912},"應用如何接入",[116,914,916],{"id":915},"demo-apps","Demo Apps",[99,918,919],{},[12,920,921],{},"目前Signoz UI & collector 的憑證還在處理，好了的話就不用 skip ssl 驗證，目前先用個 workaround 的方法繞過去",[12,923,924,927],{},[42,925,748],{"href":745,"rel":926,"title":747},[46]," 裡有三個 demo 應用（Express、NestJS、FastAPI），涵蓋了幾種常見場景：",[145,929,930,933,936,939,950],{},[72,931,932],{},"一般 API 回應",[72,934,935],{},"慢回應模擬（500ms delay）",[72,937,938],{},"DB 查詢（SQLAlchemy）",[72,940,941,942,949],{},"外部 HTTP 呼叫（httpx → ",[42,943,946],{"href":944,"rel":945},"http://httpbin.org/",[46],[16,947,948],{},"httpbin.org"," ）",[72,951,952],{},"Error logging",[12,954,955,956,959,960,963,964,967],{},"用 ",[191,957,958],{},"make demo"," 一鍵啟動，",[191,961,962],{},"make demo-traffic"," 打流量，",[191,965,966],{},"make verify-signoz"," 驗證端到端是否通。",[116,969,970],{"id":970},"三個設計原則",[69,972,973,979,989],{},[72,974,975,978],{},[16,976,977],{},"盡量不寫 SDK wrapper"," — 直接使用官方 OpenTelemetry SDK，跟著社群走，減少維護負擔",[72,980,981,984,985,988],{},[16,982,983],{},"所有配置走環境變數"," — 程式碼裡零 hardcode，SDK 自動讀取 ",[191,986,987],{},"OTEL_*"," 環境變數",[72,990,991,994],{},[16,992,993],{},"tracing 初始化"," — 在任何其他 import / app 初始化之前，否則可能會漏掉早期的 span",[116,996,998],{"id":997},"共用-backend-如何分辨service","共用 backend 如何分辨service",[145,1000,1001],{},[72,1002,1003,1004,1007],{},"SigNoz 可以設定客製化的 filter 欄位，透過環境變數 ",[191,1005,1006],{},"OTEL_RESOURCE_ATTRIBUTES"," 來進行標注，UI上就會出現 filter",[12,1009,1010],{},[1011,1012],"img",{"alt":327,"src":1013},"/images/blog/internal-signoz/image.png",[116,1015,1017],{"id":1016},"切換-backend-修改環境變數","切換 Backend = 修改環境變數",[12,1019,1020],{},"這是整個架構最核心的設計：不管客戶選哪套 backend，應用端的程式碼完全一樣，只需要改一個環境變數：",[145,1022,1023,1030],{},[72,1024,1025,1026,1029],{},"選 SigNoz → ",[191,1027,1028],{},"OTEL_EXPORTER_OTLP_ENDPOINT=\u003Chttps://signoz-otel.internal.xx.ai",">",[72,1031,1032,1033,1036],{},"選 CloudWatch → ",[191,1034,1035],{},"OTEL_EXPORTER_OTLP_ENDPOINT=\u003Chttp://localhost:4318","（透過本地> ADOT Collector）",[116,1038,1040],{"id":1039},"sampling-策略","Sampling 策略",[12,1042,1043,1044,1047],{},"所有服務統一使用 10% head-based sampling（",[191,1045,1046],{},"parentbased_traceidratio","）。",[12,1049,955,1050,1053,1054,1057],{},[191,1051,1052],{},"parentbased"," 而不是純 ",[191,1055,1056],{},"traceidratio"," 是為了確保：如果上游服務決定要 sample 這個 trace，下游服務也會保留，不會斷掉整條追蹤鏈。",[24,1059,1060],{"id":1060},"參考資料",[145,1062,1063,1070,1077,1084],{},[72,1064,1065],{},[42,1066,1069],{"href":1067,"rel":1068,"title":1067},"https://signoz.io/docs/introduction/",[46],"SigNoz Documentation",[72,1071,1072],{},[42,1073,1076],{"href":1074,"rel":1075,"title":1074},"https://opentelemetry.io/docs/",[46],"OpenTelemetry Documentation",[72,1078,1079],{},[42,1080,1083],{"href":1081,"rel":1082,"title":1081},"https://aws-otel.github.io/docs/introduction",[46],"AWS ADOT Documentation",[72,1085,1086],{},[42,1087,1090],{"href":1088,"rel":1089,"title":1088},"https://github.com/SigNoz/charts",[46],"SigNoz Helm Chart",{"title":327,"searchDepth":328,"depth":328,"links":1092},[1093,1094,1095,1098,1103,1110],{"id":374,"depth":328,"text":374},{"id":394,"depth":328,"text":394},{"id":463,"depth":328,"text":464,"children":1096},[1097],{"id":690,"depth":336,"text":690},{"id":738,"depth":328,"text":739,"children":1099},[1100,1101,1102],{"id":756,"depth":336,"text":756},{"id":838,"depth":336,"text":838},{"id":873,"depth":336,"text":874},{"id":912,"depth":328,"text":912,"children":1104},[1105,1106,1107,1108,1109],{"id":915,"depth":336,"text":916},{"id":970,"depth":336,"text":970},{"id":997,"depth":336,"text":998},{"id":1016,"depth":336,"text":1017},{"id":1039,"depth":336,"text":1040},{"id":1060,"depth":328,"text":1060},"技術隨筆","2026-03-10","記錄如何架設與部署 SigNoz 作為公司內部的 Observability 服務，適用於統一管理 Metrics、Traces 和 Logs。","/images/blog/internal-signoz/banner.png",{},"/blog/internal-signoz",{"title":369,"description":1113},"blog/internal-signoz",[430,1120,1121,1122,1123],"Observability","APM","分散式追蹤","技術架設","q5GOiX3bLLFhl-rdyCBKycwJu2N4dS4Brco4tgUv2YU",{"id":1126,"title":1127,"author":7,"body":1128,"category":1211,"date":1212,"description":327,"draft":350,"extension":351,"image":327,"meta":1213,"navigation":354,"path":1214,"seo":1215,"stem":1216,"tags":1217,"__hash__":1220},"blog/blog/2026q1-insights.md","2026 工作與生活的一些 Insights & 隨筆",{"type":9,"value":1129,"toc":1205},[1130,1133,1136,1139,1144,1147,1152,1155,1158,1161,1164,1172,1175,1178,1183,1186,1189,1192,1200],[24,1131,1132],{"id":1132},"前一份工作教會我的事情",[12,1134,1135],{},"離職後大概過了4個月，偶爾從前同事口中得知前公司的經營狀況亮起紅燈，甚至各種「鬼故事」頻傳。",[12,1137,1138],{},"這讓我再度反思：在動盪的環境下，如何持續保有競爭力，並提高自己無可取代的價值，好讓未來的自己擁有更多選擇權？",[99,1140,1141],{},[12,1142,1143],{},"工作與生活的界線必須清晰",[12,1145,1146],{},"這不只是時間分配，更是心理邊界的建立。",[99,1148,1149],{},[12,1150,1151],{},"警惕家族企業的非理性因子",[12,1153,1154],{},"雖然凡事皆有例外，但這類企業往往容易將勞資權益與人情壓力混為一談。當極端狀況發生時，人情壓力往往會干擾對你的人生有重大影響的判斷。",[12,1156,1157],{},"我的底層邏輯：\n工作之於我，核心價值是獲取資源（賺錢）。我不排斥在職場建立友誼，但若某些因素挑戰到這個底層邏輯，我必須能毫不猶豫地做出選擇。ㄑ",[24,1159,1160],{"id":1160},"對於目前工作的看法",[12,1162,1163],{},"目前的工作環境對我而言是一個絕佳的成長平台。同事與主管都蠻好相處的，最重要的是公司制度透明，績效與獎金機制明確。",[69,1165,1166,1169],{},[72,1167,1168],{},"把握環境紅利：全力累積技術實力與產業經驗。",[72,1170,1171],{},"量化成長：透過明確的績效爭取實質回饋，包括金錢與技術上的（技術如何量化成長可以另外寫一篇文章聊）",[24,1173,1174],{"id":1174},"接案",[12,1176,1177],{},"因緣際會下，今年應該有機會可以接到幾個副業專案，包含前後端開發與 AI 應用的接案機會。雖然這與我的職涯發展高度相關，但我也意識到一個現實問題：",[99,1179,1180],{},[12,1181,1182],{},"我的能量與注意力，是否足以支撐如此高強度的輸出？",[12,1184,1185],{},"在正職工作之外，我還需要處理接案、語言學習、結婚與人生規劃，所以 Overloading 是可預期會發生的。希望我可以撐過這段高壓期，只要撐過去，預期可收穫非常豐厚的成果。",[24,1187,1188],{"id":1188},"對於生活的一些紀錄",[12,1190,1191],{},"這次過年回家，我也跟家人釐清了家裡的經濟狀況，也讓我持續反思未來 3-5 年的佈局。",[69,1193,1194,1197],{},[72,1195,1196],{},"婚姻計畫的取捨\n我決定將結婚計畫適度推遲。如果婚禮的開銷會大幅影響現有的工作專注度或投資布局，那這件事就不是現階段的優先選項。理性的選擇，是為了讓未來更有保障。",[72,1198,1199],{},"日本移居計畫\n面對國際情勢的不確定性，加上對台灣經濟長期走勢的觀察，前往日本的計畫仍需持續推進。",[99,1201,1202],{},[12,1203,1204],{},"2026 是一個關鍵的轉折點，願我能在這場多線並進的計畫中可以保持清醒且穩定輸出。加油勒！",{"title":327,"searchDepth":328,"depth":328,"links":1206},[1207,1208,1209,1210],{"id":1132,"depth":328,"text":1132},{"id":1160,"depth":328,"text":1160},{"id":1174,"depth":328,"text":1174},{"id":1188,"depth":328,"text":1188},"生活","2026-02-20",{},"/blog/2026q1-insights",{"title":1127,"description":327},"blog/2026q1-insights",[1218,1219],"Insights","Mood","WKk4DLUdFDA0KuDovfMPZtMC_vxlPwHgzE4WCvw_Vfo",{"id":1222,"title":1223,"author":7,"body":1224,"category":347,"date":1448,"description":1449,"draft":350,"extension":351,"image":1450,"meta":1451,"navigation":354,"path":1452,"seo":1453,"stem":1454,"tags":1455,"__hash__":1458},"blog/blog/decrease-coding-agent-illusion.md","如何降低 Coding Agent的幻覺？",{"type":9,"value":1225,"toc":1435},[1226,1229,1232,1235,1239,1246,1253,1256,1259,1262,1273,1276,1279,1282,1285,1294,1297,1303,1308,1312,1315,1323,1328,1334,1337,1341,1348,1359,1367,1371,1378,1383,1391,1394,1401,1407,1410,1416,1419,1424],[1227,1228,374],"h1",{"id":374},[12,1230,1231],{},"在做公司 POC 的過程，因為需要用到 Strands Agent & AWS AgentCore 一些比較新的SDK，實在有太多次都遇到 AI Gen 完以後發現 Syntax Highlight 沒有提示，看一下 Source Code or Docs 發現根本沒有這個 function / attr 的狀況，嘗試了幾個提升 LLM 準確率的方法",[12,1233,1234],{},"基本上解決思路都是讓 Agent 可以有個參照對象，提升正確率",[24,1236,1238],{"id":1237},"context7","Context7",[12,1240,1241],{},[42,1242,1245],{"href":1243,"rel":1244},"https://github.com/upstash/context7",[46],"GitHub - upstash/context7: Context7 MCP Server -- Up-to-date code documentation for LLMs and AI code editors",[12,1247,1248],{},[42,1249,1252],{"href":1250,"rel":1251},"https://context7.com/",[46],"Context7 - Up-to-date documentation for LLMs and AI code editors",[12,1254,1255],{},"Context7 是一個 MCP Tool, 可以自動查詢最新文檔",[116,1257,1258],{"id":1258},"文檔來源",[12,1260,1261],{},"任何人都可以到官網去發一個 Add Library Request，這邊可以看到目前正在進行 Parsing & Crawling 的目標文檔，我嘗試把我自己的repo丟上去也可以跑，並且有辦法在 Dashboard 搜尋到",[12,1263,1264,1267,1270],{},[1011,1265],{"alt":327,"src":1266},"/images/blog/decrease-coding-agent-illusion/image1.png",[1011,1268],{"alt":327,"src":1269},"/images/blog/decrease-coding-agent-illusion/image2.png",[1011,1271],{"alt":327,"src":1272},"/images/blog/decrease-coding-agent-illusion/image3.png",[116,1274,1275],{"id":1275},"使用",[12,1277,1278],{},"可以選擇自架 MCP Server or 使用官方服務",[12,1280,1281],{},"原本因為方便所以直接使用官方服務(安裝時需填入 API key)，一個月限制 1000 次 query，實際使用發現他好像每天都會重置 1000 次的 quota，基本上用不完。",[12,1283,1284],{},"你可以選擇加敘述在會自動放到 Context 的地方 (ex: cursor rule, claude.md, agent.md, etc.)",[1286,1287,1292],"pre",{"className":1288,"code":1290,"language":1291},[1289],"language-text","Always use Context7 MCP when I need library/API documentation, code generation, setup or configuration steps without me having to explicitly ask.\n","text",[191,1293,1290],{"__ignoreMap":327},[12,1295,1296],{},"或者加在 LLM 來回的地方",[1286,1298,1301],{"className":1299,"code":1300,"language":1291},[1289],"幫我寫一個 strands agent graph pattern example, 記得遵照文檔教學執行/必須使用 context 7...\n",[191,1302,1300],{"__ignoreMap":327},[12,1304,1305],{},[1011,1306],{"alt":327,"src":1307},"/images/blog/decrease-coding-agent-illusion/image4.png",[116,1309,1311],{"id":1310},"how-it-works","How it works?",[12,1313,1314],{},"使用兩個 MCP Tools 來實現",[145,1316,1317,1320],{},[72,1318,1319],{},"Search",[72,1321,1322],{},"Get Context",[1324,1325,1327],"h4",{"id":1326},"context-usage","Context Usage",[1286,1329,1332],{"className":1330,"code":1331,"language":1291},[1289],"MCP tools · /mcp                                                                                                                                                   \n     └ mcp__context7__resolve-library-id: 499 tokens                                                                                                                    \n     └ mcp__context7__query-docs: 408 tokens             \n",[191,1333,1331],{"__ignoreMap":327},[12,1335,1336],{},"可以看到 context7在 context 使用上算是非常輕量的 MCP Tool",[116,1338,1340],{"id":1339},"context7-作為-skill-管理工具","Context7 作為 Skill 管理工具",[12,1342,1343],{},[42,1344,1347],{"href":1345,"rel":1346},"https://context7.com/docs/skills",[46],"Skills - Context7 MCP",[145,1349,1350,1353,1356],{},[72,1351,1352],{},"搜尋功能",[72,1354,1355],{},"號稱有濾除有安全疑慮的 Skill (不安全的 script / prompt injection…)",[72,1357,1358],{},"可以自動偵測本機上有安裝的 Coding LLM 然後安裝進去 (但是看起來不支援 Kiro🥲)",[12,1360,1361,1364],{},[1011,1362],{"alt":327,"src":1363},"/images/blog/decrease-coding-agent-illusion/image5.png",[1011,1365],{"alt":327,"src":1366},"/images/blog/decrease-coding-agent-illusion/image6.png",[24,1368,1370],{"id":1369},"skill","Skill",[12,1372,1373],{},[42,1374,1377],{"href":1375,"rel":1376},"https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview",[46],"Agent Skills",[99,1379,1380],{},[12,1381,1382],{},"大家應該都知道的東西，網路上很多基本介紹這邊不多贅述，我的用法是把docs repo 直接放到 Reference 內，讓 LLM 去產生 SKILL.md",[145,1384,1385,1388],{},[72,1386,1387],{},"讓 LLM 知道有這個 skill",[72,1389,1390],{},"不一定每個 sdk 都有 docs repo, 所以也順便有個 index 可以讓 LLM 快速的查詢文檔路徑",[116,1392,1393],{"id":1393},"範例",[12,1395,1396],{},[42,1397,1400],{"href":1398,"rel":1399},"https://github.com/strands-agents/docs",[46],"GitHub - strands-agents/docs: Documentation for the Strands Agents SDK. A model-driven approach to building AI agents in just a few lines of code.",[1286,1402,1405],{"className":1403,"code":1404,"language":1291},[1289],"# ~/.claude/skills/strands-agents-docs/SKILL.md\n---\nname: strands-agents-docs\ndescription: Access comprehensive Strands Agents documentation to help build, deploy, and operate AI agents using the Strands Agents SDK\n---\n# Strands Agents Documentation Skill\nThis skill provides access to the complete Strands Agents documentation, enabling code agents to quickly locate relevant guides, examples, and API references for building AI agents with the Strands Agents SDK.\n## Instructions\nWhen working with Strands Agents SDK, use this skill to:\n1. Locate relevant documentation files based on the task or concept\n2. Reference specific guides for implementation patterns\n3. Find deployment and production operation best practices\n4. Access model provider configurations and examples\n5. Understand multi-agent patterns and tool development\nSearch for documentation by topic or concept, and reference the appropriate markdown file path below for detailed information.\n## Documentation Index\n### Quickstart Guides\n- `/Users/ting/.claude/skills/strands-agents-docs/references/docs/user-guide/quickstart.md` - Complete quickstart guide showing how to create your first Strands agent with tools and model providers\n- `/Users/ting/.claude/skills/strands-agents-docs/references/docs/user-guide/quickstart/overview.md` - Overview of getting started with Strands Agents\n=== 下略 ===\n",[191,1406,1404],{"__ignoreMap":327},[116,1408,1327],{"id":1409},"context-usage-1",[1286,1411,1414],{"className":1412,"code":1413,"language":1291},[1289],"Skills · /skills\n User\n └ bedrock-agentcore-sdk: 38 tokens                                                                                                                                 \n └ strands-agents-docs: 36 tokens\n",[191,1415,1413],{"__ignoreMap":327},[24,1417,1418],{"id":1418},"其他",[99,1420,1421],{},[12,1422,1423],{},"基本上都是“LLM已經拉了一坨沒辦法跑的 Code”的時候，已經參與Debug了，是不理想的狀況",[145,1425,1426,1429,1432],{},[72,1427,1428],{},"指出 site-packages 內的原始碼路徑給 LLM, 讓他參照使用",[72,1430,1431],{},"複製文檔直接喂到嘴邊",[72,1433,1434],{},"搭配其他 AI Tools (Perplexity, Deepwiki)",{"title":327,"searchDepth":328,"depth":328,"links":1436},[1437,1443,1447],{"id":1237,"depth":328,"text":1238,"children":1438},[1439,1440,1441,1442],{"id":1258,"depth":336,"text":1258},{"id":1275,"depth":336,"text":1275},{"id":1310,"depth":336,"text":1311},{"id":1339,"depth":336,"text":1340},{"id":1369,"depth":328,"text":1370,"children":1444},[1445,1446],{"id":1393,"depth":336,"text":1393},{"id":1409,"depth":336,"text":1327},{"id":1418,"depth":328,"text":1418},"2026-01-26","介紹透過 context7 & agent skills 來提升 Coding agent one shot 機率的方法","/images/blog/decrease-coding-agent-illusion/banner.png",{},"/blog/decrease-coding-agent-illusion",{"title":1223,"description":1449},"blog/decrease-coding-agent-illusion",[1456,1457],"Tooling","Development","m_26ovdfaN8lhTUGpa0GYVOveveCALhEyBRKj-NqpLs",{"id":1460,"title":1461,"author":7,"body":1462,"category":1211,"date":1550,"description":1551,"draft":350,"extension":351,"image":327,"meta":1552,"navigation":354,"path":1553,"seo":1554,"stem":1555,"tags":1556,"__hash__":1559},"blog/blog/2025-review.md","2025 Review",{"type":9,"value":1463,"toc":1548},[1464,1467,1470,1485,1488,1495,1498,1506,1513,1516,1519,1526,1531,1534,1539],[12,1465,1466],{},"今天是我開始工作的第708天，今年順利從智電系統畢業，順利來到GC成爲比較正式一點的軟體工程師。",[12,1468,1469],{},"11月底Onboard到現在其實也一個月出頭而已，必須說這一個月的收穫是超乎我想像的，雖然我沒辦法說出具體的感覺，但我可以感受到我正身處一個“正確的環境”",[12,1471,1472,1473,1478,1479,1484],{},"今天同時看到我的兩位師傅 Alex & Danny 的",[42,1474,1477],{"href":1475,"rel":1476},"https://www.linkedin.com/feed/update/urn:li:activity:7409841197114028032/",[46],"文章1"," & ",[42,1480,1483],{"href":1481,"rel":1482},"https://chenghsuan.me/posts/imposter-syndrome",[46],"文章2","，都在談論冒牌者症候群，我其實一直都處在這樣的狀態，不過這讓我無時無刻都在思考我的不足之處並且補齊，以結果來說暫時是正向的，就先這樣子吧。",[12,1486,1487],{},"最後可以來一點明年的期許：",[99,1489,1490],{},[12,1491,1492],{},[16,1493,1494],{},"Mindset",[12,1496,1497],{},"主要是改進一些我自己很annoying我自己的點吧，例如",[145,1499,1500,1503],{},[72,1501,1502],{},"停止懷疑任何會阻止我變得更好的行為，例如我會花過多的時間去確認我腦子裡冒出來的問題是否是\"不笨的問題\"，然後再花時間琢磨要怎麼問，期許我可以就直接問，被罵了再說",[72,1504,1505],{},"感覺自己還是有一些心防，例如會有：“這個人會不會其實覺得我很爛？“的念頭，我認為更好的方法還是把這個防備卸下比較好，被背刺再說",[99,1507,1508],{},[12,1509,1510],{},[16,1511,1512],{},"Skills",[12,1514,1515],{},"問題很大，但也不大，相信繼續待在這個環境下，就算我不想進步也很難",[12,1517,1518],{},"除了保持現有的幾個主線 (主業 & Moniit & 每週讀書會)，時間允許之下希望自己可以週更這個blog，不管是心態上或是把在GC寫的文章搬來都好，最好是用英文寫",[99,1520,1521],{},[12,1522,1523],{},[16,1524,1525],{},"Languages",[145,1527,1528],{},[72,1529,1530],{},"Conversational English",[12,1532,1533],{},"作為剛換工作的藉口我已經停差不多兩個月了，哈，這個必須要做到，是通往國外工作的必須門票",[145,1535,1536],{},[72,1537,1538],{},"Japanese at least N3",[12,1540,1541,1542,1547],{},"看起來很難，不過根據我的基礎加上一點運氣(?)，參考 ",[42,1543,1546],{"href":1544,"rel":1545},"https://zh.wikipedia.org/zh-tw/%E6%97%A5%E6%9C%AC%E8%AF%AD%E8%83%BD%E5%8A%9B%E6%B5%8B%E8%AF%95",[46],"維基百科: 漢字圈考生學習時數","，絕對是有可能的。",{"title":327,"searchDepth":328,"depth":328,"links":1549},[],"2025-12-26","今天WFH，午餐吃新媛煮的泡菜鍋，讚",{},"/blog/2025-review",{"title":1461,"description":1551},"blog/2025-review",[1557,1558],"Review","2025","a--I2MAWFPvTaWjOROqM2NxWOXW-vsLpx3tIeVCH4PA",{"id":1561,"title":1562,"author":7,"body":1563,"category":1739,"date":2164,"description":2165,"draft":350,"extension":351,"image":327,"meta":2166,"navigation":354,"path":2167,"seo":2168,"stem":2169,"tags":2170,"__hash__":2173},"blog/blog/2025q3q4-career.md","2025 軟體工程師面試經驗",{"type":9,"value":1564,"toc":2139},[1565,1567,1570,1573,1576,1579,1593,1596,1610,1613,1621,1624,1627,1631,1634,1637,1648,1651,1655,1658,1661,1664,1668,1671,1674,1677,1681,1684,1687,1714,1717,1719,1722,1726,1729,1737,1740,1743,1745,1749,1752,1762,1765,1770,1786,1791,1802,1808,1810,1814,1817,1839,1842,1847,1850,1861,1866,1883,1888,1893,1896,1899,1901,1905,1908,1925,1928,1933,1938,1943,1948,1953,1958,1966,1971,1973,1977,1980,1993,1996,2002,2008,2013,2027,2029,2033,2036,2041,2044,2049,2083,2086,2089,2091,2094,2097,2100,2117,2119,2123],[24,1566,374],{"id":374},[12,1568,1569],{},"嗨，大家好，我是Ting，是一名全端工程師。\n這篇文是寫給想要轉職工程師的你，希望透過我的經驗分享能對你有幫助。",[12,1571,1572],{},"我原本在智電系統工作，是一個新創能源公司，除了web相關開發以外，我也投入非常多時間在能源相關的業務邏輯研究與開發，但在工作一年半以後，我認為我對能源相關的業務邏輯已經沒有太大的興趣，工作內容也漸漸被甲方的客製化需求給淹沒，所以我決定離職。",[12,1574,1575],{},"做這個決定主要有以下考量",[116,1577,1578],{"id":1578},"目標",[145,1580,1581,1584,1587,1590],{},[72,1582,1583],{},"更明確的職涯目標",[72,1585,1586],{},"更高的薪資成長(或是說更穩定)",[72,1588,1589],{},"WFH或自由的工作時間",[72,1591,1592],{},"未來有規劃國外工作",[116,1594,1595],{"id":1595},"離職的好處",[145,1597,1598,1601,1604,1607],{},[72,1599,1600],{},"可以挑選公司，讓工作內容更集中在開發",[72,1602,1603],{},"學習新技術，使用一樣的時間來學習更多東西",[72,1605,1606],{},"作為通往國外工作的敲門磚，必須提升履歷價值，至少到一間有聽過的公司",[72,1608,1609],{},"更有機會WFH，自由安排自己的時間，原本需要耗費大量通勤時間",[116,1611,1612],{"id":1612},"離職的壞處",[145,1614,1615,1618],{},[72,1616,1617],{},"原公司是正在IPO階段，離職後需要放棄RSU，算是一筆不小的機會成本",[72,1619,1620],{},"離開舒適圈，其實原本的工作環境除了需要每日通勤以外，工作內容不太會有人管",[1622,1623],"hr",{},[24,1625,1626],{"id":1626},"準備",[116,1628,1630],{"id":1629},"_1-程人頻道ted-協助","1. 程人頻道Ted 協助",[12,1632,1633],{},"首先這篇文章必須 S/O 給程人頻道的Ted，可以說這個計劃沒有他的話就不會這麼順利。\n原本就有在聽Ted的Podcast，他也一直有在提供想要轉換跑道的工程師協助，剛好在我下定決心要轉職以後，\n他上一期的諮詢服務剛結束一陣子，在招下一期的工程師，我把我的履歷與動機整理一下以後提供，就這麼被選上了。",[12,1635,1636],{},"這個計劃主要包含每週一小時的諮詢時間直到轉職成功，內容主要有",[145,1638,1639,1642,1645],{},[72,1640,1641],{},"履歷健檢",[72,1643,1644],{},"模擬面試 (BQ / Leetcode / System Design)",[72,1646,1647],{},"檢視準備方向是否正確",[12,1649,1650],{},"其實Ted的協助不僅僅是單純轉職而已，我們幾乎花了一樣多的時間在討論與探索“我真正想要的職涯規劃”，透過他的經驗，與整個計劃的進行，我也更聚焦我的職涯目標。",[116,1652,1654],{"id":1653},"_2-專案實作-moniit","2. 專案實作 - Moniit",[12,1656,1657],{},"延續上一點，除了每週一小時的諮詢以外，程人頻道也一直有在經營社群開發的項目，剛好他們近期規劃的資產管理App - Moniit正在開發早期階段，透過這個機會我也幾乎參與了這個產品從0開始的開發過程，",[12,1659,1660],{},"我主要參與了後端開發，第一次使用FastAPI作為主要框架，且實戰了System Design，如何根據實際狀況來取捨設計，怎麼樣算是好的設計，怎麼樣算是Over Design。",[12,1662,1663],{},"並且這個專案有非常完善的K8s設計，包括但不限於helm, hpa, IaC框架等，這些都是我第一次接觸，透過實戰我對於k8s以及gcp相關雲生態系有更深入的了解。",[116,1665,1667],{"id":1666},"_3-leetcode","3. leetcode",[12,1669,1670],{},"我leetcode只寫了166題 (E:72/M:82/H:12)，主要參考Blind 75跟Neetcode 150，如果對該演算法不熟，我會先把Hello Algo相關的文章看完，然後練習，最後再找Neetcode的影片來複習以及參考他的解題思路，慢慢把整個解題思維培養起來。",[12,1672,1673],{},"但相較於大部分的工程師，我覺得我在Leetcode的投入相對較少，一部分是因為瞄準公司的是台商居多，而不是Fang等一線外商，另一部分則是我花了更多時間在實際開發專案來獲取實務經驗。",[12,1675,1676],{},"我認為台商的OA的題目好像沒有遇到真的很難的，以及面試過程也很少遇到演算法題目，基本上都是履歷相關的問答，但若是要以更高水準的公司為目標的話，我認爲還需要投入更多的時間與精力。",[116,1678,1680],{"id":1679},"_4-履歷","4. 履歷",[12,1682,1683],{},"可以參考標準的Latex模板，基本上是分成Work Experience、Side Projects、Education、Technical Skills四個部分。",[12,1685,1686],{},"各個部分我認為的重點",[145,1688,1689,1697,1708,1711],{},[72,1690,1691,1692],{},"General\n",[145,1693,1694],{},[72,1695,1696],{},"根據職位寫公版的履歷，再根據投遞目標的JD來微調",[72,1698,1699,1700],{},"Work Experience\n",[145,1701,1702,1705],{},[72,1703,1704],{},"強調技術相關的經驗，並且盡量量化指標(ex: rps, qps, 節省多少時間, 節省多少成本等等)",[72,1706,1707],{},"若有PM相關經驗，我覺得是個加分",[72,1709,1710],{},"Education: 簡單即可，基本上只看畢業校系，除非你的專案或論文與JD高度相關，不然我感覺放了只是佔版面而已",[72,1712,1713],{},"Technical Skills: 盡量跟JD上提到的技術棧相同或高度相關",[12,1715,1716],{},"以現今的web相關工作來看，除了前後端與資料庫技術棧以外，Docker與K8s基本上一定會出現在JD的Preferred Skills中，面試中如果可以帶一下或是有被問到並且回答的不錯是個大加分，所以可以的話一定要補一下相關知識。",[1622,1718],{},[24,1720,1721],{"id":1721},"面試",[116,1723,1725],{"id":1724},"_1-cardlytics-full-stack-engineer","1. Cardlytics Full Stack Engineer",[1324,1727,1728],{"id":1728},"時程",[145,1730,1731,1734],{},[72,1732,1733],{},"D+0: 投遞履歷",[72,1735,1736],{},"D+2: Quick chat with Manager",[1324,1738,1739],{"id":1739},"心得",[12,1741,1742],{},"這是我第一個獲得投遞履歷有後續的公司，但這間對我來說實在是越級打怪，需要的職位要求以及語言我都還無法勝任，但也很趕謝Manager還是跟我聊了30分鐘，且知道我的狀況後給了我一些方向與建議，也不吝於分享Cardlytics的產品。",[1622,1744],{},[116,1746,1748],{"id":1747},"_2-捷思科技-full-stack-engineer-偏前端","2. 捷思科技 - Full Stack Engineer (偏前端)",[1324,1750,1728],{"id":1751},"時程-1",[145,1753,1754,1756,1759],{},[72,1755,1733],{},[72,1757,1758],{},"D+7: 現場上機測驗 + 面試",[72,1760,1761],{},"D+14: Reject",[1324,1763,1739],{"id":1764},"心得-1",[12,1766,1767],{},[16,1768,1769],{},"Pair Programming 題目：",[69,1771,1772,1775],{},[72,1773,1774],{},"寫一個翻牌互動介面 (HTML/CSS hover 翻轉效果)",[72,1776,1777,1778,1781,1782,1785],{},"不使用 ",[191,1779,1780],{},"Promise.all"," 實作一個 ",[191,1783,1784],{},"PromiseAllLike"," function",[12,1787,1788],{},[16,1789,1790],{},"Behavior Question：",[69,1792,1793,1796,1799],{},[72,1794,1795],{},"目前工作遇過最困難的難題以及如何解決",[72,1797,1798],{},"如果接手 co-workers 的程式很糟的話，會怎麼處理",[72,1800,1801],{},"若工作上遇到某個流程很冗長很麻煩的話，會怎麼處理",[12,1803,1804,1807],{},[16,1805,1806],{},"總結：","\n一間小公司，要找偏前端的，覺得我的技術棧並不符合，算是個面試經驗。",[1622,1809],{},[116,1811,1813],{"id":1812},"_3-trend-micro","3. Trend Micro",[1324,1815,1728],{"id":1816},"時程-2",[145,1818,1819,1821,1824,1827,1830,1833,1836],{},[72,1820,1733],{},[72,1822,1823],{},"D+5: 收到 Online Assignment 邀請",[72,1825,1826],{},"D+11: 完成 Online Assignment",[72,1828,1829],{},"D+14: 一面",[72,1831,1832],{},"D+32: 收到二面邀請",[72,1834,1835],{},"D+39: 二面",[72,1837,1838],{},"D+46: 寄信詢問未回，無聲卡",[1324,1840,1739],{"id":1841},"心得-2",[12,1843,1844],{},[16,1845,1846],{},"一面內容：",[12,1848,1849],{},"自我介紹：",[145,1851,1852,1855,1858],{},[72,1853,1854],{},"學經歷",[72,1856,1857],{},"工作經驗",[72,1859,1860],{},"Side Project",[12,1862,1863],{},[16,1864,1865],{},"技術問題：",[145,1867,1868,1871,1874,1877,1880],{},[72,1869,1870],{},"RESTful API 用過哪些 Method? (GET, POST, PUT, DELETE)",[72,1872,1873],{},"前端或後端有無 test 相關經驗？",[72,1875,1876],{},"協作相關經驗",[72,1878,1879],{},"Skillset 應用經驗",[72,1881,1882],{},"開發遇到最大的困難",[12,1884,1885],{},[16,1886,1887],{},"二面：",[145,1889,1890],{},[72,1891,1892],{},"一樣是根據履歷問答居多，還有畫參與過的專案的架構圖",[1324,1894,1895],{"id":1895},"總結",[12,1897,1898],{},"隔那麼久是因為中間家人生病需要照顧，所以耽誤了一些時間，趨勢那邊也表示體諒，配合我改期。\n趨勢應該是我原本目標的其中一間，雖然薪水可能開不高，但是WFH政策以及公司地理位置非常加分。",[1622,1900],{},[116,1902,1904],{"id":1903},"_4-gilacloud-backend-集雅科技","4. GilaCloud Backend / 集雅科技",[1324,1906,1728],{"id":1907},"時程-3",[145,1909,1910,1913,1916,1919,1922],{},[72,1911,1912],{},"D+0: 獵頭連繫",[72,1914,1915],{},"D+3: 確認投遞",[72,1917,1918],{},"D+8: 完成 OA",[72,1920,1921],{},"D+49: 一面",[72,1923,1924],{},"D+66: Reject",[1324,1926,1739],{"id":1927},"心得-3",[12,1929,1930],{},[16,1931,1932],{},"Coderbyte OA：",[145,1934,1935],{},[72,1936,1937],{},"難度約 Easy ~ Medium",[12,1939,1940],{},[16,1941,1942],{},"一面 (Online Interview)：",[12,1944,1945],{},[16,1946,1947],{},"技術主管提問：",[145,1949,1950],{},[72,1951,1952],{},"履歷相關題目（內容沒印象了）",[12,1954,1955],{},[16,1956,1957],{},"人資提問：",[145,1959,1960,1963],{},[72,1961,1962],{},"基本 BQ",[72,1964,1965],{},"詢問公司內是否有讀書會等進修機制，被洗臉",[12,1967,1968,1970],{},[16,1969,1806],{},"\n中間隔那麼久應該是因為我OA分數勉勉強強過關而已，所以等前面的人都面完了才輪的到我。\n技術主管雖然對能源領域不懂，但問的還蠻深入的，整體感覺不錯，但人資面試給我蠻扣分的印象。",[1622,1972],{},[116,1974,1976],{"id":1975},"_5-crescendo-lab","5. Crescendo Lab",[1324,1978,1728],{"id":1979},"時程-4",[145,1981,1982,1984,1987,1990],{},[72,1983,1733],{},[72,1985,1986],{},"D+1: 收到一面邀請",[72,1988,1989],{},"D+7: 一面 (CTO / HR quick call)",[72,1991,1992],{},"D+10: Reject",[1324,1994,1995],{"id":1995},"面試過程",[12,1997,1998,2001],{},[16,1999,2000],{},"Tech Lead","\n主要詢問了我目前公司的業務內容與產品細節，以及在專案中遇到的挑戰和我的解決經驗。技術面則聚焦於過往處理大數據、API 效能優化（例如如何調整資料庫 index）、與太陽能管理系統相關的技術細節（如數據量、QPS、缺值補償等）。也有探討到專案部署、監控、以及是否運用 AI 工具提升效率等經驗，整體問題偏重於實務經驗及技術應用。",[12,2003,2004,2007],{},[16,2005,2006],{},"HR","\nHR 主要詢問了一些基本背景與求職動機，像是過去的經歷、為何想換工作、對公司的了解與興趣，以及對未來工作環境和待遇的期待，同時也有聊到目前是否有使用 AI 工具和如果錄取後的交接安排等。",[12,2009,2010],{},[16,2011,2012],{},"整體感覺：",[145,2014,2015,2018,2021,2024],{},[72,2016,2017],{},"遇到最困難/最挫折的工作經驗，可以再想好一點的故事",[72,2019,2020],{},"DB Indexing 相關知識需補齊",[72,2022,2023],{},"PostgreSQL 相關語法與知識需補齊",[72,2025,2026],{},"錄取機會不高，就算錄取，薪水應該也會蠻低",[1622,2028],{},[116,2030,2032],{"id":2031},"_6-blockriver","6. BlockRiver",[1324,2034,1728],{"id":2035},"時程-5",[145,2037,2038],{},[72,2039,2040],{},"D+0: HR Quick Call",[1324,2042,1739],{"id":2043},"心得-4",[12,2045,2046],{},[16,2047,2048],{},"HR Quick Call 問題：",[69,2050,2051,2054],{},[72,2052,2053],{},"Introduce yourself",[72,2055,2056,2057],{},"Technique questions:\n",[145,2058,2059,2067,2075],{},[72,2060,2061,2062],{},"List 3 methods to communicate between two processes\n",[145,2063,2064],{},[72,2065,2066],{},"答案參考：Pipes、Shared Memory、Message Queues、Sockets、Signals、Redis (in-memory, web-based)",[72,2068,2069,2070],{},"List 6 data containers in STL\n",[145,2071,2072],{},[72,2073,2074],{},"參考：vector, list, deque, set, map, unordered_map 等",[72,2076,2077,2078],{},"Insert and find time complexity for set\n",[145,2079,2080],{},[72,2081,2082],{},"答案：O(log n), O(log n)（註：回答 O(1), O(1) 可能不正確，set 是紅黑樹實作）",[1324,2084,1895],{"id":2085},"總結-1",[12,2087,2088],{},"全英文面試，但我英文講感覺不太順，HR應該是香港或是中國人，直接問我要不要說中文...\n感覺是透過HR快速篩選履歷，照著問題問，我想要請他多敘述一下問題(STL是啥真的沒聽過)都不理睬。",[1622,2090],{},[24,2092,1895],{"id":2093},"總結-2",[12,2095,2096],{},"細算下來這半年以來我投了大概接近200個履歷（中間因為家人生病有暫停一個月），有後續的大概只佔不到10%，但一部分是因為我認為我需要累積面試經驗才有辦法在我真正想去的公司的面試中表現得更好，所以基本上職位技術棧有對到我就投了，不管薪資待遇等其他事項，若是你認為你的面試經驗已經充足，也可以指投你想去的公司就好。",[116,2098,2099],{"id":2099},"建議",[145,2101,2102,2105,2108,2111,2114],{},[72,2103,2104],{},"一定要嘗試將目前有參與過的專案，或是Side Projects嘗試繪製成架構圖，並且有辦法在面試的時候從0開始邊畫邊講解為何要選用這個，以及這個設計的優缺點或是取捨等。",[72,2106,2107],{},"一直投履歷就對了，累積面試過程的經驗我覺得是一條必經之路，可以檢視目前的準備是否充足，也可以調整自己在面試過程中的問答表現。",[72,2109,2110],{},"面試趣以及LinkedIn Premiere都可以直接課，在密集投遞履歷的階段幫助很大。",[72,2112,2113],{},"從面試官的角度來思考他們想要怎麼樣特色的人，切記面試的時候需要給面試官”你是來提供價值，是來輸出的“的印象，若有機會的話可以自己延伸一些技術話題，\n只是單純問答的話蠻可惜的，因為一方面對方也是在看未來是否可以跟你順利的合作。",[72,2115,2116],{},"常見的BQ問題一定要先想好一個故事，不要現場遇到問題才想，通常會講到爆掉，或是不那麼適合的回覆。",[1622,2118],{},[24,2120,2122],{"id":2121},"reference","Reference",[69,2124,2125,2132],{},[72,2126,2127],{},[42,2128,2131],{"href":2129,"rel":2130},"https://techporn.io/podcast/052c5980-7a04-4bd3-9976-24cf8aeecdc6",[46],"程人頻道EP263",[72,2133,2134],{},[42,2135,2138],{"href":2136,"rel":2137},"https://moniit.com/",[46],"資產管理App - Moniit官方網站",{"title":327,"searchDepth":328,"depth":328,"links":2140},[2141,2146,2152,2160,2163],{"id":374,"depth":328,"text":374,"children":2142},[2143,2144,2145],{"id":1578,"depth":336,"text":1578},{"id":1595,"depth":336,"text":1595},{"id":1612,"depth":336,"text":1612},{"id":1626,"depth":328,"text":1626,"children":2147},[2148,2149,2150,2151],{"id":1629,"depth":336,"text":1630},{"id":1653,"depth":336,"text":1654},{"id":1666,"depth":336,"text":1667},{"id":1679,"depth":336,"text":1680},{"id":1721,"depth":328,"text":1721,"children":2153},[2154,2155,2156,2157,2158,2159],{"id":1724,"depth":336,"text":1725},{"id":1747,"depth":336,"text":1748},{"id":1812,"depth":336,"text":1813},{"id":1903,"depth":336,"text":1904},{"id":1975,"depth":336,"text":1976},{"id":2031,"depth":336,"text":2032},{"id":2093,"depth":328,"text":1895,"children":2161},[2162],{"id":2099,"depth":336,"text":2099},{"id":2121,"depth":328,"text":2122},"2025-11-13","Software engineer interview experience in 2025.",{},"/blog/2025q3q4-career",{"title":1562,"description":2165},"blog/2025q3q4-career",[2171,2172,1558],"Interview","Career","dIQ4AxjCw-2ZyrTZoptA-22qc9EV3GQKUOWsMTIFHmE",{"id":2175,"title":2176,"author":7,"body":2177,"category":347,"date":3634,"description":3635,"draft":350,"extension":351,"image":327,"meta":3636,"navigation":354,"path":3637,"seo":3638,"stem":3639,"tags":3640,"__hash__":3644},"blog/blog/concurrency.md","Python 並發處理方法",{"type":9,"value":2178,"toc":3613},[2179,2183,2186,2189,2192,2196,2243,2247,2282,2286,2319,2323,2341,2344,2486,2489,3185,3188,3440,3444,3450,3468,3472,3477,3495,3498,3503,3507,3510,3514,3517,3531,3535,3538,3584,3586,3609],[24,2180,2182],{"id":2181},"這篇文章的用處","這篇文章的用處？",[12,2184,2185],{},"若專案內有遇到效能瓶頸，這可能可以幫助你加速。",[12,2187,2188],{},"Python 提供了幾種並發方式，每種方式適用於不同類型的任務。",[24,2190,2191],{"id":2191},"各方式介紹",[116,2193,2195],{"id":2194},"_1-多線程multithreading","1. 多線程（Multithreading）",[145,2197,2198,2204,2225,2231,2237],{},[72,2199,2200,2203],{},[16,2201,2202],{},"特點","：由於 GIL 的存在，多線程無法實現真正的並行，線程間由解釋器切換執行。",[72,2205,2206,2209,2210,2213,2214],{},[16,2207,2208],{},"適用場景","：適合 ",[16,2211,2212],{},"I/O 綁定任務","，如：\n",[145,2215,2216,2219,2222],{},[72,2217,2218],{},"API 呼叫",[72,2220,2221],{},"檔案讀寫",[72,2223,2224],{},"使用者輸入等待",[72,2226,2227,2230],{},[16,2228,2229],{},"限制","：在 CPU 綁定任務（如計算 Fibonacci 數列）中，效能與單線程迴圈相差無幾。",[72,2232,2233,2236],{},[16,2234,2235],{},"優點","：線程間資料共享簡單，記憶體開銷低。",[72,2238,2239,2242],{},[16,2240,2241],{},"缺點","：受 GIL 限制，無法利用多核心。",[116,2244,2246],{"id":2245},"_2-多進程multiprocessing","2. 多進程（Multiprocessing）",[145,2248,2249,2254,2272,2277],{},[72,2250,2251,2253],{},[16,2252,2202],{},"：每個進程擁有獨立的 Python 解釋器和 GIL，因此可以在不同 CPU 核心上實現真正的並行。",[72,2255,2256,2209,2258,2213,2261],{},[16,2257,2208],{},[16,2259,2260],{},"CPU 綁定任務",[145,2262,2263,2266,2269],{},[72,2264,2265],{},"數學運算",[72,2267,2268],{},"圖像處理",[72,2270,2271],{},"日誌分析",[72,2273,2274,2276],{},[16,2275,2235],{},"：繞過 GIL，實現真並行，速度較快。",[72,2278,2279,2281],{},[16,2280,2241],{},"：記憶體使用量高，進程間資料共享較複雜（需使用管道或共享記憶體）。",[116,2283,2285],{"id":2284},"_3-asyncio","3. Asyncio",[145,2287,2288,2293,2309,2314],{},[72,2289,2290,2292],{},[16,2291,2202],{},"：使用協程（coroutines）實現非阻塞的並發，適合 I/O 綁定任務。",[72,2294,2295,2297,2298],{},[16,2296,2208],{},"：\n",[145,2299,2300,2303,2306],{},[72,2301,2302],{},"網路請求",[72,2304,2305],{},"檔案 I/O",[72,2307,2308],{},"其他等待外部資源的任務",[72,2310,2311,2313],{},[16,2312,2235],{},"：輕量，記憶體使用效率高。",[72,2315,2316,2318],{},[16,2317,2241],{},"：不適合 CPU 密集型任務，因為它並非真正的並行。",[116,2320,2322],{"id":2321},"_4-第三方庫","4. 第三方庫",[145,2324,2325,2331,2336],{},[72,2326,2327,2330],{},[16,2328,2329],{},"例子","：Cython、NumPy、Pandas、PyTorch 等。",[72,2332,2333,2335],{},[16,2334,2202],{},"：這些庫通常使用 C 語言實現，部分操作可繞過 GIL，提供高效能。",[72,2337,2338,2340],{},[16,2339,2208],{},"：特定計算密集型任務（如矩陣運算）。",[24,2342,2343],{"id":2343},"特性比較表",[399,2345,2346,2365],{},[402,2347,2348],{},[405,2349,2350,2353,2356,2359,2362],{},[408,2351,2352],{},"特性",[408,2354,2355],{},"Multi-threading",[408,2357,2358],{},"Multi-processing",[408,2360,2361],{},"AsyncIO",[408,2363,2364],{},"Disabled GIL",[421,2366,2367,2384,2401,2419,2436,2452,2467],{},[405,2368,2369,2374,2377,2380,2382],{},[426,2370,2371],{},[16,2372,2373],{},"並行",[426,2375,2376],{},"❌",[426,2378,2379],{},"✅",[426,2381,2376],{},[426,2383,2379],{},[405,2385,2386,2391,2394,2397,2399],{},[426,2387,2388],{},[16,2389,2390],{},"記憶體共享",[426,2392,2393],{},"容易",[426,2395,2396],{},"困難",[426,2398,2393],{},[426,2400,2393],{},[405,2402,2403,2408,2411,2414,2416],{},[426,2404,2405],{},[16,2406,2407],{},"記憶體使用",[426,2409,2410],{},"低",[426,2412,2413],{},"高",[426,2415,2410],{},[426,2417,2418],{},"中等",[405,2420,2421,2426,2429,2432,2434],{},[426,2422,2423],{},[16,2424,2425],{},"CPU 密集型",[426,2427,2428],{},"差",[426,2430,2431],{},"好",[426,2433,2428],{},[426,2435,2431],{},[405,2437,2438,2443,2445,2447,2450],{},[426,2439,2440],{},[16,2441,2442],{},"I/O 密集型",[426,2444,2431],{},[426,2446,2418],{},[426,2448,2449],{},"最佳",[426,2451,2431],{},[405,2453,2454,2459,2461,2463,2465],{},[426,2455,2456],{},[16,2457,2458],{},"實作複雜度",[426,2460,2418],{},[426,2462,2413],{},[426,2464,2418],{},[426,2466,2413],{},[405,2468,2469,2474,2477,2480,2483],{},[426,2470,2471],{},[16,2472,2473],{},"競爭條件風險",[426,2475,2476],{},"低（GIL保護）",[426,2478,2479],{},"低（隔離）",[426,2481,2482],{},"無（單執行緒）",[426,2484,2485],{},"高（需手動處理）",[24,2487,2488],{"id":2488},"範例程式碼",[1286,2490,2494],{"className":2491,"code":2492,"language":2493,"meta":327,"style":327},"language-python shiki shiki-themes github-light github-dark","import time\nimport threading\nimport multiprocessing\nimport asyncio\nimport sys\nfrom concurrent.futures import ThreadPoolExecutor\nfrom functools import wraps\n\n# 計時裝飾器，用於測量執行時間\ndef timing_decorator(func):\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        result = func(*args, **kwargs)\n        end = time.time()\n        print(f\"{func.__name__} 執行時間: {end - start:.4f} 秒\")\n        return result\n    return wrapper\n\n# CPU 密集型任務：計算 Fibonacci 數列\ndef fib(n):\n    if n \u003C= 1:\n        return n\n    return fib(n - 1) + fib(n - 2)\n\n# I/O 綁定任務：模擬網路請求（全局函數）\ndef sync_io_task():\n    time.sleep(1)  # 模擬 1 秒的 I/O 延遲\n\n# 異步 I/O 任務\nasync def io_bound_task():\n    await asyncio.sleep(1)  # 模擬 1 秒的 I/O 延遲\n    return \"I/O 任務完成\"\n\n# 多線程實現\n@timing_decorator\ndef run_multithreading(n_tasks, task_type=\"cpu\"):\n    threads = []\n    if task_type == \"cpu\":\n        for _ in range(n_tasks):\n            t = threading.Thread(target=fib, args=(35,))\n            threads.append(t)\n            t.start()\n        for t in threads:\n            t.join()\n    else:  # I/O 任務\n        for _ in range(n_tasks):\n            t = threading.Thread(target=sync_io_task)\n            threads.append(t)\n            t.start()\n        for t in threads:\n            t.join()\n\n# 多進程實現\n@timing_decorator\ndef run_multiprocessing(n_tasks, task_type=\"cpu\"):\n    processes = []\n    if task_type == \"cpu\":\n        for _ in range(n_tasks):\n            p = multiprocessing.Process(target=fib, args=(35,))\n            processes.append(p)\n            p.start()\n        for p in processes:\n            p.join()\n    else:  # I/O 任務\n        for _ in range(n_tasks):\n            p = multiprocessing.Process(target=sync_io_task)\n            processes.append(p)\n            p.start()\n        for p in processes:\n            p.join()\n\n# Asyncio 實現\n@timing_decorator\nasync def run_asyncio(n_tasks):\n    tasks = [io_bound_task() for _ in range(n_tasks)]\n    await asyncio.gather(*tasks)\n\n# 禁用 GIL 的多線程實現（需 Python 3.13 --disable-gil）\n@timing_decorator\ndef run_nogil_multithreading(n_tasks, task_type=\"cpu\"):\n    with ThreadPoolExecutor(max_workers=n_tasks) as executor:\n        if task_type == \"cpu\":\n            futures = [executor.submit(fib, 35) for _ in range(n_tasks)]\n        else:  # I/O 任務\n            futures = [executor.submit(sync_io_task) for _ in range(n_tasks)]\n        for future in futures:\n            future.result()\n\n# 主程式\nif __name__ == \"__main__\":\n    n_tasks = 4  # 任務數量\n\n    print(\"=== CPU 密集型任務 (計算 Fibonacci 數列) ===\")\n    print(\"多線程 (Multithreading):\")\n    run_multithreading(n_tasks, task_type=\"cpu\")\n\n    print(\"\\n多進程 (Multiprocessing):\")\n    run_multiprocessing(n_tasks, task_type=\"cpu\")\n\n    if sys.version_info >= (3, 13) and hasattr(sys, \"disable_gil\"):\n        print(\"\\n禁用 GIL 的多線程:\")\n        run_nogil_multithreading(n_tasks, task_type=\"cpu\")\n    else:\n        print(\"\\n禁用 GIL 的多線程: 需要 Python 3.13 並啟用 --disable-gil\")\n\n    print(\"\\n=== I/O 綁定任務 (模擬網路請求) ===\")\n    print(\"多線程 (Multithreading):\")\n    run_multithreading(n_tasks, task_type=\"io\")\n\n    print(\"\\n多進程 (Multiprocessing):\")\n    run_multiprocessing(n_tasks, task_type=\"io\")\n\n    print(\"\\nAsyncio:\")\n    asyncio.run(run_asyncio(n_tasks))\n\n    if sys.version_info >= (3, 13) and hasattr(sys, \"disable_gil\"):\n        print(\"\\n禁用 GIL 的多線程:\")\n        run_nogil_multithreading(n_tasks, task_type=\"io\")\n    else:\n        print(\"\\n禁用 GIL 的多線程: 需要 Python 3.13 並啟用 --disable-gil\")\n","python",[191,2495,2496,2504,2509,2514,2520,2526,2532,2538,2544,2550,2556,2562,2568,2574,2580,2586,2592,2598,2604,2609,2615,2621,2627,2633,2639,2644,2650,2656,2662,2667,2673,2679,2685,2691,2696,2702,2708,2714,2720,2726,2732,2738,2744,2750,2756,2762,2768,2773,2779,2784,2789,2794,2799,2804,2810,2815,2821,2827,2832,2837,2843,2849,2855,2861,2867,2872,2877,2883,2888,2893,2898,2903,2908,2914,2919,2925,2931,2937,2942,2948,2953,2959,2965,2971,2977,2983,2989,2995,3001,3006,3012,3018,3024,3029,3035,3041,3047,3052,3058,3064,3069,3075,3081,3087,3093,3099,3104,3110,3115,3121,3126,3131,3137,3142,3148,3154,3159,3164,3169,3175,3180],{"__ignoreMap":327},[2497,2498,2501],"span",{"class":2499,"line":2500},"line",1,[2497,2502,2503],{},"import time\n",[2497,2505,2506],{"class":2499,"line":328},[2497,2507,2508],{},"import threading\n",[2497,2510,2511],{"class":2499,"line":336},[2497,2512,2513],{},"import multiprocessing\n",[2497,2515,2517],{"class":2499,"line":2516},4,[2497,2518,2519],{},"import asyncio\n",[2497,2521,2523],{"class":2499,"line":2522},5,[2497,2524,2525],{},"import sys\n",[2497,2527,2529],{"class":2499,"line":2528},6,[2497,2530,2531],{},"from concurrent.futures import ThreadPoolExecutor\n",[2497,2533,2535],{"class":2499,"line":2534},7,[2497,2536,2537],{},"from functools import wraps\n",[2497,2539,2541],{"class":2499,"line":2540},8,[2497,2542,2543],{"emptyLinePlaceholder":354},"\n",[2497,2545,2547],{"class":2499,"line":2546},9,[2497,2548,2549],{},"# 計時裝飾器，用於測量執行時間\n",[2497,2551,2553],{"class":2499,"line":2552},10,[2497,2554,2555],{},"def timing_decorator(func):\n",[2497,2557,2559],{"class":2499,"line":2558},11,[2497,2560,2561],{},"    @wraps(func)\n",[2497,2563,2565],{"class":2499,"line":2564},12,[2497,2566,2567],{},"    def wrapper(*args, **kwargs):\n",[2497,2569,2571],{"class":2499,"line":2570},13,[2497,2572,2573],{},"        start = time.time()\n",[2497,2575,2577],{"class":2499,"line":2576},14,[2497,2578,2579],{},"        result = func(*args, **kwargs)\n",[2497,2581,2583],{"class":2499,"line":2582},15,[2497,2584,2585],{},"        end = time.time()\n",[2497,2587,2589],{"class":2499,"line":2588},16,[2497,2590,2591],{},"        print(f\"{func.__name__} 執行時間: {end - start:.4f} 秒\")\n",[2497,2593,2595],{"class":2499,"line":2594},17,[2497,2596,2597],{},"        return result\n",[2497,2599,2601],{"class":2499,"line":2600},18,[2497,2602,2603],{},"    return wrapper\n",[2497,2605,2607],{"class":2499,"line":2606},19,[2497,2608,2543],{"emptyLinePlaceholder":354},[2497,2610,2612],{"class":2499,"line":2611},20,[2497,2613,2614],{},"# CPU 密集型任務：計算 Fibonacci 數列\n",[2497,2616,2618],{"class":2499,"line":2617},21,[2497,2619,2620],{},"def fib(n):\n",[2497,2622,2624],{"class":2499,"line":2623},22,[2497,2625,2626],{},"    if n \u003C= 1:\n",[2497,2628,2630],{"class":2499,"line":2629},23,[2497,2631,2632],{},"        return n\n",[2497,2634,2636],{"class":2499,"line":2635},24,[2497,2637,2638],{},"    return fib(n - 1) + fib(n - 2)\n",[2497,2640,2642],{"class":2499,"line":2641},25,[2497,2643,2543],{"emptyLinePlaceholder":354},[2497,2645,2647],{"class":2499,"line":2646},26,[2497,2648,2649],{},"# I/O 綁定任務：模擬網路請求（全局函數）\n",[2497,2651,2653],{"class":2499,"line":2652},27,[2497,2654,2655],{},"def sync_io_task():\n",[2497,2657,2659],{"class":2499,"line":2658},28,[2497,2660,2661],{},"    time.sleep(1)  # 模擬 1 秒的 I/O 延遲\n",[2497,2663,2665],{"class":2499,"line":2664},29,[2497,2666,2543],{"emptyLinePlaceholder":354},[2497,2668,2670],{"class":2499,"line":2669},30,[2497,2671,2672],{},"# 異步 I/O 任務\n",[2497,2674,2676],{"class":2499,"line":2675},31,[2497,2677,2678],{},"async def io_bound_task():\n",[2497,2680,2682],{"class":2499,"line":2681},32,[2497,2683,2684],{},"    await asyncio.sleep(1)  # 模擬 1 秒的 I/O 延遲\n",[2497,2686,2688],{"class":2499,"line":2687},33,[2497,2689,2690],{},"    return \"I/O 任務完成\"\n",[2497,2692,2694],{"class":2499,"line":2693},34,[2497,2695,2543],{"emptyLinePlaceholder":354},[2497,2697,2699],{"class":2499,"line":2698},35,[2497,2700,2701],{},"# 多線程實現\n",[2497,2703,2705],{"class":2499,"line":2704},36,[2497,2706,2707],{},"@timing_decorator\n",[2497,2709,2711],{"class":2499,"line":2710},37,[2497,2712,2713],{},"def run_multithreading(n_tasks, task_type=\"cpu\"):\n",[2497,2715,2717],{"class":2499,"line":2716},38,[2497,2718,2719],{},"    threads = []\n",[2497,2721,2723],{"class":2499,"line":2722},39,[2497,2724,2725],{},"    if task_type == \"cpu\":\n",[2497,2727,2729],{"class":2499,"line":2728},40,[2497,2730,2731],{},"        for _ in range(n_tasks):\n",[2497,2733,2735],{"class":2499,"line":2734},41,[2497,2736,2737],{},"            t = threading.Thread(target=fib, args=(35,))\n",[2497,2739,2741],{"class":2499,"line":2740},42,[2497,2742,2743],{},"            threads.append(t)\n",[2497,2745,2747],{"class":2499,"line":2746},43,[2497,2748,2749],{},"            t.start()\n",[2497,2751,2753],{"class":2499,"line":2752},44,[2497,2754,2755],{},"        for t in threads:\n",[2497,2757,2759],{"class":2499,"line":2758},45,[2497,2760,2761],{},"            t.join()\n",[2497,2763,2765],{"class":2499,"line":2764},46,[2497,2766,2767],{},"    else:  # I/O 任務\n",[2497,2769,2771],{"class":2499,"line":2770},47,[2497,2772,2731],{},[2497,2774,2776],{"class":2499,"line":2775},48,[2497,2777,2778],{},"            t = threading.Thread(target=sync_io_task)\n",[2497,2780,2782],{"class":2499,"line":2781},49,[2497,2783,2743],{},[2497,2785,2787],{"class":2499,"line":2786},50,[2497,2788,2749],{},[2497,2790,2792],{"class":2499,"line":2791},51,[2497,2793,2755],{},[2497,2795,2797],{"class":2499,"line":2796},52,[2497,2798,2761],{},[2497,2800,2802],{"class":2499,"line":2801},53,[2497,2803,2543],{"emptyLinePlaceholder":354},[2497,2805,2807],{"class":2499,"line":2806},54,[2497,2808,2809],{},"# 多進程實現\n",[2497,2811,2813],{"class":2499,"line":2812},55,[2497,2814,2707],{},[2497,2816,2818],{"class":2499,"line":2817},56,[2497,2819,2820],{},"def run_multiprocessing(n_tasks, task_type=\"cpu\"):\n",[2497,2822,2824],{"class":2499,"line":2823},57,[2497,2825,2826],{},"    processes = []\n",[2497,2828,2830],{"class":2499,"line":2829},58,[2497,2831,2725],{},[2497,2833,2835],{"class":2499,"line":2834},59,[2497,2836,2731],{},[2497,2838,2840],{"class":2499,"line":2839},60,[2497,2841,2842],{},"            p = multiprocessing.Process(target=fib, args=(35,))\n",[2497,2844,2846],{"class":2499,"line":2845},61,[2497,2847,2848],{},"            processes.append(p)\n",[2497,2850,2852],{"class":2499,"line":2851},62,[2497,2853,2854],{},"            p.start()\n",[2497,2856,2858],{"class":2499,"line":2857},63,[2497,2859,2860],{},"        for p in processes:\n",[2497,2862,2864],{"class":2499,"line":2863},64,[2497,2865,2866],{},"            p.join()\n",[2497,2868,2870],{"class":2499,"line":2869},65,[2497,2871,2767],{},[2497,2873,2875],{"class":2499,"line":2874},66,[2497,2876,2731],{},[2497,2878,2880],{"class":2499,"line":2879},67,[2497,2881,2882],{},"            p = multiprocessing.Process(target=sync_io_task)\n",[2497,2884,2886],{"class":2499,"line":2885},68,[2497,2887,2848],{},[2497,2889,2891],{"class":2499,"line":2890},69,[2497,2892,2854],{},[2497,2894,2896],{"class":2499,"line":2895},70,[2497,2897,2860],{},[2497,2899,2901],{"class":2499,"line":2900},71,[2497,2902,2866],{},[2497,2904,2906],{"class":2499,"line":2905},72,[2497,2907,2543],{"emptyLinePlaceholder":354},[2497,2909,2911],{"class":2499,"line":2910},73,[2497,2912,2913],{},"# Asyncio 實現\n",[2497,2915,2917],{"class":2499,"line":2916},74,[2497,2918,2707],{},[2497,2920,2922],{"class":2499,"line":2921},75,[2497,2923,2924],{},"async def run_asyncio(n_tasks):\n",[2497,2926,2928],{"class":2499,"line":2927},76,[2497,2929,2930],{},"    tasks = [io_bound_task() for _ in range(n_tasks)]\n",[2497,2932,2934],{"class":2499,"line":2933},77,[2497,2935,2936],{},"    await asyncio.gather(*tasks)\n",[2497,2938,2940],{"class":2499,"line":2939},78,[2497,2941,2543],{"emptyLinePlaceholder":354},[2497,2943,2945],{"class":2499,"line":2944},79,[2497,2946,2947],{},"# 禁用 GIL 的多線程實現（需 Python 3.13 --disable-gil）\n",[2497,2949,2951],{"class":2499,"line":2950},80,[2497,2952,2707],{},[2497,2954,2956],{"class":2499,"line":2955},81,[2497,2957,2958],{},"def run_nogil_multithreading(n_tasks, task_type=\"cpu\"):\n",[2497,2960,2962],{"class":2499,"line":2961},82,[2497,2963,2964],{},"    with ThreadPoolExecutor(max_workers=n_tasks) as executor:\n",[2497,2966,2968],{"class":2499,"line":2967},83,[2497,2969,2970],{},"        if task_type == \"cpu\":\n",[2497,2972,2974],{"class":2499,"line":2973},84,[2497,2975,2976],{},"            futures = [executor.submit(fib, 35) for _ in range(n_tasks)]\n",[2497,2978,2980],{"class":2499,"line":2979},85,[2497,2981,2982],{},"        else:  # I/O 任務\n",[2497,2984,2986],{"class":2499,"line":2985},86,[2497,2987,2988],{},"            futures = [executor.submit(sync_io_task) for _ in range(n_tasks)]\n",[2497,2990,2992],{"class":2499,"line":2991},87,[2497,2993,2994],{},"        for future in futures:\n",[2497,2996,2998],{"class":2499,"line":2997},88,[2497,2999,3000],{},"            future.result()\n",[2497,3002,3004],{"class":2499,"line":3003},89,[2497,3005,2543],{"emptyLinePlaceholder":354},[2497,3007,3009],{"class":2499,"line":3008},90,[2497,3010,3011],{},"# 主程式\n",[2497,3013,3015],{"class":2499,"line":3014},91,[2497,3016,3017],{},"if __name__ == \"__main__\":\n",[2497,3019,3021],{"class":2499,"line":3020},92,[2497,3022,3023],{},"    n_tasks = 4  # 任務數量\n",[2497,3025,3027],{"class":2499,"line":3026},93,[2497,3028,2543],{"emptyLinePlaceholder":354},[2497,3030,3032],{"class":2499,"line":3031},94,[2497,3033,3034],{},"    print(\"=== CPU 密集型任務 (計算 Fibonacci 數列) ===\")\n",[2497,3036,3038],{"class":2499,"line":3037},95,[2497,3039,3040],{},"    print(\"多線程 (Multithreading):\")\n",[2497,3042,3044],{"class":2499,"line":3043},96,[2497,3045,3046],{},"    run_multithreading(n_tasks, task_type=\"cpu\")\n",[2497,3048,3050],{"class":2499,"line":3049},97,[2497,3051,2543],{"emptyLinePlaceholder":354},[2497,3053,3055],{"class":2499,"line":3054},98,[2497,3056,3057],{},"    print(\"\\n多進程 (Multiprocessing):\")\n",[2497,3059,3061],{"class":2499,"line":3060},99,[2497,3062,3063],{},"    run_multiprocessing(n_tasks, task_type=\"cpu\")\n",[2497,3065,3067],{"class":2499,"line":3066},100,[2497,3068,2543],{"emptyLinePlaceholder":354},[2497,3070,3072],{"class":2499,"line":3071},101,[2497,3073,3074],{},"    if sys.version_info >= (3, 13) and hasattr(sys, \"disable_gil\"):\n",[2497,3076,3078],{"class":2499,"line":3077},102,[2497,3079,3080],{},"        print(\"\\n禁用 GIL 的多線程:\")\n",[2497,3082,3084],{"class":2499,"line":3083},103,[2497,3085,3086],{},"        run_nogil_multithreading(n_tasks, task_type=\"cpu\")\n",[2497,3088,3090],{"class":2499,"line":3089},104,[2497,3091,3092],{},"    else:\n",[2497,3094,3096],{"class":2499,"line":3095},105,[2497,3097,3098],{},"        print(\"\\n禁用 GIL 的多線程: 需要 Python 3.13 並啟用 --disable-gil\")\n",[2497,3100,3102],{"class":2499,"line":3101},106,[2497,3103,2543],{"emptyLinePlaceholder":354},[2497,3105,3107],{"class":2499,"line":3106},107,[2497,3108,3109],{},"    print(\"\\n=== I/O 綁定任務 (模擬網路請求) ===\")\n",[2497,3111,3113],{"class":2499,"line":3112},108,[2497,3114,3040],{},[2497,3116,3118],{"class":2499,"line":3117},109,[2497,3119,3120],{},"    run_multithreading(n_tasks, task_type=\"io\")\n",[2497,3122,3124],{"class":2499,"line":3123},110,[2497,3125,2543],{"emptyLinePlaceholder":354},[2497,3127,3129],{"class":2499,"line":3128},111,[2497,3130,3057],{},[2497,3132,3134],{"class":2499,"line":3133},112,[2497,3135,3136],{},"    run_multiprocessing(n_tasks, task_type=\"io\")\n",[2497,3138,3140],{"class":2499,"line":3139},113,[2497,3141,2543],{"emptyLinePlaceholder":354},[2497,3143,3145],{"class":2499,"line":3144},114,[2497,3146,3147],{},"    print(\"\\nAsyncio:\")\n",[2497,3149,3151],{"class":2499,"line":3150},115,[2497,3152,3153],{},"    asyncio.run(run_asyncio(n_tasks))\n",[2497,3155,3157],{"class":2499,"line":3156},116,[2497,3158,2543],{"emptyLinePlaceholder":354},[2497,3160,3162],{"class":2499,"line":3161},117,[2497,3163,3074],{},[2497,3165,3167],{"class":2499,"line":3166},118,[2497,3168,3080],{},[2497,3170,3172],{"class":2499,"line":3171},119,[2497,3173,3174],{},"        run_nogil_multithreading(n_tasks, task_type=\"io\")\n",[2497,3176,3178],{"class":2499,"line":3177},120,[2497,3179,3092],{},[2497,3181,3183],{"class":2499,"line":3182},121,[2497,3184,3098],{},[24,3186,3187],{"id":3187},"執行結果分析",[1286,3189,3193],{"className":3190,"code":3191,"language":3192,"meta":327,"style":327},"language-bash shiki shiki-themes github-light github-dark","$ uv run --no-project --python 3.13.5+freethreaded test.py\n\n=== CPU 密集型任務 (計算 Fibonacci 數列) ===\n多線程 (Multithreading):\nrun_multithreading 執行時間: 0.8912 秒\n\n多進程 (Multiprocessing):\nrun_multiprocessing 執行時間: 1.0846 秒\n\n禁用 GIL 的多線程:\nGIL 狀態: 禁用\nrun_nogil_multithreading 執行時間: 0.8713 秒\n\n=== I/O 綁定任務 (模擬網路請求) ===\n多線程 (Multithreading):\nrun_multithreading 執行時間: 1.0072 秒\n\n多進程 (Multiprocessing):\nrun_multiprocessing 執行時間: 1.1046 秒\n\nAsyncio:\nrun_asyncio 執行時間: 0.0000 秒\n\n禁用 GIL 的多線程:\nGIL 狀態: 禁用\nrun_nogil_multithreading 執行時間: 1.0093 秒\n","bash",[191,3194,3195,3221,3225,3249,3257,3271,3275,3283,3295,3299,3310,3321,3333,3337,3350,3356,3367,3371,3377,3388,3392,3397,3409,3413,3421,3429],{"__ignoreMap":327},[2497,3196,3197,3201,3205,3208,3212,3215,3218],{"class":2499,"line":2500},[2497,3198,3200],{"class":3199},"sScJk","$",[2497,3202,3204],{"class":3203},"sZZnC"," uv",[2497,3206,3207],{"class":3203}," run",[2497,3209,3211],{"class":3210},"sj4cs"," --no-project",[2497,3213,3214],{"class":3210}," --python",[2497,3216,3217],{"class":3203}," 3.13.5+freethreaded",[2497,3219,3220],{"class":3203}," test.py\n",[2497,3222,3223],{"class":2499,"line":328},[2497,3224,2543],{"emptyLinePlaceholder":354},[2497,3226,3227,3230,3233,3236,3240,3243,3246],{"class":2499,"line":336},[2497,3228,3229],{"class":3203},"===",[2497,3231,3232],{"class":3203}," CPU",[2497,3234,3235],{"class":3203}," 密集型任務",[2497,3237,3239],{"class":3238},"sVt8B"," (計算 ",[2497,3241,3242],{"class":3203},"Fibonacci",[2497,3244,3245],{"class":3203}," 數列",[2497,3247,3248],{"class":3238},") ===\n",[2497,3250,3251,3254],{"class":2499,"line":2516},[2497,3252,3253],{"class":3199},"多線程",[2497,3255,3256],{"class":3238}," (Multithreading):\n",[2497,3258,3259,3262,3265,3268],{"class":2499,"line":2522},[2497,3260,3261],{"class":3199},"run_multithreading",[2497,3263,3264],{"class":3203}," 執行時間:",[2497,3266,3267],{"class":3210}," 0.8912",[2497,3269,3270],{"class":3203}," 秒\n",[2497,3272,3273],{"class":2499,"line":2528},[2497,3274,2543],{"emptyLinePlaceholder":354},[2497,3276,3277,3280],{"class":2499,"line":2534},[2497,3278,3279],{"class":3199},"多進程",[2497,3281,3282],{"class":3238}," (Multiprocessing):\n",[2497,3284,3285,3288,3290,3293],{"class":2499,"line":2540},[2497,3286,3287],{"class":3199},"run_multiprocessing",[2497,3289,3264],{"class":3203},[2497,3291,3292],{"class":3210}," 1.0846",[2497,3294,3270],{"class":3203},[2497,3296,3297],{"class":2499,"line":2546},[2497,3298,2543],{"emptyLinePlaceholder":354},[2497,3300,3301,3304,3307],{"class":2499,"line":2552},[2497,3302,3303],{"class":3199},"禁用",[2497,3305,3306],{"class":3203}," GIL",[2497,3308,3309],{"class":3203}," 的多線程:\n",[2497,3311,3312,3315,3318],{"class":2499,"line":2558},[2497,3313,3314],{"class":3199},"GIL",[2497,3316,3317],{"class":3203}," 狀態:",[2497,3319,3320],{"class":3203}," 禁用\n",[2497,3322,3323,3326,3328,3331],{"class":2499,"line":2564},[2497,3324,3325],{"class":3199},"run_nogil_multithreading",[2497,3327,3264],{"class":3203},[2497,3329,3330],{"class":3210}," 0.8713",[2497,3332,3270],{"class":3203},[2497,3334,3335],{"class":2499,"line":2570},[2497,3336,2543],{"emptyLinePlaceholder":354},[2497,3338,3339,3341,3344,3347],{"class":2499,"line":2576},[2497,3340,3229],{"class":3203},[2497,3342,3343],{"class":3203}," I/O",[2497,3345,3346],{"class":3203}," 綁定任務",[2497,3348,3349],{"class":3238}," (模擬網路請求) ===\n",[2497,3351,3352,3354],{"class":2499,"line":2582},[2497,3353,3253],{"class":3199},[2497,3355,3256],{"class":3238},[2497,3357,3358,3360,3362,3365],{"class":2499,"line":2588},[2497,3359,3261],{"class":3199},[2497,3361,3264],{"class":3203},[2497,3363,3364],{"class":3210}," 1.0072",[2497,3366,3270],{"class":3203},[2497,3368,3369],{"class":2499,"line":2594},[2497,3370,2543],{"emptyLinePlaceholder":354},[2497,3372,3373,3375],{"class":2499,"line":2600},[2497,3374,3279],{"class":3199},[2497,3376,3282],{"class":3238},[2497,3378,3379,3381,3383,3386],{"class":2499,"line":2606},[2497,3380,3287],{"class":3199},[2497,3382,3264],{"class":3203},[2497,3384,3385],{"class":3210}," 1.1046",[2497,3387,3270],{"class":3203},[2497,3389,3390],{"class":2499,"line":2611},[2497,3391,2543],{"emptyLinePlaceholder":354},[2497,3393,3394],{"class":2499,"line":2617},[2497,3395,3396],{"class":3199},"Asyncio:\n",[2497,3398,3399,3402,3404,3407],{"class":2499,"line":2623},[2497,3400,3401],{"class":3199},"run_asyncio",[2497,3403,3264],{"class":3203},[2497,3405,3406],{"class":3210}," 0.0000",[2497,3408,3270],{"class":3203},[2497,3410,3411],{"class":2499,"line":2629},[2497,3412,2543],{"emptyLinePlaceholder":354},[2497,3414,3415,3417,3419],{"class":2499,"line":2635},[2497,3416,3303],{"class":3199},[2497,3418,3306],{"class":3203},[2497,3420,3309],{"class":3203},[2497,3422,3423,3425,3427],{"class":2499,"line":2641},[2497,3424,3314],{"class":3199},[2497,3426,3317],{"class":3203},[2497,3428,3320],{"class":3203},[2497,3430,3431,3433,3435,3438],{"class":2499,"line":2646},[2497,3432,3325],{"class":3199},[2497,3434,3264],{"class":3203},[2497,3436,3437],{"class":3210}," 1.0093",[2497,3439,3270],{"class":3203},[116,3441,3443],{"id":3442},"cpu-密集型任務計算-fibonacci-數列","CPU 密集型任務（計算 Fibonacci 數列）",[12,3445,3446,3447],{},"執行時間排序：",[16,3448,3449],{},"GIL Disabled (0.8713 秒) \u003C 多線程 (0.8912 秒) \u003C 多進程 (1.0846 秒)",[145,3451,3452,3457,3462],{},[72,3453,3454,3456],{},[16,3455,3253],{},"：受 GIL 限制，執行速度與單 CPU 執行差不多",[72,3458,3459,3461],{},[16,3460,3279],{},"：運用多個 CPU 核心加速",[72,3463,3464,3467],{},[16,3465,3466],{},"GIL Disabled","：運用多個 CPU 核心加速，且避免了多進程管理的額外開銷",[116,3469,3471],{"id":3470},"io-綁定任務模擬網路請求","I/O 綁定任務（模擬網路請求）",[12,3473,3446,3474],{},[16,3475,3476],{},"Asyncio (0.0000 秒) \u003C\u003C 多線程 (1.0072 秒) ≈ GIL Disabled (1.0093 秒) ≈ 多進程 (1.1046 秒)",[145,3478,3479,3485],{},[72,3480,3481,3484],{},[16,3482,3483],{},"多線程、多進程、GIL Disabled","：三者效能差不多",[72,3486,3487,3490,3491,3494],{},[16,3488,3489],{},"Asyncio","：效能最好，因為測試任務使用 ",[191,3492,3493],{},"await asyncio.sleep","，基本上不會有運行時間開銷",[24,3496,3497],{"id":3497},"實際應用場景",[12,3499,3500],{},[16,3501,3502],{},"首先需要考慮業務場景，做這個優化加速是否有需要？如果加速沒有很重要的話就是一個 Nice to have 的優化。",[116,3504,3506],{"id":3505},"不建議使用-disabled-gil","不建議使用 Disabled GIL",[12,3508,3509],{},"目前 Disabled GIL 還在測試階段，許多套件都還沒完全支援，也需要自己處理變數管理等問題，目前不考慮使用。",[116,3511,3513],{"id":3512},"適合使用-multiprocessing-的場景","適合使用 Multiprocessing 的場景",[12,3515,3516],{},"非 AI 的大量運算需求，可以使用 Multiprocessing 來加速：",[145,3518,3519,3525],{},[72,3520,3521,3524],{},[16,3522,3523],{},"售電平台的最佳參數求取","：目前策略使用網格搜索，可以切分網格分配給 CPU 執行",[72,3526,3527,3530],{},[16,3528,3529],{},"EV 管理平台","：需要定時計算電站使用率",[116,3532,3534],{"id":3533},"適合使用-asyncio-的場景","適合使用 Asyncio 的場景",[12,3536,3537],{},"網路的大量需求，可以使用 Asyncio 來加速：",[145,3539,3540,3546,3564,3574],{},[72,3541,3542,3545],{},[16,3543,3544],{},"MQTT / OCPP","：這類 pub/sub 協議",[72,3547,3548,2297,3551],{},[16,3549,3550],{},"爬蟲或大量 Third-party API 請求",[145,3552,3553],{},[72,3554,3555,3556,3559,3560,3563],{},"使用 ",[191,3557,3558],{},"aiohttp"," 替換 ",[191,3561,3562],{},"requests"," 模組",[72,3565,3566,2297,3569],{},[16,3567,3568],{},"Database 異步操作",[145,3570,3571],{},[72,3572,3573],{},"pymongo 遷移到 async client (≥ v4.13)",[72,3575,3576,2297,3579],{},[16,3577,3578],{},"Web Framework",[145,3580,3581],{},[72,3582,3583],{},"Flask 遷移到 FastAPI",[24,3585,1060],{"id":1060},[145,3587,3588,3595,3602],{},[72,3589,3590],{},[42,3591,3594],{"href":3592,"rel":3593},"https://www.youtube.com/watch?v=brYsDi-JajI",[46],"【python】asyncio的理解與入門，搞不明白協程？看這個視頻就夠了",[72,3596,3597],{},[42,3598,3601],{"href":3599,"rel":3600},"https://www.youtube.com/watch?v=K0BjgYZbgfE&t=94s",[46],"【python】await機制詳解。再來個硬核內容，把並行和依賴背後的原理全給你講明白",[72,3603,3604],{},[42,3605,3608],{"href":3606,"rel":3607},"https://pymongo.readthedocs.io/en/stable/async-tutorial.html",[46],"Async Tutorial - PyMongo 4.14.1 documentation",[3610,3611,3612],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":327,"searchDepth":328,"depth":328,"links":3614},[3615,3616,3622,3623,3624,3628,3633],{"id":2181,"depth":328,"text":2182},{"id":2191,"depth":328,"text":2191,"children":3617},[3618,3619,3620,3621],{"id":2194,"depth":336,"text":2195},{"id":2245,"depth":336,"text":2246},{"id":2284,"depth":336,"text":2285},{"id":2321,"depth":336,"text":2322},{"id":2343,"depth":328,"text":2343},{"id":2488,"depth":328,"text":2488},{"id":3187,"depth":328,"text":3187,"children":3625},[3626,3627],{"id":3442,"depth":336,"text":3443},{"id":3470,"depth":336,"text":3471},{"id":3497,"depth":328,"text":3497,"children":3629},[3630,3631,3632],{"id":3505,"depth":336,"text":3506},{"id":3512,"depth":336,"text":3513},{"id":3533,"depth":336,"text":3534},{"id":1060,"depth":328,"text":1060},"2025-10-24","介紹 Python 並發處理方法，包括多線程、多進程、asyncio 以及其實務應用。",{},"/blog/concurrency",{"title":2176,"description":3635},"blog/concurrency",[3641,3642,3643,3489],"Python","Concurrency","Threading","1DEoTJ_oFj-_n1U7PDKbn7wPsTmsBUK-5vAp6nZ_s0E",{"id":3646,"title":3647,"author":7,"body":3648,"category":347,"date":3634,"description":4032,"draft":350,"extension":351,"image":327,"meta":4033,"navigation":354,"path":4034,"seo":4035,"stem":4036,"tags":4037,"__hash__":4038},"blog/blog/python-gil-introduction.md","Python GIL 介紹",{"type":9,"value":3649,"toc":4020},[3650,3654,3659,3662,3682,3685,3689,3694,3698,3718,3722,3725,3800,3804,3807,3875,3879,3882,3906,3909,3929,3932,3988,3990],[24,3651,3653],{"id":3652},"什麼是-gil","什麼是 GIL？",[99,3655,3656],{},[12,3657,3658],{},"💡 GIL 是 CPython 解釋器中的一個鎖，確保在任何時刻只有一個線程可以執行 Python 字節碼。",[12,3660,3661],{},"這意味著即使有多個線程，Python 程式也無法真正利用多核心 CPU 實現並行（parallelism），僅能實現並發（concurrency）的假象。GIL 為 Python 提供了以下好處：",[145,3663,3664,3670,3676],{},[72,3665,3666,3669],{},[16,3667,3668],{},"簡化種族條件（Race Conditions）","：防止Multi thread同時修改共享資料，降低記憶體損壞風險。",[72,3671,3672,3675],{},[16,3673,3674],{},"簡化垃圾回收（Garbage Collection）","：GIL 使記憶體管理更簡單，無需複雜的鎖機制。",[72,3677,3678,3681],{},[16,3679,3680],{},"歷史背景","：Python 於 1991 年設計時，多數電腦僅有單核心 CPU，GIL 是當時簡化實現的合理選擇。",[12,3683,3684],{},"然而，GIL 的缺點顯而易見：它限制了多核心 CPU 的利用率，使 Python 在 CPU 密集型任務（如數學計算、資料解析、AI 模型訓練）上的效能不如預期。",[24,3686,3688],{"id":3687},"為什麼移除-gilpep-703-python-313","為什麼移除 GIL？（PEP 703, Python 3.13）",[99,3690,3691],{},[12,3692,3693],{},"💡 隨著硬體技術的進步（多核心 CPU 普及）和 Python 社群對高效能的需求，GIL 逐漸成為瓶頸。PEP 703（Python 3.13）提出使 GIL 可選，允許程式在編譯時或運行時禁用 GIL，讓Multi thread直接利用操作系統的線程調度器，實現真正的並行。",[116,3695,3697],{"id":3696},"移除-gil-的動機","移除 GIL 的動機",[145,3699,3700,3706,3712],{},[72,3701,3702,3705],{},[16,3703,3704],{},"社群需求","：開發者希望 Python 能更快，並充分利用現代多核心硬體。",[72,3707,3708,3711],{},[16,3709,3710],{},"效能提升","：禁用 GIL 後，某些 CPU 密集型任務（如桶排序、分形生成）可顯著加速。",[72,3713,3714,3717],{},[16,3715,3716],{},"競爭壓力","：其他語言（如 Go、Rust）支援真正的並行，Python 需跟上時代。",[116,3719,3721],{"id":3720},"移除-gil-的挑戰","移除 GIL 的挑戰",[12,3723,3724],{},"移除 GIL 並非易事，因為 Python 的許多核心機制依賴 GIL 的保護。以下是主要挑戰：",[69,3726,3727,3759,3771,3788],{},[72,3728,3729,3732],{},[16,3730,3731],{},"引用計數（Reference Counting）",[145,3733,3734,3740],{},[72,3735,3736,3739],{},[16,3737,3738],{},"問題","：傳統的非原子引用計數（non-atomic reference counting）不具線程安全，可能導致種族條件。例如，refcount++ 分三步（讀、加、寫），可能被其他線程中斷。",[72,3741,3742,2297,3745],{},[16,3743,3744],{},"解決方案",[145,3746,3747,3753],{},[72,3748,3749,3752],{},[16,3750,3751],{},"原子引用計數","：線程安全，但速度慢 10x-100x。",[72,3754,3755,3758],{},[16,3756,3757],{},"偏向引用計數（Biased Reference Counting）","：檢查引用是否僅屬於單一線程，若是則使用快速的非原子計數，否則使用原子計數。",[72,3760,3761,3764],{},[16,3762,3763],{},"垃圾回收（Garbage Collection）",[145,3765,3766],{},[72,3767,3768,3770],{},[16,3769,3738],{},"：傳統垃圾回收依賴 GIL 保護，需改用延遲引用計數（deferred reference counting）來處理循環引用。",[72,3772,3773,3776],{},[16,3774,3775],{},"記憶體分配",[145,3777,3778,3783],{},[72,3779,3780,3782],{},[16,3781,3738],{},"：現有記憶體分配器假設 GIL 保護，不具線程安全。",[72,3784,3785,3787],{},[16,3786,3744],{},"：開發新的線程安全記憶體分配器，優化列表和字典的快速讀取。",[72,3789,3790,3793],{},[16,3791,3792],{},"兼容性",[145,3794,3795],{},[72,3796,3797,3799],{},[16,3798,3738],{},"：許多 C-API 擴展（如 NumPy、Pandas）假設 GIL 存在，需重新編譯以支援無 GIL 環境。",[24,3801,3803],{"id":3802},"gil-與禁用-gil-的比較","GIL 與禁用 GIL 的比較",[12,3805,3806],{},"以下表格比較了 GIL 和禁用 GIL 的特點：",[399,3808,3809,3821],{},[402,3810,3811],{},[405,3812,3813,3815,3818],{},[408,3814,2352],{},[408,3816,3817],{},"啟用 GIL",[408,3819,3820],{},"禁用 GIL",[421,3822,3823,3836,3849,3862],{},[405,3824,3825,3830,3833],{},[426,3826,3827],{},[16,3828,3829],{},"並行性",[426,3831,3832],{},"非真正的並行，僅一個線程執行",[426,3834,3835],{},"真正的Multi thread並行，利用多核心",[405,3837,3838,3843,3846],{},[426,3839,3840],{},[16,3841,3842],{},"資料共享",[426,3844,3845],{},"簡單，無需額外鎖",[426,3847,3848],{},"需小心處理種族條件（如使用 threading.Lock）",[405,3850,3851,3856,3859],{},[426,3852,3853],{},[16,3854,3855],{},"效能",[426,3857,3858],{},"受限於 GIL，CPU 綁定任務慢",[426,3860,3861],{},"視程式而定，可能顯著提升（如桶排序、分形生成）",[405,3863,3864,3869,3872],{},[426,3865,3866],{},[16,3867,3868],{},"擴展相容性",[426,3870,3871],{},"廣泛支援",[426,3873,3874],{},"需檢查擴展版本（如 Cython、NumPy）",[24,3876,3878],{"id":3877},"禁用-gil-的推薦場景","禁用 GIL 的推薦場景",[12,3880,3881],{},"禁用 GIL 在以下場景中特別有用：",[145,3883,3884,3890,3895,3900],{},[72,3885,3886,3889],{},[16,3887,3888],{},"ETL 處理","：資料提取、轉換和載入需要大量計算。",[72,3891,3892,3894],{},[16,3893,2268],{},"：如濾波、轉換等 CPU 密集型操作。",[72,3896,3897,3899],{},[16,3898,2271],{},"：處理大量資料並進行模式匹配。",[72,3901,3902,3905],{},[16,3903,3904],{},"即時分析","：需要快速響應的計算任務。",[24,3907,3908],{"id":3908},"注意事項",[145,3910,3911,3917,3923],{},[72,3912,3913,3916],{},[16,3914,3915],{},"Race Condition","：禁用 GIL 後，Multi thread可能同時存取共享資料，導致記憶體損壞。建議使用 threading.Lock 或其他同步機制。",[72,3918,3919,3922],{},[16,3920,3921],{},"擴展模組相容性","：確認使用的庫（如 NumPy、Pandas、PyTorch）支援無 GIL 環境。",[72,3924,3925,3928],{},[16,3926,3927],{},"效能不確定性","：禁用 GIL 不保證所有程式都變快，需針對具體任務進行基準測試。",[24,3930,3931],{"id":3931},"常見誤解與解答",[69,3933,3934,3948,3958,3968,3978],{},[72,3935,3936,3939,3940],{},[16,3937,3938],{},"並發(Concurrency)與並行(Parallelism)的區別","：",[145,3941,3942,3945],{},[72,3943,3944],{},"並發是多任務「看似」同時進行，實際上可能是切換執行。",[72,3946,3947],{},"並行是多任務在多核心上真正同時執行。",[72,3949,3950,3953],{},[16,3951,3952],{},"為什麼 Python 慢？",[145,3954,3955],{},[72,3956,3957],{},"GIL 限制了Multi thread的並行性，導致 CPU 綁定任務無法充分利用多核心。",[72,3959,3960,3963],{},[16,3961,3962],{},"禁用 GIL 如何管理 CPU 使用？",[145,3964,3965],{},[72,3966,3967],{},"透過操作系統的線程調度器，讓Multi thread在多核心上並行執行。",[72,3969,3970,3973],{},[16,3971,3972],{},"禁用 GIL 是否總是更快？",[145,3974,3975],{},[72,3976,3977],{},"不一定，效能提升取決於任務類型和程式設計。某些任務（如 I/O 綁定）可能無明顯改善。",[72,3979,3980,3983],{},[16,3981,3982],{},"簡單資料共享是什麼？",[145,3984,3985],{},[72,3986,3987],{},"指線程間直接存取記憶體的能力，而Multi process需要管道或共享記憶體，較為複雜。",[24,3989,1060],{"id":1060},[145,3991,3992,3999,4006,4013],{},[72,3993,3994],{},[42,3995,3998],{"href":3996,"rel":3997},"https://peps.python.org/pep-0703/",[46],"PEP 703 – Making the Global Interpreter Lock Optional in CPython",[72,4000,4001],{},[42,4002,4005],{"href":4003,"rel":4004},"https://tw.pycon.org/2025/zh-hant/conference/keynotes#Donghee_Na",[46],"PyCon TW 2025 主題演講",[72,4007,4008],{},[42,4009,4012],{"href":4010,"rel":4011},"https://tw.pycon.org/2025/zh-hant/conference/talk/352",[46],"PyCon TW 2025 - Talk 352",[72,4014,4015],{},[42,4016,4019],{"href":4017,"rel":4018},"https://tw.pycon.org/2025/zh-hant/conference/talk/335",[46],"PyCon TW 2025 - Talk 335",{"title":327,"searchDepth":328,"depth":328,"links":4021},[4022,4023,4027,4028,4029,4030,4031],{"id":3652,"depth":328,"text":3653},{"id":3687,"depth":328,"text":3688,"children":4024},[4025,4026],{"id":3696,"depth":336,"text":3697},{"id":3720,"depth":336,"text":3721},{"id":3802,"depth":328,"text":3803},{"id":3877,"depth":328,"text":3878},{"id":3908,"depth":328,"text":3908},{"id":3931,"depth":328,"text":3931},{"id":1060,"depth":328,"text":1060},"Python 全域直譯器鎖（GIL）介紹",{},"/blog/python-gil-introduction",{"title":3647,"description":4032},"blog/python-gil-introduction",[3641,3314,3643,3642],"HEyyMIX4ERxJ70UGu2zKfrzTPLf2cYOCJfUly7xneRo",{"id":4040,"title":4041,"author":7,"body":4042,"category":347,"date":3634,"description":5361,"draft":350,"extension":351,"image":327,"meta":5362,"navigation":354,"path":5363,"seo":5364,"stem":5365,"tags":5366,"__hash__":5368},"blog/blog/uv-and-ruff.md","uv and Ruff: 最佳的 Python 工具鏈",{"type":9,"value":4043,"toc":5343},[4044,4046,4051,4054,4062,4066,4096,4100,4107,4129,4136,4147,4151,4379,4382,4387,4393,4398,4404,4406,4409,4417,4421,4443,4445,4449,4496,4498,4501,4504,4529,4532,4536,4574,4578,4679,4683,4758,4765,4772,5050,5054,5074,5078,5089,5093,5152,5156,5181,5185,5313,5315,5317,5340],[1227,4045,2182],{"id":2181},[99,4047,4048],{},[12,4049,4050],{},"若遇過 format on save 很慢 / 裝環境很麻煩 / 套件衝突，那這篇文章可能對你有用",[1227,4052,4053],{"id":4053},"uv",[99,4055,4056,4059],{},[12,4057,4058],{},"uv 是一個極速的 Python 套件管理器和專案管理工具，被設計為 pip、pip-tools、pipx、poetry、pyenv、virtualenv 等工具的統一替代方案。",[12,4060,4061],{},"uv 的核心理念是提供一個快速、可靠且易用的 Python 工具鏈。",[24,4063,4065],{"id":4064},"uv-的主要特點","uv 的主要特點",[145,4067,4068,4073,4086,4093],{},[72,4069,4070],{},[16,4071,4072],{},"速度極快",[72,4074,4075,4078],{},[16,4076,4077],{},"統一的工作流程",[145,4079,4080,4083],{},[72,4081,4082],{},"整合了套件安裝、虛擬環境管理、專案初始化等功能",[72,4084,4085],{},"一個工具解決多個需求，簡化開發工作流程",[72,4087,4088,4089,4092],{},"確保所有環境部署時依賴相同 (",[191,4090,4091],{},"uv.lock",")",[72,4094,4095],{},"方便切換 python 版本，以及在設定檔中鎖定 python 版本",[24,4097,4099],{"id":4098},"requirementstxt-vs-pyprojecttoml","requirements.txt vs pyproject.toml",[99,4101,4102],{},[12,4103,4104],{},[16,4105,4106],{},"pyproject.toml 是官方標準",[145,4108,4109,4112,4115,4118,4121],{},[72,4110,4111],{},"PEP 518、PEP 621 等官方標準定義的專案配置格式",[72,4113,4114],{},"Python 生態系統的統一標準，未來發展方向",[72,4116,4117],{},"被所有現代工具（pip、uv、poetry、setuptools）支援",[72,4119,4120],{},"可以根據開發、打包、部署等環境分別配置",[72,4122,4123,4124],{},"有 support 的工具都可以統一配置\n",[145,4125,4126],{},[72,4127,4128],{},"例如等等要介紹的 ruff",[99,4130,4131],{},[12,4132,4133],{},[16,4134,4135],{},"requirements.txt 是非正式慣例",[145,4137,4138,4141,4144],{},[72,4139,4140],{},"只是社群約定俗成的做法",[72,4142,4143],{},"沒有正式規範，格式相對簡陋",[72,4145,4146],{},"逐漸被新工具取代",[116,4148,4150],{"id":4149},"範例-pyprojecttoml","範例 pyproject.toml",[1286,4152,4156],{"className":4153,"code":4154,"language":4155,"meta":327,"style":327},"language-toml shiki shiki-themes github-light github-dark","[project]\nname = \"my-awesome-project\"\nversion = \"1.0.0\"\ndescription = \"A fantastic Python project\"\nauthors = [{name = \"Your Name\", email = \"you@example.com\"}]\nlicense = {text = \"MIT\"}\nreadme = \"README.md\"\nkeywords = [\"python\", \"awesome\"]\nclassifiers = [\n    \"Development Status :: 4 - Beta\",\n    \"Programming Language :: Python :: 3.11\",\n]\n\n# 依賴管理\ndependencies = [\n    \"requests>=2.28.0\",\n    \"click>=8.0.0\",\n]\n\n# 開發依賴\n[project.optional-dependencies]\ndev = [\n    \"pytest>=7.0.0\",\n    \"ruff>=0.1.0\",\n    \"mypy>=1.0.0\",\n]\ntest = [\n    \"pytest-cov>=4.0.0\",\n    \"pytest-mock>=3.10.0\",\n]\n\n# 工具配置\n[tool.ruff]\nline-length = 88\ntarget-version = \"py311\"\n\n[tool.ruff.lint]\nselect = [\"E\", \"F\", \"UP\", \"B\", \"SIM\", \"I\"]\n\n[tool.pytest.ini_options]\ntestpaths = [\"tests\"]\npython_files = [\"test_*.py\"]\n\n[build-system]\nrequires = [\"setuptools>=61.0\"]\nbuild-backend = \"setuptools.build_meta\"\n","toml",[191,4157,4158,4163,4168,4173,4178,4183,4188,4193,4198,4203,4208,4213,4218,4222,4227,4232,4237,4242,4246,4250,4255,4260,4265,4270,4275,4280,4284,4289,4294,4299,4303,4307,4312,4317,4322,4327,4331,4336,4341,4345,4350,4355,4360,4364,4369,4374],{"__ignoreMap":327},[2497,4159,4160],{"class":2499,"line":2500},[2497,4161,4162],{},"[project]\n",[2497,4164,4165],{"class":2499,"line":328},[2497,4166,4167],{},"name = \"my-awesome-project\"\n",[2497,4169,4170],{"class":2499,"line":336},[2497,4171,4172],{},"version = \"1.0.0\"\n",[2497,4174,4175],{"class":2499,"line":2516},[2497,4176,4177],{},"description = \"A fantastic Python project\"\n",[2497,4179,4180],{"class":2499,"line":2522},[2497,4181,4182],{},"authors = [{name = \"Your Name\", email = \"you@example.com\"}]\n",[2497,4184,4185],{"class":2499,"line":2528},[2497,4186,4187],{},"license = {text = \"MIT\"}\n",[2497,4189,4190],{"class":2499,"line":2534},[2497,4191,4192],{},"readme = \"README.md\"\n",[2497,4194,4195],{"class":2499,"line":2540},[2497,4196,4197],{},"keywords = [\"python\", \"awesome\"]\n",[2497,4199,4200],{"class":2499,"line":2546},[2497,4201,4202],{},"classifiers = [\n",[2497,4204,4205],{"class":2499,"line":2552},[2497,4206,4207],{},"    \"Development Status :: 4 - Beta\",\n",[2497,4209,4210],{"class":2499,"line":2558},[2497,4211,4212],{},"    \"Programming Language :: Python :: 3.11\",\n",[2497,4214,4215],{"class":2499,"line":2564},[2497,4216,4217],{},"]\n",[2497,4219,4220],{"class":2499,"line":2570},[2497,4221,2543],{"emptyLinePlaceholder":354},[2497,4223,4224],{"class":2499,"line":2576},[2497,4225,4226],{},"# 依賴管理\n",[2497,4228,4229],{"class":2499,"line":2582},[2497,4230,4231],{},"dependencies = [\n",[2497,4233,4234],{"class":2499,"line":2588},[2497,4235,4236],{},"    \"requests>=2.28.0\",\n",[2497,4238,4239],{"class":2499,"line":2594},[2497,4240,4241],{},"    \"click>=8.0.0\",\n",[2497,4243,4244],{"class":2499,"line":2600},[2497,4245,4217],{},[2497,4247,4248],{"class":2499,"line":2606},[2497,4249,2543],{"emptyLinePlaceholder":354},[2497,4251,4252],{"class":2499,"line":2611},[2497,4253,4254],{},"# 開發依賴\n",[2497,4256,4257],{"class":2499,"line":2617},[2497,4258,4259],{},"[project.optional-dependencies]\n",[2497,4261,4262],{"class":2499,"line":2623},[2497,4263,4264],{},"dev = [\n",[2497,4266,4267],{"class":2499,"line":2629},[2497,4268,4269],{},"    \"pytest>=7.0.0\",\n",[2497,4271,4272],{"class":2499,"line":2635},[2497,4273,4274],{},"    \"ruff>=0.1.0\",\n",[2497,4276,4277],{"class":2499,"line":2641},[2497,4278,4279],{},"    \"mypy>=1.0.0\",\n",[2497,4281,4282],{"class":2499,"line":2646},[2497,4283,4217],{},[2497,4285,4286],{"class":2499,"line":2652},[2497,4287,4288],{},"test = [\n",[2497,4290,4291],{"class":2499,"line":2658},[2497,4292,4293],{},"    \"pytest-cov>=4.0.0\",\n",[2497,4295,4296],{"class":2499,"line":2664},[2497,4297,4298],{},"    \"pytest-mock>=3.10.0\",\n",[2497,4300,4301],{"class":2499,"line":2669},[2497,4302,4217],{},[2497,4304,4305],{"class":2499,"line":2675},[2497,4306,2543],{"emptyLinePlaceholder":354},[2497,4308,4309],{"class":2499,"line":2681},[2497,4310,4311],{},"# 工具配置\n",[2497,4313,4314],{"class":2499,"line":2687},[2497,4315,4316],{},"[tool.ruff]\n",[2497,4318,4319],{"class":2499,"line":2693},[2497,4320,4321],{},"line-length = 88\n",[2497,4323,4324],{"class":2499,"line":2698},[2497,4325,4326],{},"target-version = \"py311\"\n",[2497,4328,4329],{"class":2499,"line":2704},[2497,4330,2543],{"emptyLinePlaceholder":354},[2497,4332,4333],{"class":2499,"line":2710},[2497,4334,4335],{},"[tool.ruff.lint]\n",[2497,4337,4338],{"class":2499,"line":2716},[2497,4339,4340],{},"select = [\"E\", \"F\", \"UP\", \"B\", \"SIM\", \"I\"]\n",[2497,4342,4343],{"class":2499,"line":2722},[2497,4344,2543],{"emptyLinePlaceholder":354},[2497,4346,4347],{"class":2499,"line":2728},[2497,4348,4349],{},"[tool.pytest.ini_options]\n",[2497,4351,4352],{"class":2499,"line":2734},[2497,4353,4354],{},"testpaths = [\"tests\"]\n",[2497,4356,4357],{"class":2499,"line":2740},[2497,4358,4359],{},"python_files = [\"test_*.py\"]\n",[2497,4361,4362],{"class":2499,"line":2746},[2497,4363,2543],{"emptyLinePlaceholder":354},[2497,4365,4366],{"class":2499,"line":2752},[2497,4367,4368],{},"[build-system]\n",[2497,4370,4371],{"class":2499,"line":2758},[2497,4372,4373],{},"requires = [\"setuptools>=61.0\"]\n",[2497,4375,4376],{"class":2499,"line":2764},[2497,4377,4378],{},"build-backend = \"setuptools.build_meta\"\n",[116,4380,4381],{"id":4381},"專案檔案架構比較",[12,4383,4384],{},[16,4385,4386],{},"使用 requirements.txt 的專案結構",[1286,4388,4391],{"className":4389,"code":4390,"language":1291},[1289],"my-project/\n├── requirements.txt\n├── requirements-dev.txt\n├── requirements-test.txt\n├── setup.py        # setuptools 配置\n├── setup.cfg\n├── pytest.ini      # pytest 配置\n├── mypy.ini        # mypy 配置\n├── .coveragerc     # coverage 配置\n└── pyproject.toml  # 只給 ruff 用\n",[191,4392,4390],{"__ignoreMap":327},[12,4394,4395],{},[16,4396,4397],{},"使用 pyproject.toml 的專案結構",[1286,4399,4402],{"className":4400,"code":4401,"language":1291},[1289],"my-project/\n├── pyproject.toml  # 所有配置都在這裡\n├── uv.lock         # 版本鎖定檔案\n└── src/\n",[191,4403,4401],{"__ignoreMap":327},[1622,4405],{},[1227,4407,4408],{"id":4408},"ruff",[99,4410,4411,4414],{},[12,4412,4413],{},"ruff 是一個極速的 Python linter 和 code formatter，用 Rust 編寫，可以替代 Flake8、isort、Black 等多個工具。",[12,4415,4416],{},"它的目標是提供最快速、最全面的 Python 程式碼品質檢查和格式化解決方案。",[24,4418,4420],{"id":4419},"ruff-的主要特點","ruff 的主要特點",[145,4422,4423,4425,4438],{},[72,4424,4072],{},[72,4426,4427,4430],{},[16,4428,4429],{},"豐富的規則集",[145,4431,4432,4435],{},[72,4433,4434],{},"支援 800+ 個 lint 規則",[72,4436,4437],{},"整合了 Flake8、isort、pycodestyle、pyflakes 等工具的規則",[72,4439,4440],{},[16,4441,4442],{},"無需配置即可使用",[1622,4444],{},[1227,4446,4448],{"id":4447},"為什麼要使用-uv-和-ruff","為什麼要使用 uv 和 ruff？",[145,4450,4451,4467,4473,4490],{},[72,4452,4453,4456,4457],{},[16,4454,4455],{},"即時程式碼檢查","：ruff 的極速檢查讓程式碼品質控制變得無縫\n",[145,4458,4459],{},[72,4460,4461,4462],{},"尤其公司專案目前很多軟體動輒幾千行（較少抽象與依功能分割檔案），使用傳統程式碼檢查與 format 工具需要接近 1 分鐘或甚至更多\n",[145,4463,4464],{},[72,4465,4466],{},"想像你每次 Ctrl + S 都要等 1 分鐘…",[72,4468,4469,4472],{},[16,4470,4471],{},"穩定的工具鏈","：目前 uv 與 ruff 算是在各自領域（套件管理與 lint check / format）統一江湖的存在，未來不太會遇到需要再更換的情況",[72,4474,4475,4476],{},"為了未來可能需要統一產品版本鋪路，使用 Git 協作的情況會越來越多\n",[145,4477,4478,4484],{},[72,4479,4480,4483],{},[16,4481,4482],{},"一致的環境","：uv 確保所有團隊成員使用相同的依賴版本",[72,4485,4486,4489],{},[16,4487,4488],{},"統一的程式碼風格","：ruff 自動化程式碼風格檢查和格式化",[72,4491,4492,4495],{},[16,4493,4494],{},"更快的 CI/CD","：極速的執行效能減少構建時間，現在也有使用 GKE 的專案，可以減少 runner 開銷",[1622,4497],{},[1227,4499,4500],{"id":4500},"遷移範例",[12,4502,4503],{},"假設原本使用 requirements.txt + 很多 lint error",[145,4505,4506],{},[72,4507,4508,4509],{},"lint error 例如：\n",[145,4510,4511,4516,4519,4522],{},[72,4512,4513],{},[191,4514,4515],{},"if a == None",[72,4517,4518],{},"assign var or import but never used",[72,4520,4521],{},"assign a python keyword as a var name",[72,4523,4524,4525,4528],{},"use ",[191,4526,4527],{},"format"," instead of f-string",[12,4530,4531],{},"有遇到任何問題找 AI 處理，或是看文檔的 best practices",[24,4533,4535],{"id":4534},"_1-安裝與驗證","1. 安裝與驗證",[1286,4537,4539],{"className":3190,"code":4538,"language":3192,"meta":327,"style":327},"# 安裝 uv\npip install uv\n\n# 驗證（否則要看一下文檔，根據作業系統有不同安裝方式）\nuv --version\n",[191,4540,4541,4547,4558,4562,4567],{"__ignoreMap":327},[2497,4542,4543],{"class":2499,"line":2500},[2497,4544,4546],{"class":4545},"sJ8bj","# 安裝 uv\n",[2497,4548,4549,4552,4555],{"class":2499,"line":328},[2497,4550,4551],{"class":3199},"pip",[2497,4553,4554],{"class":3203}," install",[2497,4556,4557],{"class":3203}," uv\n",[2497,4559,4560],{"class":2499,"line":336},[2497,4561,2543],{"emptyLinePlaceholder":354},[2497,4563,4564],{"class":2499,"line":2516},[2497,4565,4566],{"class":4545},"# 驗證（否則要看一下文檔，根據作業系統有不同安裝方式）\n",[2497,4568,4569,4571],{"class":2499,"line":2522},[2497,4570,4053],{"class":3199},[2497,4572,4573],{"class":3210}," --version\n",[24,4575,4577],{"id":4576},"_2-init-專案與安裝依賴","2. init 專案與安裝依賴",[1286,4579,4581],{"className":3190,"code":4580,"language":3192,"meta":327,"style":327},"# 移到專案根目錄\ncd {your_project}\n\n# init 專案\nuv init --no-readme\n\n# 從現有 requirements.txt 遷移\nuv add -r requirements.txt\n\n# 手動新增依賴 (if needed)\nuv add requests numpy\n\n# 安裝 ruff\nuv add --dev ruff\n",[191,4582,4583,4588,4596,4600,4605,4615,4619,4624,4637,4641,4646,4658,4662,4667],{"__ignoreMap":327},[2497,4584,4585],{"class":2499,"line":2500},[2497,4586,4587],{"class":4545},"# 移到專案根目錄\n",[2497,4589,4590,4593],{"class":2499,"line":328},[2497,4591,4592],{"class":3210},"cd",[2497,4594,4595],{"class":3203}," {your_project}\n",[2497,4597,4598],{"class":2499,"line":336},[2497,4599,2543],{"emptyLinePlaceholder":354},[2497,4601,4602],{"class":2499,"line":2516},[2497,4603,4604],{"class":4545},"# init 專案\n",[2497,4606,4607,4609,4612],{"class":2499,"line":2522},[2497,4608,4053],{"class":3199},[2497,4610,4611],{"class":3203}," init",[2497,4613,4614],{"class":3210}," --no-readme\n",[2497,4616,4617],{"class":2499,"line":2528},[2497,4618,2543],{"emptyLinePlaceholder":354},[2497,4620,4621],{"class":2499,"line":2534},[2497,4622,4623],{"class":4545},"# 從現有 requirements.txt 遷移\n",[2497,4625,4626,4628,4631,4634],{"class":2499,"line":2540},[2497,4627,4053],{"class":3199},[2497,4629,4630],{"class":3203}," add",[2497,4632,4633],{"class":3210}," -r",[2497,4635,4636],{"class":3203}," requirements.txt\n",[2497,4638,4639],{"class":2499,"line":2546},[2497,4640,2543],{"emptyLinePlaceholder":354},[2497,4642,4643],{"class":2499,"line":2552},[2497,4644,4645],{"class":4545},"# 手動新增依賴 (if needed)\n",[2497,4647,4648,4650,4652,4655],{"class":2499,"line":2558},[2497,4649,4053],{"class":3199},[2497,4651,4630],{"class":3203},[2497,4653,4654],{"class":3203}," requests",[2497,4656,4657],{"class":3203}," numpy\n",[2497,4659,4660],{"class":2499,"line":2564},[2497,4661,2543],{"emptyLinePlaceholder":354},[2497,4663,4664],{"class":2499,"line":2570},[2497,4665,4666],{"class":4545},"# 安裝 ruff\n",[2497,4668,4669,4671,4673,4676],{"class":2499,"line":2576},[2497,4670,4053],{"class":3199},[2497,4672,4630],{"class":3203},[2497,4674,4675],{"class":3210}," --dev",[2497,4677,4678],{"class":3203}," ruff\n",[24,4680,4682],{"id":4681},"_3-設定-python-版本","3. 設定 python 版本",[1286,4684,4686],{"className":3190,"code":4685,"language":3192,"meta":327,"style":327},"# 查看可用的 Python 版本\nuv python list\n\n# 範例輸出：\n# cpython-3.14.0b4-macos-aarch64-none                 \u003Cdownload available>\n# cpython-3.14.0b4+freethreaded-macos-aarch64-none    \u003Cdownload available>\n# cpython-3.13.5-macos-aarch64-none                   /opt/homebrew/bin/python3.13\n# cpython-3.13.5-macos-aarch64-none                   /opt/homebrew/bin/python3\n# cpython-3.13.5-macos-aarch64-none                   \u003Cdownload available>\n\n# 設定專案使用的 Python 版本（例如 3.10）\nuv python pin 3.10\n",[191,4687,4688,4693,4703,4707,4712,4717,4722,4727,4732,4737,4741,4746],{"__ignoreMap":327},[2497,4689,4690],{"class":2499,"line":2500},[2497,4691,4692],{"class":4545},"# 查看可用的 Python 版本\n",[2497,4694,4695,4697,4700],{"class":2499,"line":328},[2497,4696,4053],{"class":3199},[2497,4698,4699],{"class":3203}," python",[2497,4701,4702],{"class":3203}," list\n",[2497,4704,4705],{"class":2499,"line":336},[2497,4706,2543],{"emptyLinePlaceholder":354},[2497,4708,4709],{"class":2499,"line":2516},[2497,4710,4711],{"class":4545},"# 範例輸出：\n",[2497,4713,4714],{"class":2499,"line":2522},[2497,4715,4716],{"class":4545},"# cpython-3.14.0b4-macos-aarch64-none                 \u003Cdownload available>\n",[2497,4718,4719],{"class":2499,"line":2528},[2497,4720,4721],{"class":4545},"# cpython-3.14.0b4+freethreaded-macos-aarch64-none    \u003Cdownload available>\n",[2497,4723,4724],{"class":2499,"line":2534},[2497,4725,4726],{"class":4545},"# cpython-3.13.5-macos-aarch64-none                   /opt/homebrew/bin/python3.13\n",[2497,4728,4729],{"class":2499,"line":2540},[2497,4730,4731],{"class":4545},"# cpython-3.13.5-macos-aarch64-none                   /opt/homebrew/bin/python3\n",[2497,4733,4734],{"class":2499,"line":2546},[2497,4735,4736],{"class":4545},"# cpython-3.13.5-macos-aarch64-none                   \u003Cdownload available>\n",[2497,4738,4739],{"class":2499,"line":2552},[2497,4740,2543],{"emptyLinePlaceholder":354},[2497,4742,4743],{"class":2499,"line":2558},[2497,4744,4745],{"class":4545},"# 設定專案使用的 Python 版本（例如 3.10）\n",[2497,4747,4748,4750,4752,4755],{"class":2499,"line":2564},[2497,4749,4053],{"class":3199},[2497,4751,4699],{"class":3203},[2497,4753,4754],{"class":3203}," pin",[2497,4756,4757],{"class":3210}," 3.10\n",[24,4759,4761,4762],{"id":4760},"_4-設定-pyprojecttoml","4. 設定 ",[191,4763,4764],{},"pyproject.toml",[12,4766,4767,4768,4771],{},"下面只是範例，基本上只需要注意 ",[191,4769,4770],{},"tool.ruff"," 相關的部分",[1286,4773,4775],{"className":4153,"code":4774,"language":4155,"meta":327,"style":327},"[project]\nname = \"my-project\"\nversion = \"0.1.0\"\ndescription = \"Add your description here\"\ndependencies = [\n    \"numpy>=1.24.0\",\n    \"requests>=2.31.0\",\n]\n\n[tool.uv]\ndev-dependencies = [\n    \"black>=23.7.0\",\n    \"flake8>=6.0.0\",\n    \"pytest>=7.4.0\",\n    \"ruff>=0.1.6\",\n]\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n# 加上 ruff 相關設置\n[tool.ruff]\nline-length = 88\ntarget-version = \"py310\"  # 改成你的 python 版本\n\n[tool.ruff.lint]\nselect = [\n    \"E\",   # pycodestyle errors\n    \"W\",   # pycodestyle warnings\n    \"F\",   # pyflakes\n    \"I\",   # isort\n    \"C\",   # flake8-comprehensions\n    \"B\",   # flake8-bugbear\n    \"UP\",  # pyupgrade\n]\nignore = [\n    \"E501\",  # line too long, handled by black\n    \"B008\",  # do not perform function calls in argument defaults\n    \"C901\",  # too complex\n    \"W191\",  # indentation contains tabs\n]\nexclude = [\n    \"tests/*\",\n    \"venv/*\",\n    \"*/site-packages/*\",\n    \"*/static/*\",\n    \"wsgi.py\",\n]\nfixable = [\"ALL\"]\nunfixable = []\n\n[tool.ruff.lint.per-file-ignores]\n\"tests/**/*\" = [\"S101\"]  # 測試中允許 assert\n\n[tool.ruff.format]\nquote-style = \"double\"\nindent-style = \"space\"\n",[191,4776,4777,4781,4786,4791,4796,4800,4805,4810,4814,4818,4823,4828,4833,4838,4843,4848,4852,4856,4860,4865,4870,4874,4879,4883,4887,4892,4896,4900,4905,4910,4915,4920,4925,4930,4935,4940,4944,4949,4954,4959,4964,4969,4973,4978,4983,4988,4993,4998,5003,5007,5012,5017,5021,5026,5031,5035,5040,5045],{"__ignoreMap":327},[2497,4778,4779],{"class":2499,"line":2500},[2497,4780,4162],{},[2497,4782,4783],{"class":2499,"line":328},[2497,4784,4785],{},"name = \"my-project\"\n",[2497,4787,4788],{"class":2499,"line":336},[2497,4789,4790],{},"version = \"0.1.0\"\n",[2497,4792,4793],{"class":2499,"line":2516},[2497,4794,4795],{},"description = \"Add your description here\"\n",[2497,4797,4798],{"class":2499,"line":2522},[2497,4799,4231],{},[2497,4801,4802],{"class":2499,"line":2528},[2497,4803,4804],{},"    \"numpy>=1.24.0\",\n",[2497,4806,4807],{"class":2499,"line":2534},[2497,4808,4809],{},"    \"requests>=2.31.0\",\n",[2497,4811,4812],{"class":2499,"line":2540},[2497,4813,4217],{},[2497,4815,4816],{"class":2499,"line":2546},[2497,4817,2543],{"emptyLinePlaceholder":354},[2497,4819,4820],{"class":2499,"line":2552},[2497,4821,4822],{},"[tool.uv]\n",[2497,4824,4825],{"class":2499,"line":2558},[2497,4826,4827],{},"dev-dependencies = [\n",[2497,4829,4830],{"class":2499,"line":2564},[2497,4831,4832],{},"    \"black>=23.7.0\",\n",[2497,4834,4835],{"class":2499,"line":2570},[2497,4836,4837],{},"    \"flake8>=6.0.0\",\n",[2497,4839,4840],{"class":2499,"line":2576},[2497,4841,4842],{},"    \"pytest>=7.4.0\",\n",[2497,4844,4845],{"class":2499,"line":2582},[2497,4846,4847],{},"    \"ruff>=0.1.6\",\n",[2497,4849,4850],{"class":2499,"line":2588},[2497,4851,4217],{},[2497,4853,4854],{"class":2499,"line":2594},[2497,4855,2543],{"emptyLinePlaceholder":354},[2497,4857,4858],{"class":2499,"line":2600},[2497,4859,4368],{},[2497,4861,4862],{"class":2499,"line":2606},[2497,4863,4864],{},"requires = [\"hatchling\"]\n",[2497,4866,4867],{"class":2499,"line":2611},[2497,4868,4869],{},"build-backend = \"hatchling.build\"\n",[2497,4871,4872],{"class":2499,"line":2617},[2497,4873,2543],{"emptyLinePlaceholder":354},[2497,4875,4876],{"class":2499,"line":2623},[2497,4877,4878],{},"# 加上 ruff 相關設置\n",[2497,4880,4881],{"class":2499,"line":2629},[2497,4882,4316],{},[2497,4884,4885],{"class":2499,"line":2635},[2497,4886,4321],{},[2497,4888,4889],{"class":2499,"line":2641},[2497,4890,4891],{},"target-version = \"py310\"  # 改成你的 python 版本\n",[2497,4893,4894],{"class":2499,"line":2646},[2497,4895,2543],{"emptyLinePlaceholder":354},[2497,4897,4898],{"class":2499,"line":2652},[2497,4899,4335],{},[2497,4901,4902],{"class":2499,"line":2658},[2497,4903,4904],{},"select = [\n",[2497,4906,4907],{"class":2499,"line":2664},[2497,4908,4909],{},"    \"E\",   # pycodestyle errors\n",[2497,4911,4912],{"class":2499,"line":2669},[2497,4913,4914],{},"    \"W\",   # pycodestyle warnings\n",[2497,4916,4917],{"class":2499,"line":2675},[2497,4918,4919],{},"    \"F\",   # pyflakes\n",[2497,4921,4922],{"class":2499,"line":2681},[2497,4923,4924],{},"    \"I\",   # isort\n",[2497,4926,4927],{"class":2499,"line":2687},[2497,4928,4929],{},"    \"C\",   # flake8-comprehensions\n",[2497,4931,4932],{"class":2499,"line":2693},[2497,4933,4934],{},"    \"B\",   # flake8-bugbear\n",[2497,4936,4937],{"class":2499,"line":2698},[2497,4938,4939],{},"    \"UP\",  # pyupgrade\n",[2497,4941,4942],{"class":2499,"line":2704},[2497,4943,4217],{},[2497,4945,4946],{"class":2499,"line":2710},[2497,4947,4948],{},"ignore = [\n",[2497,4950,4951],{"class":2499,"line":2716},[2497,4952,4953],{},"    \"E501\",  # line too long, handled by black\n",[2497,4955,4956],{"class":2499,"line":2722},[2497,4957,4958],{},"    \"B008\",  # do not perform function calls in argument defaults\n",[2497,4960,4961],{"class":2499,"line":2728},[2497,4962,4963],{},"    \"C901\",  # too complex\n",[2497,4965,4966],{"class":2499,"line":2734},[2497,4967,4968],{},"    \"W191\",  # indentation contains tabs\n",[2497,4970,4971],{"class":2499,"line":2740},[2497,4972,4217],{},[2497,4974,4975],{"class":2499,"line":2746},[2497,4976,4977],{},"exclude = [\n",[2497,4979,4980],{"class":2499,"line":2752},[2497,4981,4982],{},"    \"tests/*\",\n",[2497,4984,4985],{"class":2499,"line":2758},[2497,4986,4987],{},"    \"venv/*\",\n",[2497,4989,4990],{"class":2499,"line":2764},[2497,4991,4992],{},"    \"*/site-packages/*\",\n",[2497,4994,4995],{"class":2499,"line":2770},[2497,4996,4997],{},"    \"*/static/*\",\n",[2497,4999,5000],{"class":2499,"line":2775},[2497,5001,5002],{},"    \"wsgi.py\",\n",[2497,5004,5005],{"class":2499,"line":2781},[2497,5006,4217],{},[2497,5008,5009],{"class":2499,"line":2786},[2497,5010,5011],{},"fixable = [\"ALL\"]\n",[2497,5013,5014],{"class":2499,"line":2791},[2497,5015,5016],{},"unfixable = []\n",[2497,5018,5019],{"class":2499,"line":2796},[2497,5020,2543],{"emptyLinePlaceholder":354},[2497,5022,5023],{"class":2499,"line":2801},[2497,5024,5025],{},"[tool.ruff.lint.per-file-ignores]\n",[2497,5027,5028],{"class":2499,"line":2806},[2497,5029,5030],{},"\"tests/**/*\" = [\"S101\"]  # 測試中允許 assert\n",[2497,5032,5033],{"class":2499,"line":2812},[2497,5034,2543],{"emptyLinePlaceholder":354},[2497,5036,5037],{"class":2499,"line":2817},[2497,5038,5039],{},"[tool.ruff.format]\n",[2497,5041,5042],{"class":2499,"line":2823},[2497,5043,5044],{},"quote-style = \"double\"\n",[2497,5046,5047],{"class":2499,"line":2829},[2497,5048,5049],{},"indent-style = \"space\"\n",[24,5051,5053],{"id":5052},"_5-安裝依賴","5. 安裝依賴",[1286,5055,5057],{"className":3190,"code":5056,"language":3192,"meta":327,"style":327},"# 這會產生 uv.lock，第一次加上 --frozen 就好\nuv sync --frozen\n",[191,5058,5059,5064],{"__ignoreMap":327},[2497,5060,5061],{"class":2499,"line":2500},[2497,5062,5063],{"class":4545},"# 這會產生 uv.lock，第一次加上 --frozen 就好\n",[2497,5065,5066,5068,5071],{"class":2499,"line":328},[2497,5067,4053],{"class":3199},[2497,5069,5070],{"class":3203}," sync",[2497,5072,5073],{"class":3210}," --frozen\n",[24,5075,5077],{"id":5076},"_6-ide-設置如果你使用-vs-code","6. IDE 設置（如果你使用 VS Code）",[69,5079,5080,5083,5086],{},[72,5081,5082],{},"安裝 VS Code extensions（如果你使用 VS Code，可以去找一下 ruff 的 extension，可以使用 IDE 的修復輔助）",[72,5084,5085],{},"Ctrl + Shift + P：>Format Document With… 將 ruff 設為 default formatter",[72,5087,5088],{},"確保 format on save = true",[24,5090,5092],{"id":5091},"_7-清理現有代碼","7. 清理現有代碼",[1286,5094,5096],{"className":3190,"code":5095,"language":3192,"meta":327,"style":327},"# 先看有幾個錯誤，以及可以使用 --fix 自動修復的項目\nruff check\n\n# 自動修復\nruff check --fix\n\n# 接下來開始清理 code style 問題\n# 啟用 hot reload，可以快速修復已存在的錯誤\nruff check --watch\n",[191,5097,5098,5103,5110,5114,5119,5129,5133,5138,5143],{"__ignoreMap":327},[2497,5099,5100],{"class":2499,"line":2500},[2497,5101,5102],{"class":4545},"# 先看有幾個錯誤，以及可以使用 --fix 自動修復的項目\n",[2497,5104,5105,5107],{"class":2499,"line":328},[2497,5106,4408],{"class":3199},[2497,5108,5109],{"class":3203}," check\n",[2497,5111,5112],{"class":2499,"line":336},[2497,5113,2543],{"emptyLinePlaceholder":354},[2497,5115,5116],{"class":2499,"line":2516},[2497,5117,5118],{"class":4545},"# 自動修復\n",[2497,5120,5121,5123,5126],{"class":2499,"line":2522},[2497,5122,4408],{"class":3199},[2497,5124,5125],{"class":3203}," check",[2497,5127,5128],{"class":3210}," --fix\n",[2497,5130,5131],{"class":2499,"line":2528},[2497,5132,2543],{"emptyLinePlaceholder":354},[2497,5134,5135],{"class":2499,"line":2534},[2497,5136,5137],{"class":4545},"# 接下來開始清理 code style 問題\n",[2497,5139,5140],{"class":2499,"line":2540},[2497,5141,5142],{"class":4545},"# 啟用 hot reload，可以快速修復已存在的錯誤\n",[2497,5144,5145,5147,5149],{"class":2499,"line":2546},[2497,5146,4408],{"class":3199},[2497,5148,5125],{"class":3203},[2497,5150,5151],{"class":3210}," --watch\n",[24,5153,5155],{"id":5154},"_8-執行專案","8. 執行專案",[1286,5157,5159],{"className":3190,"code":5158,"language":3192,"meta":327,"style":327},"uv run pytest\nuv run python src/main.py\n",[191,5160,5161,5170],{"__ignoreMap":327},[2497,5162,5163,5165,5167],{"class":2499,"line":2500},[2497,5164,4053],{"class":3199},[2497,5166,3207],{"class":3203},[2497,5168,5169],{"class":3203}," pytest\n",[2497,5171,5172,5174,5176,5178],{"class":2499,"line":328},[2497,5173,4053],{"class":3199},[2497,5175,3207],{"class":3203},[2497,5177,4699],{"class":3203},[2497,5179,5180],{"class":3203}," src/main.py\n",[24,5182,5184],{"id":5183},"_9-dockerfile-範例","9. Dockerfile 範例",[1286,5186,5190],{"className":5187,"code":5188,"language":5189,"meta":327,"style":327},"language-dockerfile shiki shiki-themes github-light github-dark","# 使用官方 Python 基礎映像\nFROM python:3.11-slim\n\n# 設定工作目錄\nWORKDIR /app\n\n# 安裝 uv\nCOPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/\n\n# 複製依賴文件\nCOPY pyproject.toml uv.lock ./\n\n# 安裝依賴（不包含開發依賴）\nRUN uv sync --frozen --no-dev\n\n# 複製應用程式代碼\nCOPY . .\n\n# 設定 Python 路徑\nENV PATH=\"/app/.venv/bin:$PATH\"\n\n# 暴露端口（根據你的應用程式調整）\nEXPOSE 8000\n\n# 執行應用程式\nCMD [\"python\", \"src/main.py\"]\n","dockerfile",[191,5191,5192,5197,5202,5206,5211,5216,5220,5224,5229,5233,5238,5243,5247,5252,5257,5261,5266,5271,5275,5280,5285,5289,5294,5299,5303,5308],{"__ignoreMap":327},[2497,5193,5194],{"class":2499,"line":2500},[2497,5195,5196],{},"# 使用官方 Python 基礎映像\n",[2497,5198,5199],{"class":2499,"line":328},[2497,5200,5201],{},"FROM python:3.11-slim\n",[2497,5203,5204],{"class":2499,"line":336},[2497,5205,2543],{"emptyLinePlaceholder":354},[2497,5207,5208],{"class":2499,"line":2516},[2497,5209,5210],{},"# 設定工作目錄\n",[2497,5212,5213],{"class":2499,"line":2522},[2497,5214,5215],{},"WORKDIR /app\n",[2497,5217,5218],{"class":2499,"line":2528},[2497,5219,2543],{"emptyLinePlaceholder":354},[2497,5221,5222],{"class":2499,"line":2534},[2497,5223,4546],{},[2497,5225,5226],{"class":2499,"line":2540},[2497,5227,5228],{},"COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/\n",[2497,5230,5231],{"class":2499,"line":2546},[2497,5232,2543],{"emptyLinePlaceholder":354},[2497,5234,5235],{"class":2499,"line":2552},[2497,5236,5237],{},"# 複製依賴文件\n",[2497,5239,5240],{"class":2499,"line":2558},[2497,5241,5242],{},"COPY pyproject.toml uv.lock ./\n",[2497,5244,5245],{"class":2499,"line":2564},[2497,5246,2543],{"emptyLinePlaceholder":354},[2497,5248,5249],{"class":2499,"line":2570},[2497,5250,5251],{},"# 安裝依賴（不包含開發依賴）\n",[2497,5253,5254],{"class":2499,"line":2576},[2497,5255,5256],{},"RUN uv sync --frozen --no-dev\n",[2497,5258,5259],{"class":2499,"line":2582},[2497,5260,2543],{"emptyLinePlaceholder":354},[2497,5262,5263],{"class":2499,"line":2588},[2497,5264,5265],{},"# 複製應用程式代碼\n",[2497,5267,5268],{"class":2499,"line":2594},[2497,5269,5270],{},"COPY . .\n",[2497,5272,5273],{"class":2499,"line":2600},[2497,5274,2543],{"emptyLinePlaceholder":354},[2497,5276,5277],{"class":2499,"line":2606},[2497,5278,5279],{},"# 設定 Python 路徑\n",[2497,5281,5282],{"class":2499,"line":2611},[2497,5283,5284],{},"ENV PATH=\"/app/.venv/bin:$PATH\"\n",[2497,5286,5287],{"class":2499,"line":2617},[2497,5288,2543],{"emptyLinePlaceholder":354},[2497,5290,5291],{"class":2499,"line":2623},[2497,5292,5293],{},"# 暴露端口（根據你的應用程式調整）\n",[2497,5295,5296],{"class":2499,"line":2629},[2497,5297,5298],{},"EXPOSE 8000\n",[2497,5300,5301],{"class":2499,"line":2635},[2497,5302,2543],{"emptyLinePlaceholder":354},[2497,5304,5305],{"class":2499,"line":2641},[2497,5306,5307],{},"# 執行應用程式\n",[2497,5309,5310],{"class":2499,"line":2646},[2497,5311,5312],{},"CMD [\"python\", \"src/main.py\"]\n",[1622,5314],{},[1227,5316,1060],{"id":1060},[145,5318,5319,5326,5333],{},[72,5320,5321],{},[42,5322,5325],{"href":5323,"rel":5324},"https://astral.sh/",[46],"Astral: High-performance Python tooling",[72,5327,5328],{},[42,5329,5332],{"href":5330,"rel":5331},"https://docs.astral.sh/uv/",[46],"uv Documentation",[72,5334,5335],{},[42,5336,5339],{"href":5337,"rel":5338},"https://docs.astral.sh/ruff/",[46],"Ruff Documentation",[3610,5341,5342],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":327,"searchDepth":328,"depth":328,"links":5344},[5345,5346,5350,5351,5352,5353,5354,5356,5357,5358,5359,5360],{"id":4064,"depth":328,"text":4065},{"id":4098,"depth":328,"text":4099,"children":5347},[5348,5349],{"id":4149,"depth":336,"text":4150},{"id":4381,"depth":336,"text":4381},{"id":4419,"depth":328,"text":4420},{"id":4534,"depth":328,"text":4535},{"id":4576,"depth":328,"text":4577},{"id":4681,"depth":328,"text":4682},{"id":4760,"depth":328,"text":5355},"4. 設定 pyproject.toml",{"id":5052,"depth":328,"text":5053},{"id":5076,"depth":328,"text":5077},{"id":5091,"depth":328,"text":5092},{"id":5154,"depth":328,"text":5155},{"id":5183,"depth":328,"text":5184},"介紹 uv 和 Ruff，兩個最佳的 Python 工具鏈，以及如何遷移到 uv 和 Ruff。",{},"/blog/uv-and-ruff",{"title":4041,"description":5361},"blog/uv-and-ruff",[3641,1456,1457,4053,5367],"Ruff","-zRvt4hekmdP0cJ82m1OICrMfHIMFKCtweqM3DQ743s",1774237781937]