[{"data":1,"prerenderedAt":1210},["ShallowReactive",2],{"navigation_docs":3,"-python-memory-buffer":352,"-python-memory-buffer-surround":1205},[4,54,83,116,125,161,179,200],{"title":5,"path":6,"stem":7,"children":8},"Ai","/ai","ai",[9,12,42,46,50],{"title":10,"path":6,"stem":11},"AI 技術探索","ai/index",{"title":13,"path":14,"stem":15,"children":16,"page":41},"Rag","/ai/rag","ai/RAG",[17,21,25,29,33,37],{"title":18,"path":19,"stem":20},"BM25 演算法深入解析：從 TF-IDF 到現代搜尋引擎的核心技術","/ai/rag/bm25-deep-dive-from-tf-idf-to-rag","ai/RAG/bm25-deep-dive-from-tf-idf-to-rag",{"title":22,"path":23,"stem":24},"Dense Retrieval（向量檢索）深入解析","/ai/rag/dense-retrieval-deep-dive","ai/RAG/dense-retrieval-deep-dive",{"title":26,"path":27,"stem":28},"RAG（Retrieval-Augmented Generation）原理與實作入門","/ai/rag/rag-intro-principles-and-implementation","ai/RAG/rag-intro-principles-and-implementation",{"title":30,"path":31,"stem":32},"RAG Retrieval Strategy 深入解析","/ai/rag/rag-retrieval-strategy-deep-dive","ai/RAG/rag-retrieval-strategy-deep-dive",{"title":34,"path":35,"stem":36},"Reciprocal Rank Fusion（RRF）教學：打造更穩定的檢索融合策略","/ai/rag/rrf-reciprocal-rank-fusion-guide","ai/RAG/rrf-reciprocal-rank-fusion-guide",{"title":38,"path":39,"stem":40},"TF-IDF（Term Frequency–Inverse Document Frequency）深入解析","/ai/rag/tf-idf-deep-dive","ai/RAG/tf-idf-deep-dive",false,{"title":43,"path":44,"stem":45},"Docus AI 功能完整實作指南","/ai/docus-ai-implementation","ai/docus-ai-implementation",{"title":47,"path":48,"stem":49},"llms.txt 是什麼？在 Nuxt 中控制 AI 與搜尋引擎爬蟲的新方式","/ai/llms-txt","ai/llms-txt",{"title":51,"path":52,"stem":53},"MCP（Model Context Protocol）是什麼？用「AI 的 USB-C」理解模型如何安全連接工具與資料","/ai/mcp_concept","ai/mcp_concept",{"title":55,"path":56,"stem":57,"children":58,"page":41},"Data Analysis","/data-analysis","data-analysis",[59,63,67,71,75,79],{"title":60,"path":61,"stem":62},"Exploratory Data Analysis（EDA）資料探索分析入門","/data-analysis/exploratory-data-analysis-eda","data-analysis/exploratory-data-analysis-eda",{"title":64,"path":65,"stem":66},"PR-AUC 深入解析：不平衡分類問題的重要評估指標","/data-analysis/pr-auc-for-imbalanced-classification","data-analysis/pr-auc-for-imbalanced-classification",{"title":68,"path":69,"stem":70},"ROC-AUC 深入解析：理解分類模型的判斷能力","/data-analysis/roc-auc-complete-guide","data-analysis/roc-auc-complete-guide",{"title":72,"path":73,"stem":74},"SHAP（SHapley Additive exPlanations）模型解釋方法深入解析","/data-analysis/shap-model-interpretation-complete-guide","data-analysis/shap-model-interpretation-complete-guide",{"title":76,"path":77,"stem":78},"Social Network Analysis（SNA）深入解析","/data-analysis/social-network-analysis-sna","data-analysis/social-network-analysis-sna",{"title":80,"path":81,"stem":82},"Time-based Split：為什麼時間序列資料不能使用 Random Split？","/data-analysis/time-based-split-vs-random-split","data-analysis/time-based-split-vs-random-split",{"title":84,"path":85,"stem":86,"children":87,"page":41},"MachineLearning","/machine_learning","machine_learning",[88,92,96,100,104,108,112],{"title":89,"path":90,"stem":91},"Confusion Matrix（混淆矩陣）完整解析","/machine_learning/confusion-matrix","machine_learning/confusion-matrix",{"title":93,"path":94,"stem":95},"Decision Tree 模型完整教學：從原理到 Python 實作","/machine_learning/decision-tree-complete-guide","machine_learning/decision-tree-complete-guide",{"title":97,"path":98,"stem":99},"為什麼 Decision Tree 在 Tabular Data 上常常勝過 Deep Learning？","/machine_learning/decision-tree-vs-deep-learning-on-tabular-data","machine_learning/decision-tree-vs-deep-learning-on-tabular-data",{"title":101,"path":102,"stem":103},"LightGBM 模型入門：理解高效能的 Gradient Boosting 演算法","/machine_learning/lightgbm-intro-for-tabular-data","machine_learning/lightgbm-intro-for-tabular-data",{"title":105,"path":106,"stem":107},"Logistic Regression（Logit 回歸）完整教學","/machine_learning/logistic-regression-complete-guide","machine_learning/logistic-regression-complete-guide",{"title":109,"path":110,"stem":111},"為什麼模型訓練需要 Train / Validation / Test Dataset？","/machine_learning/train-validation-test-dataset","machine_learning/train-validation-test-dataset",{"title":113,"path":114,"stem":115},"XGBoost 原理與實務教學","/machine_learning/xgboost-complete-guide","machine_learning/xgboost-complete-guide",{"title":117,"path":118,"stem":119,"children":120,"page":41},"Network","/network","network",[121],{"title":122,"path":123,"stem":124},"DNS 設定入門：CNAME vs A Record 完整解析","/network/dns-cname-vs-a-record-guide","network/dns-cname-vs-a-record-guide",{"title":126,"path":127,"stem":128,"children":129},"Nuxt","/nuxt","nuxt",[130,133,137,141,145,149,153,157],{"title":131,"path":127,"stem":132},"Nuxt 實戰指南","nuxt/index",{"title":134,"path":135,"stem":136},"Abstraction Layer 與 Adapter 的差別，一次搞懂設計角色","/nuxt/adapter_abstraction_layer","nuxt/adapter_abstraction_layer",{"title":138,"path":139,"stem":140},"Nuxt 4 全面理解 — 專案檔案結構解析","/nuxt/directory_structure","nuxt/directory_structure",{"title":142,"path":143,"stem":144},"Nuxt 前端 build 流程完整解析","/nuxt/frontend_build","nuxt/frontend_build",{"title":146,"path":147,"stem":148},"Nitro 是什麼？從 Nuxt 專案到 Server 與 Edge 的關鍵引擎","/nuxt/nitro","nuxt/nitro",{"title":150,"path":151,"stem":152},"深入理解 Nuxt 的 .nuxt 與 .output：為什麼部署時一定要分清楚？","/nuxt/nuxt_and_output","nuxt/nuxt_and_output",{"title":154,"path":155,"stem":156},"Nuxt 中 dev、build、preview 的差異一次搞懂","/nuxt/nuxt_dev_build_preview","nuxt/nuxt_dev_build_preview",{"title":158,"path":159,"stem":160},"Nuxt Image vs \u003Cimg>：為什麼 Nuxt 3 專案幾乎都該用 \u003CNuxtImg>？","/nuxt/nuxt_image","nuxt/nuxt_image",{"title":162,"path":163,"stem":164,"children":165},"Python 程式開發","/python","python/index",[166,167,171,175],{"title":162,"path":163,"stem":164},{"title":168,"path":169,"stem":170},"如何在 FastAPI 中調用記憶體 Buffer 回傳圖片 (附實例)","/python/memory_buffer","python/memory_buffer",{"title":172,"path":173,"stem":174},"Python 單元測試教學：如何為你的程式撰寫 Test","/python/python-unit-testing-unittest-pytest","python/python-unit-testing-unittest-pytest",{"title":176,"path":177,"stem":178},"如何使用 uv 取代 pip：改善 Python 專案的開發流程","/python/uv","python/uv",{"title":180,"path":181,"stem":182,"children":183,"page":41},"Statistic","/statistic","statistic",[184,188,192,196],{"title":185,"path":186,"stem":187},"卡方檢定（Chi-Square Test）入門教學","/statistic/chi-square-test-intro","statistic/chi-square-test-intro",{"title":189,"path":190,"stem":191},"常見抽樣方法（Sampling Methods）教學：Stratified、Quota、Convenience、Systematic、Simple Random","/statistic/common-sampling-methods-guide","statistic/common-sampling-methods-guide",{"title":193,"path":194,"stem":195},"Cramér’s V 指標介紹：如何衡量兩個類別變數之間的關聯","/statistic/cramers-v-for-categorical-association","statistic/cramers-v-for-categorical-association",{"title":197,"path":198,"stem":199},"Mann–Whitney U 與 Kolmogorov–Smirnov（KS）檢定：非參數統計檢定的入門指南","/statistic/mann-whitney-u-and-ks-test-intro","statistic/mann-whitney-u-and-ks-test-intro",{"title":201,"path":202,"stem":203,"children":204},"WebDev","/web_dev","web_dev",[205,208,234,248,282,312,334],{"title":206,"path":202,"stem":207},"Web 前端開發","web_dev/index",{"title":209,"path":210,"stem":211,"children":212},"瀏覽器與渲染","/web_dev/browser","web_dev/browser/index",[213,214,218,222,226,230],{"title":209,"path":210,"stem":211},{"title":215,"path":216,"stem":217},"瀏覽器儲存空間完整解析：Cookie、localStorage、IndexedDB 到 Cache Storage","/web_dev/browser/browser-storage-comprehensive-guide","web_dev/browser/browser-storage-comprehensive-guide",{"title":219,"path":220,"stem":221},"DOM (Document Object Model) 深入解析","/web_dev/browser/dom","web_dev/browser/dom",{"title":223,"path":224,"stem":225},"Service Worker Request Flow 深入解析","/web_dev/browser/service-worker-request-flow","web_dev/browser/service-worker-request-flow",{"title":227,"path":228,"stem":229},"CSR、SSR 與 SSG 是什麼？前端渲染策略完整比較","/web_dev/browser/ssr_csr_ssg","web_dev/browser/ssr_csr_ssg",{"title":231,"path":232,"stem":233},"Virtual DOM 深入解析：為什麼它能優化前端效能？","/web_dev/browser/virtual_dom","web_dev/browser/virtual_dom",{"title":235,"path":236,"stem":237,"children":238},"圖形技術","/web_dev/graphics","web_dev/graphics/index",[239,240,244],{"title":235,"path":236,"stem":237},{"title":241,"path":242,"stem":243},"為什麼 Three.js 專案幾乎都選擇 CSR？從 SSR 問題談起","/web_dev/graphics/threejs_csr","web_dev/graphics/threejs_csr",{"title":245,"path":246,"stem":247},"WebGL 是什麼？為什麼前端 3D 幾乎都靠它？","/web_dev/graphics/webgl","web_dev/graphics/webGL",{"title":249,"path":250,"stem":251,"children":252},"架構與配置","/web_dev/infrastructure","web_dev/infrastructure/index",[253,254,258,262,266,270,274,278],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257},"Zeabur + K3s + Nuxt 部署架構完整解析","/web_dev/infrastructure/k3s-zeabur","web_dev/infrastructure/K3s-zeabur",{"title":259,"path":260,"stem":261},"ECS vs Docker vs Kubernetes：從部署堆疊理解三層架構","/web_dev/infrastructure/ecs-docker-kubernetes-stack","web_dev/infrastructure/ecs-docker-kubernetes-stack",{"title":263,"path":264,"stem":265},"ECS vs VPS 是什麼？從部署網站的角度一次搞懂差別","/web_dev/infrastructure/ecs-vs-vps","web_dev/infrastructure/ecs-vs-vps",{"title":267,"path":268,"stem":269},"Zeabur 內網服務連線完整入門指南","/web_dev/infrastructure/k3s-internal-networking","web_dev/infrastructure/k3s-internal-networking",{"title":271,"path":272,"stem":273},"使用 Microservices 架構設計系統的優勢解析","/web_dev/infrastructure/microservices-architecture-advantages","web_dev/infrastructure/microservices-architecture-advantages",{"title":275,"path":276,"stem":277},"Nginx 入門教學：從反向代理到與 Kubernetes 的架構比較","/web_dev/infrastructure/nginx-intro","web_dev/infrastructure/nginx-intro",{"title":279,"path":280,"stem":281},"YAML 配置是什麼？為什麼現代開發都在用它","/web_dev/infrastructure/yaml-configuration","web_dev/infrastructure/yaml-configuration",{"title":283,"path":284,"stem":285,"children":286},"網絡與通訊","/web_dev/network","web_dev/network/index",[287,288,292,296,300,304,308],{"title":283,"path":284,"stem":285},{"title":289,"path":290,"stem":291},"HTTP Request 結構完整解析：從 Request Line 到 Header 一次看懂","/web_dev/network/http-request-structure","web_dev/network/http-request-structure",{"title":293,"path":294,"stem":295},"OSI 模型（Open Systems Interconnection Model）完整解析","/web_dev/network/osi-model","web_dev/network/osi-model",{"title":297,"path":298,"stem":299},"RESTful API 設計原則與實務解析","/web_dev/network/restful-api","web_dev/network/restful-api",{"title":301,"path":302,"stem":303},"RESTful API 五大設計原則深入解析","/web_dev/network/restful-api-principles","web_dev/network/restful-api-principles",{"title":305,"path":306,"stem":307},"Server-Sent Events (SSE) 深入解析與實作教學","/web_dev/network/sse-introduction","web_dev/network/sse-introduction",{"title":309,"path":310,"stem":311},"WebSocket 入門教學：從概念到 Node.js 實作","/web_dev/network/websocket-introduction","web_dev/network/websocket-introduction",{"title":313,"path":314,"stem":315,"children":316},"認證與安全","/web_dev/security","web_dev/security/index",[317,318,322,326,330],{"title":313,"path":314,"stem":315},{"title":319,"path":320,"stem":321},"使用 Cookie 與 Session 建立使用者驗證（完整新手教學）","/web_dev/security/cookie-session-authentication","web_dev/security/cookie-session-authentication",{"title":323,"path":324,"stem":325},"JWT 驗證機制完整解析：從登入流程到實務應用","/web_dev/security/jwt-authentication","web_dev/security/jwt-authentication",{"title":327,"path":328,"stem":329},"Secret Key 簽名是什麼？從零理解資料簽名的本質","/web_dev/security/secret-key-signing","web_dev/security/secret-key-signing",{"title":331,"path":332,"stem":333},"SSH（Secure Shell）是什麼？從遠端登入到安全通道的核心概念","/web_dev/security/ssh","web_dev/security/ssh",{"title":335,"path":336,"stem":337,"children":338},"SEO 與規範","/web_dev/seo","web_dev/seo/index",[339,340,344,348],{"title":335,"path":336,"stem":337},{"title":341,"path":342,"stem":343},"Canonical URL 是什麼？用生活化方式搞懂前端 SEO 的基本保命符","/web_dev/seo/canonical_link","web_dev/seo/canonical_link",{"title":345,"path":346,"stem":347},"robots.txt 是什麼？SEO 的第一道守門員","/web_dev/seo/robot_txt","web_dev/seo/robot_txt",{"title":349,"path":350,"stem":351},"Nuxt SEO 中的 Meta 與 SEO 工具是如何運作的？","/web_dev/seo/seo","web_dev/seo/seo",{"id":353,"title":168,"body":354,"description":1193,"extension":1194,"links":1195,"meta":1196,"navigation":506,"path":169,"seo":1203,"stem":170,"__hash__":1204},"docs/python/memory_buffer.md",{"type":355,"value":356,"toc":1175},"minimark",[357,366,373,376,381,388,391,393,398,401,404,407,409,413,425,880,882,886,902,908,915,917,921,924,942,945,978,981,985,989,999,1002,1004,1008,1011,1057,1067,1072,1082,1084,1088,1095,1098,1104,1106,1114,1120,1127,1129,1133,1136,1143,1145,1149,1152,1159,1161,1165,1168,1171],[358,359,360,361,365],"p",{},"在使用 FastAPI 建立資料視覺化 API 時，一個常見的需求是：",[362,363,364],"strong",{},"即時產生圖表，並直接回傳給前端顯示","。許多初學者會直覺地將圖存成檔案，再讀取檔案回傳，但在實際的後端服務環境中，這種做法往往會帶來效能與併發上的風險。",[358,367,368,369,372],{},"本文將以一段實際常見的程式碼為例，說明 ",[362,370,371],{},"Matplotlib 圖片為什麼應該直接寫入記憶體（memory buffer），而不是存成實體檔案","，並解釋這種寫法在 FastAPI 中的正確定位。",[374,375],"hr",{},[377,378,380],"h2",{"id":379},"用-fastapi-動態產生並回傳-sin-函數視覺化圖表完整實例","用 FastAPI 動態產生並回傳 sin 函數視覺化圖表（完整實例）",[358,382,383,384,387],{},"在這個小節中，我們將實作一個簡單但「設計正確」的範例：",[362,385,386],{},"透過 FastAPI 接收 sin 函數參數，並即時回傳對應的圖表圖片","。",[358,389,390],{},"這個範例不會將圖片存成檔案，而是直接在記憶體中產生並回傳，符合實際 production 環境的寫法。",[374,392],{},[394,395,397],"h3",{"id":396},"一api-想解決什麼問題","一、API 想解決什麼問題？",[358,399,400],{},"我們希望做到以下幾件事：",[358,402,403],{},"使用者可以透過 URL 傳入參數，例如振幅與頻率，FastAPI 接收到參數後即時計算對應的 sin 函數，使用 Matplotlib 畫出圖形，最後將圖片直接回傳給瀏覽器顯示。",[358,405,406],{},"整個流程中，不產生任何實體檔案，也不留下伺服器狀態。",[374,408],{},[394,410,412],{"id":411},"二完整-fastapi-實作範例","二、完整 FastAPI 實作範例",[358,414,415,416,419,420,424],{},"以下是一個 ",[362,417,418],{},"可以直接執行的最小完整範例（MVP）","，你可以存成 ",[421,422,423],"code",{},"main.py"," 使用。",[426,427,432],"pre",{"className":428,"code":429,"language":430,"meta":431,"style":431},"language-python shiki shiki-themes github-dark","from fastapi import FastAPI\nfrom fastapi.responses import StreamingResponse\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport io\n\napp = FastAPI(title=\"Sine Function Visualization API\")\n\n\n@app.get(\"/sin\")\ndef plot_sin(\n    amplitude: float = 1.0,\n    frequency: float = 1.0\n):\n    # Generate x range\n    x = np.linspace(0, 2 * np.pi, 400)\n    y = amplitude * np.sin(frequency * x)\n\n    # Create figure\n    fig, ax = plt.subplots(figsize=(8, 4))\n    ax.plot(x, y)\n    ax.set_title(f\"y = {amplitude} · sin({frequency}x)\")\n    ax.set_xlabel(\"x\")\n    ax.set_ylabel(\"y\")\n    ax.grid(True)\n\n    # Export image to memory buffer\n    buf = io.BytesIO()\n    plt.tight_layout()\n    plt.savefig(buf, format=\"png\")\n    plt.close(fig)\n    buf.seek(0)\n\n    return StreamingResponse(\n        buf,\n        media_type=\"image/png\"\n    )\n","python","",[421,433,434,453,466,480,493,501,508,533,538,543,558,570,589,602,608,615,646,668,673,679,708,714,750,761,772,783,788,794,805,811,827,833,843,848,857,863,874],{"__ignoreMap":431},[435,436,439,443,447,450],"span",{"class":437,"line":438},"line",1,[435,440,442],{"class":441},"snl16","from",[435,444,446],{"class":445},"s95oV"," fastapi ",[435,448,449],{"class":441},"import",[435,451,452],{"class":445}," FastAPI\n",[435,454,456,458,461,463],{"class":437,"line":455},2,[435,457,442],{"class":441},[435,459,460],{"class":445}," fastapi.responses ",[435,462,449],{"class":441},[435,464,465],{"class":445}," StreamingResponse\n",[435,467,469,471,474,477],{"class":437,"line":468},3,[435,470,449],{"class":441},[435,472,473],{"class":445}," numpy ",[435,475,476],{"class":441},"as",[435,478,479],{"class":445}," np\n",[435,481,483,485,488,490],{"class":437,"line":482},4,[435,484,449],{"class":441},[435,486,487],{"class":445}," matplotlib.pyplot ",[435,489,476],{"class":441},[435,491,492],{"class":445}," plt\n",[435,494,496,498],{"class":437,"line":495},5,[435,497,449],{"class":441},[435,499,500],{"class":445}," io\n",[435,502,504],{"class":437,"line":503},6,[435,505,507],{"emptyLinePlaceholder":506},true,"\n",[435,509,511,514,517,520,524,526,530],{"class":437,"line":510},7,[435,512,513],{"class":445},"app ",[435,515,516],{"class":441},"=",[435,518,519],{"class":445}," FastAPI(",[435,521,523],{"class":522},"s9osk","title",[435,525,516],{"class":441},[435,527,529],{"class":528},"sU2Wk","\"Sine Function Visualization API\"",[435,531,532],{"class":445},")\n",[435,534,536],{"class":437,"line":535},8,[435,537,507],{"emptyLinePlaceholder":506},[435,539,541],{"class":437,"line":540},9,[435,542,507],{"emptyLinePlaceholder":506},[435,544,546,550,553,556],{"class":437,"line":545},10,[435,547,549],{"class":548},"svObZ","@app.get",[435,551,552],{"class":445},"(",[435,554,555],{"class":528},"\"/sin\"",[435,557,532],{"class":445},[435,559,561,564,567],{"class":437,"line":560},11,[435,562,563],{"class":441},"def",[435,565,566],{"class":548}," plot_sin",[435,568,569],{"class":445},"(\n",[435,571,573,576,580,583,586],{"class":437,"line":572},12,[435,574,575],{"class":445},"    amplitude: ",[435,577,579],{"class":578},"sDLfK","float",[435,581,582],{"class":441}," =",[435,584,585],{"class":578}," 1.0",[435,587,588],{"class":445},",\n",[435,590,592,595,597,599],{"class":437,"line":591},13,[435,593,594],{"class":445},"    frequency: ",[435,596,579],{"class":578},[435,598,582],{"class":441},[435,600,601],{"class":578}," 1.0\n",[435,603,605],{"class":437,"line":604},14,[435,606,607],{"class":445},"):\n",[435,609,611],{"class":437,"line":610},15,[435,612,614],{"class":613},"sAwPA","    # Generate x range\n",[435,616,618,621,623,626,629,632,635,638,641,644],{"class":437,"line":617},16,[435,619,620],{"class":445},"    x ",[435,622,516],{"class":441},[435,624,625],{"class":445}," np.linspace(",[435,627,628],{"class":578},"0",[435,630,631],{"class":445},", ",[435,633,634],{"class":578},"2",[435,636,637],{"class":441}," *",[435,639,640],{"class":445}," np.pi, ",[435,642,643],{"class":578},"400",[435,645,532],{"class":445},[435,647,649,652,654,657,660,663,665],{"class":437,"line":648},17,[435,650,651],{"class":445},"    y ",[435,653,516],{"class":441},[435,655,656],{"class":445}," amplitude ",[435,658,659],{"class":441},"*",[435,661,662],{"class":445}," np.sin(frequency ",[435,664,659],{"class":441},[435,666,667],{"class":445}," x)\n",[435,669,671],{"class":437,"line":670},18,[435,672,507],{"emptyLinePlaceholder":506},[435,674,676],{"class":437,"line":675},19,[435,677,678],{"class":613},"    # Create figure\n",[435,680,682,685,687,690,693,695,697,700,702,705],{"class":437,"line":681},20,[435,683,684],{"class":445},"    fig, ax ",[435,686,516],{"class":441},[435,688,689],{"class":445}," plt.subplots(",[435,691,692],{"class":522},"figsize",[435,694,516],{"class":441},[435,696,552],{"class":445},[435,698,699],{"class":578},"8",[435,701,631],{"class":445},[435,703,704],{"class":578},"4",[435,706,707],{"class":445},"))\n",[435,709,711],{"class":437,"line":710},21,[435,712,713],{"class":445},"    ax.plot(x, y)\n",[435,715,717,720,723,726,729,732,735,738,740,743,745,748],{"class":437,"line":716},22,[435,718,719],{"class":445},"    ax.set_title(",[435,721,722],{"class":441},"f",[435,724,725],{"class":528},"\"y = ",[435,727,728],{"class":578},"{",[435,730,731],{"class":445},"amplitude",[435,733,734],{"class":578},"}",[435,736,737],{"class":528}," · sin(",[435,739,728],{"class":578},[435,741,742],{"class":445},"frequency",[435,744,734],{"class":578},[435,746,747],{"class":528},"x)\"",[435,749,532],{"class":445},[435,751,753,756,759],{"class":437,"line":752},23,[435,754,755],{"class":445},"    ax.set_xlabel(",[435,757,758],{"class":528},"\"x\"",[435,760,532],{"class":445},[435,762,764,767,770],{"class":437,"line":763},24,[435,765,766],{"class":445},"    ax.set_ylabel(",[435,768,769],{"class":528},"\"y\"",[435,771,532],{"class":445},[435,773,775,778,781],{"class":437,"line":774},25,[435,776,777],{"class":445},"    ax.grid(",[435,779,780],{"class":578},"True",[435,782,532],{"class":445},[435,784,786],{"class":437,"line":785},26,[435,787,507],{"emptyLinePlaceholder":506},[435,789,791],{"class":437,"line":790},27,[435,792,793],{"class":613},"    # Export image to memory buffer\n",[435,795,797,800,802],{"class":437,"line":796},28,[435,798,799],{"class":445},"    buf ",[435,801,516],{"class":441},[435,803,804],{"class":445}," io.BytesIO()\n",[435,806,808],{"class":437,"line":807},29,[435,809,810],{"class":445},"    plt.tight_layout()\n",[435,812,814,817,820,822,825],{"class":437,"line":813},30,[435,815,816],{"class":445},"    plt.savefig(buf, ",[435,818,819],{"class":522},"format",[435,821,516],{"class":441},[435,823,824],{"class":528},"\"png\"",[435,826,532],{"class":445},[435,828,830],{"class":437,"line":829},31,[435,831,832],{"class":445},"    plt.close(fig)\n",[435,834,836,839,841],{"class":437,"line":835},32,[435,837,838],{"class":445},"    buf.seek(",[435,840,628],{"class":578},[435,842,532],{"class":445},[435,844,846],{"class":437,"line":845},33,[435,847,507],{"emptyLinePlaceholder":506},[435,849,851,854],{"class":437,"line":850},34,[435,852,853],{"class":441},"    return",[435,855,856],{"class":445}," StreamingResponse(\n",[435,858,860],{"class":437,"line":859},35,[435,861,862],{"class":445},"        buf,\n",[435,864,866,869,871],{"class":437,"line":865},36,[435,867,868],{"class":522},"        media_type",[435,870,516],{"class":441},[435,872,873],{"class":528},"\"image/png\"\n",[435,875,877],{"class":437,"line":876},37,[435,878,879],{"class":445},"    )\n",[374,881],{},[394,883,885],{"id":884},"三這段程式碼在做什麼","三、這段程式碼在做什麼？",[358,887,888,889,892,893,895,896,898,899,901],{},"當 ",[421,890,891],{},"/sin"," API 被呼叫時，FastAPI 會先自動解析 URL 中的 ",[421,894,731],{}," 與 ",[421,897,742],{}," 參數，並轉換為 Python 的 ",[421,900,579],{}," 型別。",[358,903,904,905,387],{},"接著使用 NumPy 產生連續的 x 軸數值，並計算對應的 sin 函數結果。Matplotlib 只負責一件事：",[362,906,907],{},"把數學結果轉成圖形",[358,909,910,911,914],{},"真正關鍵的地方在於，圖片並沒有被存成檔案，而是透過 ",[421,912,913],{},"io.BytesIO()"," 直接寫進記憶體。FastAPI 最後將這個記憶體中的位元組資料，當作一個串流回傳給使用者。",[374,916],{},[394,918,920],{"id":919},"五啟動與測試方式","五、啟動與測試方式",[358,922,923],{},"在專案目錄中執行：",[426,925,929],{"className":926,"code":927,"language":928,"meta":431,"style":431},"language-bash shiki shiki-themes github-dark","uvicorn main:app --reload\n","bash",[421,930,931],{"__ignoreMap":431},[435,932,933,936,939],{"class":437,"line":438},[435,934,935],{"class":548},"uvicorn",[435,937,938],{"class":528}," main:app",[435,940,941],{"class":578}," --reload\n",[358,943,944],{},"接著打開瀏覽器，測試不同參數組合，例如：",[426,946,948],{"className":926,"code":947,"language":928,"meta":431,"style":431},"http://127.0.0.1:8000/sin\nhttp://127.0.0.1:8000/sin?amplitude=2\nhttp://127.0.0.1:8000/sin?amplitude=0.5&frequency=3\n",[421,949,950,955,963],{"__ignoreMap":431},[435,951,952],{"class":437,"line":438},[435,953,954],{"class":548},"http://127.0.0.1:8000/sin\n",[435,956,957,960],{"class":437,"line":455},[435,958,959],{"class":548},"http://127.0.0.1:8000/sin?amplitude",[435,961,962],{"class":528},"=2\n",[435,964,965,967,970,973,975],{"class":437,"line":468},[435,966,959],{"class":548},[435,968,969],{"class":528},"=0.5",[435,971,972],{"class":445},"&frequency",[435,974,516],{"class":441},[435,976,977],{"class":528},"3\n",[358,979,980],{},"你會發現，每次請求都會即時產生一張新的 sin 函數圖。",[377,982,984],{"id":983},"調用記憶體-buffer-的方式","調用記憶體 Buffer 的方式",[394,986,988],{"id":987},"一從需求出發fastapi-要的是什麼","一、從需求出發：FastAPI 要的是什麼？",[358,990,991,992,995,996,387],{},"FastAPI 在回傳圖片時，通常會搭配 ",[421,993,994],{},"StreamingResponse"," 使用。這類 response 並不在乎圖片是從哪裡來的，它只關心一件事：",[362,997,998],{},"是否能取得一個可讀取的 binary stream",[358,1000,1001],{},"也就是說，只要你能提供一個「像檔案一樣可以被讀取的位元組來源」，FastAPI 就能把它當成 HTTP 回應送給瀏覽器。這個來源可以是實體檔案，也可以是存在於記憶體中的資料流。",[374,1003],{},[394,1005,1007],{"id":1006},"二把圖畫在記憶體裡而不是硬碟上","二、把圖畫在記憶體裡，而不是硬碟上",[358,1009,1010],{},"以下是一段典型的寫法：",[426,1012,1014],{"className":428,"code":1013,"language":430,"meta":431,"style":431},"buf = io.BytesIO()\nplt.tight_layout()\nplt.savefig(buf, format=\"png\")\nplt.close(fig)\nbuf.seek(0)\n",[421,1015,1016,1025,1030,1043,1048],{"__ignoreMap":431},[435,1017,1018,1021,1023],{"class":437,"line":438},[435,1019,1020],{"class":445},"buf ",[435,1022,516],{"class":441},[435,1024,804],{"class":445},[435,1026,1027],{"class":437,"line":455},[435,1028,1029],{"class":445},"plt.tight_layout()\n",[435,1031,1032,1035,1037,1039,1041],{"class":437,"line":468},[435,1033,1034],{"class":445},"plt.savefig(buf, ",[435,1036,819],{"class":522},[435,1038,516],{"class":441},[435,1040,824],{"class":528},[435,1042,532],{"class":445},[435,1044,1045],{"class":437,"line":482},[435,1046,1047],{"class":445},"plt.close(fig)\n",[435,1049,1050,1053,1055],{"class":437,"line":495},[435,1051,1052],{"class":445},"buf.seek(",[435,1054,628],{"class":578},[435,1056,532],{"class":445},[358,1058,1059,1060,387],{},"這段程式的核心概念很單純：",[362,1061,1062,1063,1066],{},"Matplotlib 把畫好的圖直接輸出成 PNG 位元組，並存進 RAM，而不是寫成 ",[421,1064,1065],{},".png"," 檔案",[358,1068,1069,1071],{},[421,1070,913],{}," 建立了一個存在於記憶體中的位元組容器，它的行為和檔案非常像，可以被寫入、讀取、移動指標位置，但完全不會碰到磁碟。對後端服務而言，這代表一次 request 就對應一份暫時性的資料，不會留下任何痕跡。",[358,1073,888,1074,1077,1078,1081],{},[421,1075,1076],{},"plt.savefig()"," 將圖片寫入這個 buffer 時，Matplotlib 其實並不知道自己不是在寫檔案，它只負責把圖轉成 PNG 格式並輸出。這正是 ",[421,1079,1080],{},"BytesIO"," 的強大之處。",[374,1083],{},[394,1085,1087],{"id":1086},"三為什麼一定要把圖關掉","三、為什麼一定要把圖關掉？",[358,1089,1090,1091,1094],{},"在 API 環境中，",[421,1092,1093],{},"plt.close(fig)"," 這一行非常關鍵。",[358,1096,1097],{},"FastAPI 是一個長時間運作的 server，每一次 request 都可能產生一張新的圖。如果沒有明確關閉 figure，Matplotlib 內部的物件會不斷累積，最終導致記憶體用量失控，甚至出現 OOM（Out Of Memory）錯誤。",[358,1099,1100,1101,387],{},"因此，這種寫法並不是「寫好看而已」，而是",[362,1102,1103],{},"符合 server 等級的資源管理方式",[374,1105],{},[394,1107,1109,1110,1113],{"id":1108},"四為什麼還需要-seek0","四、為什麼還需要 ",[421,1111,1112],{},"seek(0)","？",[358,1115,1116,1117,1119],{},"當圖片資料被寫入 ",[421,1118,1080],{}," 後，讀取指標會停留在資料的結尾。如果此時直接交給 FastAPI 回傳，實際讀取到的會是「空資料」。",[358,1121,1122,1123,1126],{},"透過 ",[421,1124,1125],{},"buf.seek(0)","，我們明確地將指標移回開頭，確保後續讀取時能從 PNG 的第一個位元組開始。這個動作在檔案操作中非常常見，但在記憶體 buffer 中也同樣重要。",[374,1128],{},[394,1130,1132],{"id":1131},"五如果改用實體檔案問題會在哪","五、如果改用實體檔案，問題會在哪？",[358,1134,1135],{},"將圖片先存成檔案再回傳，在教學範例中或許可行，但在實際部署環境會帶來不少隱憂。當多個使用者同時請求 API 時，檔名衝突、寫入競爭、權限限制與磁碟 I/O 成本，都會讓系統變得脆弱且難以維護。",[358,1137,1138,1139,1142],{},"相較之下，使用記憶體 buffer 的方式是",[362,1140,1141],{},"無狀態（stateless）且併發安全的","，也更符合容器化部署與雲端環境的設計哲學。",[374,1144],{},[394,1146,1148],{"id":1147},"六這種寫法在業界的實際定位","六、這種寫法在業界的實際定位",[358,1150,1151],{},"直接將圖像寫入記憶體並透過 API 回傳，是目前資料視覺化服務的標準模式之一。無論是科學計算 API、機器學習推論結果的圖像輸出，或是天文、光譜等領域的即時繪圖服務，都大量採用這樣的設計。",[358,1153,1154,1155,1158],{},"它的核心優勢在於：",[362,1156,1157],{},"高效、乾淨、不依賴檔案系統","，同時也讓 API 本身保持單純。",[374,1160],{},[377,1162,1164],{"id":1163},"小結一句話理解這個模式","小結：一句話理解這個模式",[358,1166,1167],{},"Matplotlib 負責把圖畫好並轉成位元組資料，而 FastAPI 則直接把這些存在 RAM 裡的資料當成 HTTP 回應送出去。整個過程沒有落地檔案，也不留下任何狀態。",[358,1169,1170],{},"這正是後端 API 在處理「即時產生內容」時，最理想的一種實作方式。",[1172,1173,1174],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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);}",{"title":431,"searchDepth":455,"depth":455,"links":1176},[1177,1183,1192],{"id":379,"depth":455,"text":380,"children":1178},[1179,1180,1181,1182],{"id":396,"depth":468,"text":397},{"id":411,"depth":468,"text":412},{"id":884,"depth":468,"text":885},{"id":919,"depth":468,"text":920},{"id":983,"depth":455,"text":984,"children":1184},[1185,1186,1187,1188,1190,1191],{"id":987,"depth":468,"text":988},{"id":1006,"depth":468,"text":1007},{"id":1086,"depth":468,"text":1087},{"id":1108,"depth":468,"text":1189},"四、為什麼還需要 seek(0)？",{"id":1131,"depth":468,"text":1132},{"id":1147,"depth":468,"text":1148},{"id":1163,"depth":455,"text":1164},"學習如何使用 io.BytesIO 在 FastAPI 中即時產生並回傳 Matplotlib 圖表，避免實體檔案帶來的效能瓶頸。","md",null,{"tags":1197,"category":430,"date":1202},[1198,1199,430,1200,1201],"fastapi","matplotlib","memory-buffer","optimization","2026-02-03",{"title":168,"description":1193},"BLoE0gxC-afwqxQ3WqlfOfrEHsWmHlWrGF7krnyexac",[1206,1208],{"title":162,"path":163,"stem":164,"description":1207,"children":-1},"從基礎到進階的 Python 開發技巧，涵蓋效能優化、內存管理與自動化腳本。",{"title":172,"path":173,"stem":174,"description":1209,"children":-1},"介紹 Python 中常見的單元測試方法，包含 unittest 與 pytest 的基本概念、撰寫方式與實際範例，幫助你建立可靠的程式品質。",1776690843498]