{
  "version": "https://jsonfeed.org/version/1", 
  "title": "Python", 
  "description": "\u8fd9\u91cc\u8ba8\u8bba\u5404\u79cd Python \u8bed\u8a00\u7f16\u7a0b\u8bdd\u9898\uff0c\u4e5f\u5305\u62ec Django\uff0cTornado \u7b49\u6846\u67b6\u7684\u8ba8\u8bba\u3002\u8fd9\u91cc\u662f\u4e00\u4e2a\u80fd\u591f\u5e2e\u52a9\u4f60\u89e3\u51b3\u5b9e\u9645\u95ee\u9898\u7684\u5730\u65b9\u3002", 
  "home_page_url": "https://www.v2ex.com/go/python", 
  "feed_url": "https://www.v2ex.com/feed/python.json", 
  "icon": "https://cdn.v2ex.com/navatar/8613/985e/90_large.png?m=1648339948", 
  "favicon": "https://cdn.v2ex.com/navatar/8613/985e/90_normal.png?m=1648339948", 
  "items": [
    {
      "author": {
        "url": "https://www.v2ex.com/member/KingZZZZ", 
        "name": "KingZZZZ", 
        "avatar": "https://cdn.v2ex.com/avatar/790c/1ff3/525929_large.png?m=1713402403"
      }, 
      "url": "https://www.v2ex.com/t/1204542", 
      "title": "\u722c\u866b\u5f00\u53d1\u5de5\u4f5c\u4e2d\uff0c\u4f60\u4eec\u662f\u5982\u4f55\u57fa\u4e8e AI \u8fdb\u884c\u63d0\u6548\u7684\uff1f", 
      "id": "https://www.v2ex.com/t/1204542", 
      "date_published": "2026-04-09T03:09:13+00:00", 
      "content_html": "<p>\u5404\u4f4d\u5f66\u7956\uff0c\u7531\u4e8e\u516c\u53f8\u7ed9\u51fa\u4e86 AI \u63d0\u6548\u7684\u538b\u529b\uff0c\u60f3\u8bf7\u6559\u4e00\u4e0b\u5404\u4f4d\uff0c\u5728\u722c\u866b\u5f00\u53d1\u5de5\u4f5c\u4e2d\uff0c\u662f\u5982\u4f55\u57fa\u4e8e AI \u8fdb\u884c\u63d0\u6548\u7684\uff0c\u5e0c\u671b\u80fd\u6df1\u5165\u4e00\u4e9b\u3002</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/xFrank", 
        "name": "xFrank", 
        "avatar": "https://cdn.v2ex.com/gravatar/edc8cef8925e056a5cb3e532a12c97f8?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1202699", 
      "date_modified": "2026-04-01T04:44:16+00:00", 
      "content_html": "<p>\u5df2\u77e5\u5982\u4e0b python \u4ee3\u7801\u6587\u4ef6\uff08\u5df2\u7ecf\u662f\u5168\u90e8\u5185\u5bb9\uff09\uff1a</p>\n<pre><code>def foo():\n    print(\"start\")\n    xxxxxx\n    print(\"end\")\nif __name__ == \"__main__\":   \n    print(\"1111\")  \n    foo()\n    print(\"2222\")\n</code></pre>\n<pre><code>\u95ee\u9898\uff1a\u8bf7\u5c06 xxxxx \u66ff\u6362\u4e3a\u4e00\u884c python \u4ee3\u7801\uff0c\u4f7f\u8fd9\u4e2a python \u6587\u4ef6\u6267\u884c\u540e\u6700\u7ec8\u8f93\u51fa\u53ea\u6709 1111 \u548c 2222\n\u66ff\u6362\u4ee3\u7801\u8981\u6c42\uff1a\u53ea\u80fd\u6709\u4e00\u4e2a\u8bed\u53e5\uff0c\u53ea\u6709\u4e00\u884c\uff1b\u591a\u4e2a\u8bed\u53e5\u7ec4\u5408\u7684\u4e0d\u884c\uff08 import \u4e5f\u7b97\u4e00\u4e2a\u8bed\u53e5\uff0c\u56e0\u6b64\u7c7b\u4f3c import sys; sys.xxx \u7684\u4e0d\u884c\uff09\n</code></pre>\n<p>\u5f15\u7533\u95ee\u9898\uff1a\u4e0a\u8ff0\u66ff\u6362\u4ee3\u7801\u8981\u6c42\u653e\u5bbd\u4e3a\u201c\u5141\u8bb8\u5f15\u5165\u81ea\u5e26\u5e93\u5e76\u5141\u8bb8\u591a\u6761\u8bed\u53e5\u201d\uff0c\u662f\u5426\u8fd8\u6709\u522b\u7684\u65b9\u6848\uff1f</p>\n", 
      "date_published": "2026-04-01T02:12:53+00:00", 
      "title": "\u53d1\u73b0 Python \u4e00\u4e2a\u6709\u610f\u601d\u7684\u5c0f\u7279\u6027\uff0c\u53d1\u73b0\u5f88\u5408\u9002\u641e\u6210\u9762\u8bd5\u9898\u3002\u95ee\u4e86 AI \u90fd\u4e0d\u884c:)\uff0c\u6b22\u8fce\u6765\u6311\u6218~", 
      "id": "https://www.v2ex.com/t/1202699"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/shadowmeld", 
        "name": "shadowmeld", 
        "avatar": "https://cdn.v2ex.com/gravatar/1505c2d747d4973cd2d425518d573517?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1201368", 
      "title": "\u5927\u578b Python \u5f00\u6e90\u9879\u76ee\u90fd\u4e0d\u4f1a\u5bf9\u53d8\u91cf\u8fdb\u884c\u7c7b\u578b\u6ce8\u89e3\uff1f", 
      "id": "https://www.v2ex.com/t/1201368", 
      "date_published": "2026-03-26T08:58:06+00:00", 
      "content_html": "<p>\u5f88\u5947\u602a\u554a\uff0c\u6211\u95ee AI \u7684\u8bdd\uff0c\u5b83\u603b\u662f\u8bf4\u7ed9\u53d8\u91cf\u52a0\u7c7b\u578b\u6ce8\u89e3\u66f4\u597d\uff0c\u8bf4\u4ec0\u4e48\u73b0\u4ee3 Python \u5927\u578b\u9879\u76ee\u90fd\u8fd9\u6837\u505a\uff0cPython \u5b98\u65b9\u4e5f\u63a8\u8350\uff0c\u4f46\u662f\u6211\u8ba9\u5b83\u63a8\u8350\u51e0\u4e2a\u9879\u76ee\uff0c\u6211\u53bb\u770b\u6e90\u7801\uff0c\u6ca1\u6709\u4efb\u4f55\u4e00\u4e2a\u5f00\u6e90\u9879\u76ee\u5bf9\u53d8\u91cf\u5199\u4e86\u7c7b\u578b\u6ce8\u89e3\uff0c\u800c\u4e14 AI \u7684\u4ee3\u7801\u4e5f\u4e0d\u4f1a\u6709\u5bf9\u53d8\u91cf\u7684\u7c7b\u578b\u6ce8\u89e3\u3002\u4f46\u5b9e\u9645\u4e0a\u6839\u672c\u6ca1\u6709\u4efb\u4f55\u9879\u76ee\u8fd9\u6837\u505a\uff0c\u81f3\u5c11\u6211\u627e\u4e0d\u5230\u3002</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/programMrxu", 
        "name": "programMrxu", 
        "avatar": "https://cdn.v2ex.com/avatar/3681/f0ad/481564_large.png?m=1735196118"
      }, 
      "url": "https://www.v2ex.com/t/1200848", 
      "title": "\u4f7f\u7528 pycharm \u5f00\u53d1 Python \uff0c\u81ea\u5b9a\u4e49\u4ee3\u7801\u98ce\u683c\uff0c\u5e76\u5b9e\u65f6\u63d0\u793a", 
      "id": "https://www.v2ex.com/t/1200848", 
      "date_published": "2026-03-24T14:53:30+00:00", 
      "content_html": "<p><a href=\"github\" rel=\"nofollow\">https://github.com/xrl12/pycharm-format/blob/main/USAGE.md</a>\n<a href=\"release\" rel=\"nofollow\">https://github.com/xrl12/pycharm-format/releases/tag/1.0.0</a></p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/GPLer", 
        "name": "GPLer", 
        "avatar": "https://cdn.v2ex.com/gravatar/d157c8c4ad24344ad27169d12b516d0c?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1199650", 
      "date_modified": "2026-03-19T15:54:42+00:00", 
      "content_html": "<p>Astral \u65b9\u9762\u7684\u516c\u544a: <a href=\"https://astral.sh/blog/openai\" rel=\"nofollow\">https://astral.sh/blog/openai</a></p>\n<p>OpenAI \u65b9\u9762\u7684\u516c\u544a: <a href=\"https://openai.com/index/openai-to-acquire-astral/\" rel=\"nofollow\">https://openai.com/index/openai-to-acquire-astral/</a></p>\n<p>\u4ece\u4e4b\u524d\u7684 React \uff0c\u4e0a\u6b21\u7684 Bun \uff0c\u5230\u8fd9\u6b21\u7684 uv \uff0c\u4e0b\u4e00\u4e2a\u4f1a\u662f Vite \u5417\uff1f</p>\n", 
      "date_published": "2026-03-19T15:52:54+00:00", 
      "title": "\u521b\u9020\u4e86 uv \u7684 Astral \u516c\u53f8\u88ab OpenAI \u6536\u8d2d", 
      "id": "https://www.v2ex.com/t/1199650"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/Deteriorator", 
        "name": "Deteriorator", 
        "avatar": "https://cdn.v2ex.com/gravatar/ae89116bc2f881a5488ac50bf9b1f8ea?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1199608", 
      "title": "\u614e\u7528 PyCharm Remote Development \u529f\u80fd", 
      "id": "https://www.v2ex.com/t/1199608", 
      "date_published": "2026-03-19T12:28:01+00:00", 
      "content_html": "<p>\u6700\u8fd1\u88ab PyCharm \u7684 Remote Development \u529f\u80fd\u6298\u78e8\u7684\u6b32\u4ed9\u6b32\u6b7b\uff0c \u7ecf\u5e38\u65ad\u5f00\u540e\u65e0\u6cd5\u518d\u6b21\u8fde\u63a5</p>\n<p>\u5177\u4f53\u8868\u73b0\u4e3a\u670d\u52a1\u5668\u4e0a\u7684 PyCharm \u5df2\u7ecf\u5728\u7ebf\u4e86\uff0cJetBrains Gateway \u4e5f\u5df2\u7ecf\u5de5\u4f5c\u4e86\uff0c \u4f46\u662f PyCharm \u7a97\u53e3\u8d77\u4e0d\u6765\uff0c \u5728 Windows \u4e0a\u662f Gateway \u56fe\u6807\u5728\u72b6\u6001\u680f\u663e\u793a\u4e86\uff0cPyCharm \u7a97\u53e3\u5c31\u662f\u4e0d\u51fa\u6765\u3002</p>\n<p>\u53ea\u80fd\u5220\u9664\u914d\u7f6e\u91cd\u65b0\u914d\uff0c \u8017\u8d39\u4e86\u5927\u91cf\u65f6\u95f4\uff0c \u90fd\u8003\u8651\u4f7f\u7528 VSCode \u8fdb\u884c\u8fdc\u7a0b\u5f00\u53d1\u4e86\u3002</p>\n<p>\u7ecf\u8fc7\u5927\u91cf\u5b9e\u9a8c\uff0c \u76ee\u524d\u53d1\u73b0\u5728 2025.3.1 \u7248\u672c\u53ca\u4ee5\u4e0a\uff0c \u8fd9\u4e2a\u95ee\u9898\u5f88\u9891\u7e41\uff0c \u4f7f\u7528 2025.2.6 \u7248\u672c\u6ca1\u6709\u8fd9\u4e2a\u95ee\u9898\u3002</p>\n<p>\u771f\u662f\u88c2\u5f00\uff0c \u611f\u89c9\u65b0\u7248\u672c\u592a\u62c9\u4e86\u3002\u3002\u3002\u3002</p>\n<p>\u4e0d\u786e\u5b9a JetBrains \u7684\u5176\u4ed6 IDE \u662f\u4e0d\u662f\u4e5f\u6709\u8fd9\u4e2a\u95ee\u9898\uff0c \u6709\u7684\u8bdd\u4e5f\u53ef\u4ee5\u8bd5\u4e00\u4e0b\u8001\u7248\u672c</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/zxccv1876", 
        "name": "zxccv1876", 
        "avatar": "https://cdn.v2ex.com/gravatar/6a6e625d7b2cb16f79ca16f6c521b6f0?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1199233", 
      "title": "Python 3.15 JIT \u7684\u6700\u65b0\u8fdb\u5c55\uff0c\u5df2\u7ecf\u6709\u5927\u6982 5%\u7684\u6027\u80fd\u63d0\u5347\u4e86", 
      "id": "https://www.v2ex.com/t/1199233", 
      "date_published": "2026-03-18T07:32:11+00:00", 
      "content_html": "<p>\u53c2\u8003\uff1a <a href=\"https://fidget-spinner.github.io/posts/jit-on-track.html\" rel=\"nofollow\">https://fidget-spinner.github.io/posts/jit-on-track.html</a></p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/leemysw", 
        "name": "leemysw", 
        "avatar": "https://cdn.v2ex.com/gravatar/d10bea99ac4423d1aa721f74b3b33e2e?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1198963", 
      "title": "[\u5f00\u6e90] \u505a\u4e86\u4e2a feishu-docx\uff0c\u628a\u98de\u4e66\u77e5\u8bc6\u5e93\u53d8\u6210 AI \u66f4\u5bb9\u6613\u8bfb\u5199\u548c\u7ba1\u7406\u7684\u5185\u5bb9\u6e90\uff0c\u65b9\u4fbf\u7ed9 Agent \u7528", 
      "id": "https://www.v2ex.com/t/1198963", 
      "date_published": "2026-03-17T08:39:18+00:00", 
      "content_html": "<p>\u6700\u8fd1\u505a\u4e86\u4e2a\u5c0f\u5de5\u5177\uff1a<strong>feishu-docx</strong></p>\n<p>GitHub\uff1a\n<a href=\"https://github.com/leemysw/feishu-docx\" rel=\"nofollow\">https://github.com/leemysw/feishu-docx</a></p>\n<p>\u73b0\u5728\u5f88\u591a\u56e2\u961f\u6587\u6863\u90fd\u5728\u98de\u4e66\u91cc\uff0c\u4f46\u4e0d\u7ba1\u662f\u81ea\u5df1\u5199\u811a\u672c\uff0c\u8fd8\u662f\u7ed9 Claude / Codex / Cursor \u8fd9\u7c7b AI Agent \u7528\uff0c\u98de\u4e66\u5185\u5bb9\u90fd\u4e0d\u592a\u597d\u76f4\u63a5\u63a5\u5165\u3002<br/>\n\u4e8e\u662f\u5c31\u505a\u4e86\u8fd9\u4e2a\u5de5\u5177\uff0c\u76ee\u6807\u5f88\u660e\u786e\uff1a</p>\n<p><strong>\u628a\u98de\u4e66\u77e5\u8bc6\u5e93\u53d8\u6210 AI \u66f4\u5bb9\u6613\u8bfb\u5199\u548c\u7ba1\u7406\u7684\u5185\u5bb9\u6e90\u3002</strong></p>\n<p>\u76ee\u524d\u652f\u6301\u8fd9\u4e9b\u80fd\u529b\uff1a</p>\n<ol>\n<li>\n<p>\u5bfc\u51fa\u98de\u4e66\u5185\u5bb9\u4e3a Markdown</p>\n<ul>\n<li>docx</li>\n<li>sheet</li>\n<li>bitable</li>\n<li>wiki</li>\n<li>\u6574\u4e2a wiki space \u6279\u91cf\u5bfc\u51fa</li>\n</ul>\n</li>\n<li>\n<p>\u5199\u56de\u98de\u4e66\u6587\u6863</p>\n<ul>\n<li>\u521b\u5efa\u6587\u6863</li>\n<li>\u8ffd\u52a0 Markdown \u5185\u5bb9</li>\n<li>\u66f4\u65b0\u6307\u5b9a block</li>\n<li>\u652f\u6301\u4ece\u5fae\u4fe1\u516c\u4f17\u53f7\u6587\u7ae0 URL \u76f4\u63a5\u6293\u53d6\u540e\u521b\u5efa\u98de\u4e66\u6587\u6863</li>\n</ul>\n</li>\n<li>\n<p>\u4e91\u7a7a\u95f4\u7ba1\u7406</p>\n<ul>\n<li>\u5217\u51fa\u5e94\u7528\u4e91\u7a7a\u95f4 / \u4e2a\u4eba\u4e91\u7a7a\u95f4\u6587\u4ef6</li>\n<li>\u5220\u9664\u6587\u4ef6</li>\n<li>\u67e5\u770b / \u4fee\u6539\u516c\u5f00\u6743\u9650</li>\n<li>\u67e5\u770b / \u7ba1\u7406\u6743\u9650\u6210\u5458</li>\n<li>\u6279\u91cf\u6e05\u7a7a\uff08\u5e26\u53cc\u91cd\u786e\u8ba4\uff0c\u907f\u514d\u8bef\u5220\uff09</li>\n</ul>\n</li>\n<li>\n<p>\u66f4\u9002\u5408 AI / Agent \u4f7f\u7528</p>\n<ul>\n<li>\u53ef\u76f4\u63a5\u8f93\u51fa Markdown \u5230 stdout</li>\n<li>\u652f\u6301 block id \uff0c\u65b9\u4fbf\u540e\u7eed\u5b9a\u5411\u66f4\u65b0</li>\n<li>\u81ea\u5e26 skill \uff0c\u53ef\u4ee5\u63a5\u5230 Agent \u5de5\u4f5c\u6d41\u91cc</li>\n</ul>\n</li>\n</ol>\n<hr/>\n<p>\u5982\u679c\u4f60\u6709\u8fd9\u4e9b\u573a\u666f\uff0c\u53ef\u80fd\u4f1a\u6709\u70b9\u7528\uff1a</p>\n<ul>\n<li>\u60f3\u628a\u98de\u4e66\u77e5\u8bc6\u5e93\u5582\u7ed9 AI</li>\n<li>\u60f3\u505a\u5185\u90e8\u77e5\u8bc6\u5e93\u540c\u6b65 / \u5907\u4efd</li>\n<li>\u60f3\u8ba9 Agent \u81ea\u52a8\u751f\u6210\u5185\u5bb9\u540e\u5199\u56de\u98de\u4e66</li>\n<li>\u60f3\u7ba1\u7406\u5e94\u7528\u521b\u5efa\u7684\u98de\u4e66\u6587\u6863\u548c\u4e91\u7a7a\u95f4\u8d44\u6e90</li>\n</ul>\n<p>\u6b22\u8fce\u63d0 issue / PR \uff0c\u4e5f\u6b22\u8fce\u76f4\u63a5\u62cd\u9700\u6c42\u3002</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/mrytsr", 
        "name": "mrytsr", 
        "avatar": "https://cdn.v2ex.com/gravatar/29309537930f2a03c134f410d75ec86e?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1198097", 
      "title": "\u5f00\u6e90\u9879\u76ee\uff1a clawOS \u89e3\u51b3 openclaw \u6587\u4ef6\u7cfb\u7edf\uff0c\u4e00\u952e\u5b89\u88c5", 
      "id": "https://www.v2ex.com/t/1198097", 
      "date_published": "2026-03-13T08:55:57+00:00", 
      "content_html": "<p>linux \u6216\u8005 mac \u7cfb\u7edf\uff08 windows \u53ef\u4ee5\u5b89\u88c5\uff0c\u90e8\u5206\u529f\u80fd\u7528\u4e0d\u4e86\uff09</p>\n<h2>\u4e00\u952e\u5b89\u88c5</h2>\n<pre><code>pip install clawos\nclawos start \nclawos status \uff08\u67e5\u770b\u8fd0\u884c\u5bc6\u7801\u548c\u7aef\u53e3\uff09\n\u7136\u540e\u6253\u5f00\uff1a http://127.0.0.1:6002/\uff08\u6216\u8005\u4f60\u81ea\u5df1\u7684 IP \u57df\u540d\uff09\n</code></pre>\n<h2>\u4ed3\u5e93\uff08\u8be6\u7ec6\u4ecb\u7ecd\u89c1\u4ed3\u5e93\uff0c\u6709\u622a\u56fe\uff09</h2>\n<p><a href=\"https://github.com/mrytsr/clawos\" rel=\"nofollow\">mrytsr/clawos</a></p>\n<h2>\u529f\u80fd\u5217\u8868</h2>\n<ul>\n<li>\u652f\u6301 openclaw,nanobot,picoclaw \u5b89\u88c5\u914d\u7f6e</li>\n<li>\u6587\u4ef6\u7cfb\u7edf\u7ba1\u7406</li>\n<li>git \u4ed3\u5e93\u7ba1\u7406\uff08\u81ea\u52a8\u8bc6\u522b git \u4ed3\u5e93\uff09</li>\n<li>systemd \u7ba1\u7406</li>\n<li>\u652f\u6301\u7f51\u9875\u4e2d\u6253\u5f00\u7ec8\u7aef</li>\n<li>\u4e00\u4e2a\u597d\u7528\u7684\u6570\u636e\u5e93\u7ba1\u7406\u5668\uff08\u652f\u6301\u81ea\u7136\u8bed\u8a00\u751f\u6210 SQL \uff09</li>\n<li>\u78c1\u76d8\uff0c\u663e\u5361\uff0c\u8fdb\u7a0b\uff0cCRON \u7ba1\u7406</li>\n</ul>\n<p>\u57fa\u672c\u76f8\u5f53\u4e8e\u64cd\u4f5c\u7cfb\u7edf\uff0c\u529f\u80fd\u6301\u7eed\u589e\u5f3a\u4e2d</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/sunfinv", 
        "name": "sunfinv", 
        "avatar": "https://cdn.v2ex.com/gravatar/146e470f8688ae90a374cf3da251e0a5?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1197248", 
      "title": "\u6211\u7528\u514d\u8d39 A \u80a1\u6570\u636e\u505a\u4e86\u51e0\u4e2a\u5de5\u5177\uff0c\u6b22\u8fce\u4e00\u8d77\u5f00\u6e90\uff01", 
      "id": "https://www.v2ex.com/t/1197248", 
      "date_published": "2026-03-10T15:50:40+00:00", 
      "content_html": "<p>\u5927\u5bb6\u597d\uff01</p>\n<p>\u4e4b\u524d\u5206\u4eab\u4e86 <strong>finshare</strong>\uff08\u514d\u8d39 A \u80a1\u6570\u636e\u5e93\uff09\uff0c\u6700\u8fd1\u7528\u5b83\u505a\u4e86\u51e0\u4e2a\u5c0f\u5de5\u5177\uff0c\u4eca\u5929\u5206\u4eab\u4e0b\u3002</p>\n<hr/>\n<h2>3 \u5206\u949f\u505a\u4e00\u4e2a\u884c\u60c5\u770b\u677f</h2>\n<pre><code class=\"language-python\">pip install finshare streamlit plotly\n</code></pre>\n<pre><code class=\"language-python\">import streamlit as st\nimport finshare as fs\n\nst.title(\"\ud83d\udcc8 \u5b9e\u65f6\u884c\u60c5\")\nstocks = ['000001.SZ', '600519.SH', '300750.SZ']\nfor code in stocks:\n    snap = fs.get_snapshot_data(code)\n    st.metric(code, snap.last_price, f\"{snap.change_pct:.2f}%\")\n</code></pre>\n<p>\u8fd0\u884c <code>streamlit run <a href=\"http://app.py\" rel=\"nofollow\">app.py</a></code>\uff0c\u641e\u5b9a\uff01</p>\n<hr/>\n<h2>\u5df2\u5f00\u6e90\u7684\u9879\u76ee</h2>\n<table>\n<thead>\n<tr>\n<th>\u9879\u76ee</th>\n<th>\u529f\u80fd</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><a href=\"https://github.com/finvfamily/finboard\" rel=\"nofollow\">finboard</a></td>\n<td>\u5b9e\u65f6\u884c\u60c5\u770b\u677f</td>\n</tr>\n<tr>\n<td><a href=\"https://github.com/finvfamily/finscreener\" rel=\"nofollow\">finscreener</a></td>\n<td>\u9009\u80a1\u5668</td>\n</tr>\n</tbody></table><hr/>\n<h2>\u4e00\u8d77\u5f00\u6e90\uff01</h2>\n<p>\u57fa\u4e8e finshare \u53ef\u4ee5\u505a\uff1a</p>\n<ul>\n<li>\u9009\u80a1\u5668 / \u6761\u4ef6\u7b5b\u9009</li>\n<li>\u4ef7\u683c\u63d0\u9192\u673a\u5668\u4eba</li>\n<li>K \u7ebf\u56fe\u8868\u5de5\u5177</li>\n<li>\u6570\u636e\u5bfc\u51fa Excel</li>\n<li>...</li>\n</ul>\n<p><strong>fork \u9879\u76ee\uff0c\u81ea\u5df1\u6539\u6539\u5c31\u80fd\u7528\uff01</strong></p>\n<p>\u4f18\u79c0\u9879\u76ee\u6211\u4f1a\u63a8\u8350\u3001\u5e2e\u4f60\u5ba3\u4f20\uff5e</p>\n<hr/>\n<p>GitHub: <a href=\"https://github.com/finvfamily/finshare\" rel=\"nofollow\">https://github.com/finvfamily/finshare</a></p>\n<p>Discord: <a href=\"https://discord.gg/XT5f8ZGB\" rel=\"nofollow\">https://discord.gg/XT5f8ZGB</a></p>\n<p>\u6b22\u8fce\u6765\u73a9\uff01</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/sunfinv", 
        "name": "sunfinv", 
        "avatar": "https://cdn.v2ex.com/gravatar/146e470f8688ae90a374cf3da251e0a5?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1197162", 
      "title": "\u5206\u4eab\u4e00\u4e2a\u5b8c\u5168\u514d\u8d39\u7684\u4e2d\u56fd A \u80a1\u6570\u636e\u83b7\u53d6\u5e93", 
      "id": "https://www.v2ex.com/t/1197162", 
      "date_published": "2026-03-10T08:00:14+00:00", 
      "content_html": "<p>\u5927\u5bb6\u597d\uff01\u4eca\u5929\u60f3\u5206\u4eab\u4e00\u4e2a\u6211\u5f00\u6e90\u7684\u91d1\u878d\u6570\u636e\u83b7\u53d6\u5e93 finshare \uff0c\u5b8c\u5168\u514d\u8d39\uff0c\u65e0\u9700 API\nKey \u3002</p>\n<p>GitHub: <a href=\"https://github.com/finvfamily/finshare\" rel=\"nofollow\">https://github.com/finvfamily/finshare</a></p>\n<p>finshare \u6e90\u4e8e\u6211\u7684\u9879\u76ee\uff1a <a href=\"https://meepoquant.com/\" rel=\"nofollow\">https://meepoquant.com/</a></p>\n<p>\u7279\u6027\uff1a</p>\n<ul>\n<li>\u5b8c\u5168\u514d\u8d39\uff1a\u65e0\u9700 API Key \uff0c\u65e0\u8c03\u7528\u6b21\u6570\u9650\u5236</li>\n<li>\u591a\u6570\u636e\u6e90\uff1a\u4e1c\u65b9\u8d22\u5bcc\u3001\u817e\u8baf\u3001\u65b0\u6d6a\u3001\u901a\u8fbe\u4fe1\u3001BaoStock</li>\n<li>\u81ea\u52a8\u6545\u969c\u5207\u6362\uff1a\u6570\u636e\u6e90\u5931\u8d25\u65f6\u81ea\u52a8\u5207\u6362\u5907\u7528\u6e90</li>\n<li>\u9ad8\u6027\u80fd\uff1a\u652f\u6301\u5f02\u6b65\u6279\u91cf\u83b7\u53d6</li>\n<li>\u5185\u7f6e\u7f13\u5b58\uff1a\u51cf\u5c11\u91cd\u590d\u8bf7\u6c42</li>\n</ul>\n<p>\u5b89\u88c5\uff1a\npip install finshare</p>\n<p>\u5feb\u901f\u5f00\u59cb\uff1a\nimport finshare as fs</p>\n<h1>\u83b7\u53d6\u5386\u53f2 K \u7ebf\u6570\u636e</h1>\n<p>df = fs.get_historical_data('<a href=\"http://000001.SZ\" rel=\"nofollow\">000001.SZ</a>', start='2024-01-01', end='2024-12-31',\nadjust='qfq')</p>\n<h1>\u83b7\u53d6\u5b9e\u65f6\u5feb\u7167</h1>\n<p>snapshot = fs.get_snapshot_data('<a href=\"http://000001.SZ\" rel=\"nofollow\">000001.SZ</a>')</p>\n<h1>\u8d22\u52a1\u6570\u636e</h1>\n<p>df = fs.get_income('<a href=\"http://000001.SZ\" rel=\"nofollow\">000001.SZ</a>')  # \u5229\u6da6\u8868</p>\n<h1>\u7279\u8272\u6570\u636e</h1>\n<p>df = fs.get_money_flow('<a href=\"http://000001.SZ\" rel=\"nofollow\">000001.SZ</a>')  # \u8d44\u91d1\u6d41\u5411\ndf = fs.get_lhb()                     # \u9f99\u864e\u699c</p>\n<p>\u5f81\u96c6\u60f3\u6cd5\uff1a</p>\n<p>\u6211\u6b63\u5728\u5f00\u53d1 finquant \u5f00\u6e90\u91cf\u5316\u4ea4\u6613\u6846\u67b6\uff0c\u60f3\u6536\u96c6\u5927\u5bb6\u7684\u60f3\u6cd5\uff1a</p>\n<ul>\n<li>\u4f60\u60f3\u8981\u4ec0\u4e48\u6837\u7684\u4ea4\u6613\u7cfb\u7edf\uff1f</li>\n<li>\u9700\u8981\u54ea\u4e9b\u529f\u80fd\uff1f\uff08\u56de\u6d4b\u3001\u5b9e\u76d8\u3001\u56e0\u5b50\u5e93\u3001\u98ce\u63a7\u3001\u5b9e\u65f6\u4ea4\u6613\u7b49\uff09</li>\n</ul>\n<p>\u6b22\u8fce\u52a0\u5165 Discord \u793e\u7fa4\u4e00\u8d77\u8ba8\u8bba\uff1a <a href=\"https://discord.gg/XT5f8ZGB\" rel=\"nofollow\">https://discord.gg/XT5f8ZGB</a></p>\n<p>\u4e5f\u6b22\u8fce Star \u548c PR \uff01</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/MAzrael", 
        "name": "MAzrael", 
        "avatar": "https://cdn.v2ex.com/gravatar/3f898f487f765ebbc8a81f15176de4b2?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1195422", 
      "title": "AI \u5e94\u7528\uff08\u5982 RAG\uff09\u80cc\u666f\u4e0b\uff0c\u5982\u4f55\u9009\u62e9\u975e\u5173\u7cfb\u578b\u6570\u636e\u5e93", 
      "id": "https://www.v2ex.com/t/1195422", 
      "date_published": "2026-03-03T04:00:49+00:00", 
      "content_html": "<h1>\u6838\u5fc3\u9700\u6c42</h1>\n<h2>\u5411\u91cf\u68c0\u7d22</h2>\n<ol>\n<li>\u539f\u751f\u652f\u6301\u5411\u91cf\u68c0\u7d22</li>\n<li>\u5411\u91cf\u68c0\u7d22\u53ef\u80fd\u4f1a\u8de8\u7d22\u5f15\uff0c\u5982\u5206\u522b\u5728\u4e0d\u540c\u7684\u7d22\u5f15\uff08\u8868\uff09\u4e2d\u68c0\u7d22\uff0c\u662f\u5426\u6709\u4e00\u6b21\u8c03\u7528\u6267\u884c\u5168\u90e8\u68c0\u7d22\u7684</li>\n<li>\u591a\u5411\u91cf\u68c0\u7d22\uff0c\u5982\u8f93\u5165\u5411\u91cf\u6709\u591a\u4e2a\uff0c\u4e00\u6b21\u8c03\u7528\u5206\u522b\u6839\u636e\u6bcf\u4e2a\u5411\u91cf\u8fdb\u884c\u68c0\u7d22\u7136\u540e\u8fd4\u56de\u805a\u5408\u540e\u7684\u68c0\u7d22\u7ed3\u679c</li>\n</ol>\n<h2>\u6587\u6863\u68c0\u7d22</h2>\n<ol>\n<li>Lucene</li>\n<li>\u652f\u6301\u5206\u8bcd</li>\n<li>\u539f\u751f\u6216\u63d2\u4ef6\u652f\u6301\u5173\u8054\u68c0\u7d22\uff0c\u7c7b\u4f3c\u4e8e mysql \u7684 join \u3002\u5982 es \u53ef\u901a\u8fc7 Siren Federate \u63d2\u4ef6\u5b9e\u73b0</li>\n</ol>\n<h2>\u7f16\u7a0b\u8bed\u8a00</h2>\n<p>python</p>\n<h1>\u95ee\u9898</h1>\n<ol>\n<li>\u76ee\u524d\u662f\u5426\u6709\u6570\u636e\u5e93\u80fd\u591f\u540c\u65f6\u652f\u6301\u4e0a\u8ff0\u9700\u6c42\uff1f</li>\n<li>\u4e0d\u80fd\u540c\u65f6\u6ee1\u8db3\u9700\u6c42\u7684\u60c5\u51b5\u4e0b\u5982\u4f55\u9009\u62e9\uff1f</li>\n</ol>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/XIVN1987", 
        "name": "XIVN1987", 
        "avatar": "https://cdn.v2ex.com/avatar/c4ce/3bc8/138148_large.png?m=1772508180"
      }, 
      "url": "https://www.v2ex.com/t/1195409", 
      "date_modified": "2026-03-03T03:26:01+00:00", 
      "content_html": "<p>\u5c06\u4e8c\u7ef4\u6570\u7ec4 <code>lists = [[1, 2], [3, 4], [5]]</code> \u5c55\u5f00\u6210\u4e00\u7ef4\u6570\u7ec4 <code>[1, 2, 3, 4, 5]</code></p>\n<p>\u4e4b\u524d\u5199\u6cd5\uff1a<code>[x for L in lists for x in L]</code></p>\n<p>Python 3.15 \u65b0\u8bed\u6cd5\uff1a<code>[*L for L in lists]</code></p>\n<p>\u8fd9\u4e2a\u65b0\u8bed\u6cd5\u771f\u662f\u7b80\u6d01\u53c8\u76f4\u89c2\uff0c\uff0c\u8fd9\u4e48\u7b26\u5408\u76f4\u89c9\u7684\u8bed\u6cd5\u600e\u4e48\u4e4b\u524d\u6ca1\u60f3\u5230\u6dfb\u52a0\uff1f\uff1f</p>\n", 
      "date_published": "2026-03-03T03:25:39+00:00", 
      "title": "Python 3.15 \u5c06\u5f15\u5165\u4e00\u4e2a\u5f88\u65b9\u4fbf\u7684\u8bed\u6cd5\uff1a Unpacking in Comprehensions", 
      "id": "https://www.v2ex.com/t/1195409"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/mrytsr", 
        "name": "mrytsr", 
        "avatar": "https://cdn.v2ex.com/gravatar/29309537930f2a03c134f410d75ec86e?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1195187", 
      "title": "clawOS \u89e3\u51b3 openclaw \u6587\u4ef6\u7cfb\u7edf\u4ea4\u4e92\u8fc7\u4e8e\u62bd\u8c61\u95ee\u9898", 
      "id": "https://www.v2ex.com/t/1195187", 
      "date_published": "2026-03-02T06:43:21+00:00", 
      "content_html": "<p>linux \u6216\u8005 mac \u7cfb\u7edf\uff08 windows \u53ef\u4ee5\u5b89\u88c5\uff0c\u90e8\u5206\u529f\u80fd\u7528\u4e0d\u4e86\uff09</p>\n<h2>\u5b89\u88c5\u65b9\u4fbf</h2>\n<pre><code>pip install clawos\nclawos start \nclawos status \uff08\u67e5\u770b\u8fd0\u884c\u5bc6\u7801\u548c\u7aef\u53e3\uff09\n\u7136\u540e\u6253\u5f00\uff1a http://127.0.0.1:6002/\uff08\u6216\u8005\u4f60\u81ea\u5df1\u7684 IP \u57df\u540d\uff09\n</code></pre>\n<h2>\u4ed3\u5e93\uff08\u8be6\u7ec6\u4ecb\u7ecd\u89c1\u4ed3\u5e93\uff0c\u6709\u622a\u56fe\uff09</h2>\n<p><a href=\"https://github.com/mrytsr/clawos\" rel=\"nofollow\">mrytsr/clawos</a></p>\n<h2>\u529f\u80fd\u5217\u8868</h2>\n<ul>\n<li>\u652f\u6301 openclaw,nanobot,picoclaw \u5b89\u88c5\u914d\u7f6e</li>\n<li>\u6587\u4ef6\u7cfb\u7edf\u7ba1\u7406</li>\n<li>git \u4ed3\u5e93\u7ba1\u7406\uff08\u81ea\u52a8\u8bc6\u522b git \u4ed3\u5e93\uff09</li>\n<li>systemd \u7ba1\u7406</li>\n<li>\u652f\u6301\u7f51\u9875\u4e2d\u6253\u5f00\u7ec8\u7aef</li>\n<li>\u4e00\u4e2a\u597d\u7528\u7684\u6570\u636e\u5e93\u7ba1\u7406\u5668\uff08\u652f\u6301\u81ea\u7136\u8bed\u8a00\u751f\u6210 SQL \uff09</li>\n<li>\u78c1\u76d8\uff0c\u663e\u5361\uff0c\u8fdb\u7a0b\uff0cCRON \u7ba1\u7406</li>\n</ul>\n<p>\u57fa\u672c\u76f8\u5f53\u4e8e\u64cd\u4f5c\u7cfb\u7edf\uff0c\u529f\u80fd\u6301\u7eed\u589e\u5f3a\u4e2d</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/lanshiL3C", 
        "name": "lanshiL3C", 
        "avatar": "https://cdn.v2ex.com/gravatar/6e4090efe90f7cb7e226c8cb6e04ae70?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1195005", 
      "date_modified": "2026-03-01T10:45:10+00:00", 
      "content_html": "<h2>\u89e3\u51b3\u4ec0\u4e48\u95ee\u9898\uff1f</h2>\n<p>\u505a\u673a\u5668\u5b66\u4e60\u8c03\u53c2\u65f6\uff1a</p>\n<ul>\n<li>\u5199 Bash \u5faa\u73af\u811a\u672c\u7e41\u7410</li>\n<li>\u65e5\u5fd7\u6df7\u5728\u4e00\u8d77\u96be\u5b9a\u4f4d</li>\n<li>\u96be\u4ee5\u7ba1\u7406\u8bb0\u5f55\u8fd0\u884c\u8fc7\u7684\u547d\u4ee4</li>\n<li>\u53c2\u6570\u548c\u7ed3\u679c\u96be\u5bf9\u5e94</li>\n</ul>\n<hr/>\n<h2>pyruns \u505a\u4e86\u4ec0\u4e48\uff1f</h2>\n<p><strong>\u7ed9 python \u811a\u672c\u52a0\u4e2a\u6d4f\u89c8\u5668\u754c\u9762\uff0c\u53ef\u89c6\u5316\u7ba1\u7406\u5b9e\u9a8c\u4efb\u52a1</strong></p>\n<p>\u6838\u5fc3\u7279\u6027\uff1a</p>\n<ul>\n<li>\ud83d\udd0c <strong>\u96f6\u6539\u52a8\u63a5\u5165</strong> - \u81ea\u52a8\u89e3\u6790 <code>argparse</code>\uff0c\u751f\u6210 Web UI \u8868\u5355</li>\n<li>\ud83e\uddee <strong>\u6279\u91cf\u5c55\u5f00</strong> - \u7528 <code>lr: 0.001 | 0.01 | 0.1</code> \u81ea\u52a8\u751f\u6210\u591a\u4e2a\u4efb\u52a1</li>\n<li>\ud83d\udccb <strong>\u72b6\u6001\u7ba1\u7406</strong> - \u6d4f\u89c8\u5668\u67e5\u770b\u6240\u6709\u4efb\u52a1\u72b6\u6001\u3001\u5386\u53f2\u8bb0\u5f55</li>\n<li>\ud83d\udda5\ufe0f <strong>\u9694\u79bb\u65e5\u5fd7</strong> - \u6bcf\u4e2a\u4efb\u52a1\u72ec\u7acb\u76ee\u5f55\uff0cSSH \u65ad\u5f00\u4e5f\u80fd\u7ee7\u7eed\u770b</li>\n<li>\ud83d\udcca <strong>\u6307\u6807\u5bfc\u51fa</strong> - \u8de8\u4efb\u52a1\u5bf9\u6bd4\uff0c\u5bfc\u51fa CSV \u62a5\u8868</li>\n</ul>\n<hr/>\n<h2>\u4f7f\u7528\u65b9\u5f0f</h2>\n<pre><code class=\"language-bash\">pip install pyruns\npyr train.py  # \u6253\u5f00 http://localhost:8099\n</code></pre>\n<p><strong>\u65e0\u9700\u4fee\u6539\u6e90\u7801</strong>\uff0c\u76f4\u63a5\u542f\u52a8\u3002</p>\n<hr/>\n<h2>\u94fe\u63a5</h2>\n<ul>\n<li>GitHub: <a href=\"https://github.com/LthreeC/pyruns\" rel=\"nofollow\">https://github.com/LthreeC/pyruns</a></li>\n<li>\u6587\u6863: <a href=\"https://lthreec.github.io/pyruns/\" rel=\"nofollow\">https://lthreec.github.io/pyruns/</a></li>\n</ul>\n<p>\u6b22\u8fce\u8bd5\u7528\uff0cStar \ud83c\udf1f \u652f\u6301\uff01</p>\n", 
      "date_published": "2026-03-01T10:44:47+00:00", 
      "title": "[\u5f00\u6e90] pyruns - \u7ed9 Python \u811a\u672c\u52a0\u4e2a Web UI\uff0c\u7ba1\u7406\u6279\u91cf\u5b9e\u9a8c\u4efb\u52a1", 
      "id": "https://www.v2ex.com/t/1195005"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/kangkona", 
        "name": "kangkona", 
        "avatar": "https://cdn.v2ex.com/gravatar/f2245d4f48d185a8b8f80e56e69b1402?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1194075", 
      "title": "\u6211\u5f00\u6e90\u4e86 Python \u7248\u672c\u7684 pi-mono(OpenClaw \u7684\u5e95\u5c42\u7ec4\u4ef6)", 
      "id": "https://www.v2ex.com/t/1194075", 
      "date_published": "2026-02-25T11:14:24+00:00", 
      "content_html": "<p>2 \u6708\u4efd\u7684\u670b\u53cb\u5708\u90fd\u88ab OpenClaw \u9738\u699c\u4e86\uff0c\u5168\u4e16\u754c\u90fd\u5728\u201c\u517b\u867e\u201d\uff0c\u5404\u79cd\u73a9\u6cd5\u3001\u5404\u79cd\u63a5\u5165\u3001\u5404\u79cd\u5de5\u4f5c\u6d41\u5206\u4eab\u5237\u5c4f\u3002</p>\n<p>\u770b\u7740 TypeScript \u751f\u6001\u91cc\u5404\u79cd\u201c\u517b\u867e\u5e95\u5ea7\u201d\u5c42\u51fa\u4e0d\u7a77\uff1b\u53cd\u89c2 Python \u793e\u533a\uff0c\u5927\u5bb6\u8981\u4e48\u5728\u624b\u6413\u4e00\u5806 if/else \uff0c\u8981\u4e48\u5728\u548c\u8fc7\u5ea6\u5c01\u88c5\u7684\u6c89\u91cd\u6846\u67b6\u640f\u6597\uff0c\u5b9e\u5728\u6709\u70b9\u51b7\u6e05\u3002</p>\n<p>\u4e8e\u662f\uff0c\u6211\u51b3\u5b9a\u5728\u5f00\u5de5\u7b2c\u4e00\u5929\uff0c\u6b63\u5f0f\u5f00\u542f Python \u5708\u7684\u201c\u517b\u732a\u201d\u4e8b\u4e1a\u2014\u2014\u641e\u4e86\u4e2a\u53eb pig-mono \uff08\ud83d\udc37\uff09 \u7684\u5f00\u6e90\u9879\u76ee\u3002\u4f60\u53ef\u4ee5\u628a\u5b83\u7406\u89e3\u6210 pi-mono \uff08 OpenClaw \u7684\u5e95\u5ea7\uff09\u5728 Python \u751f\u6001\u7684\u5bf9\u5e94\u7248\u672c\u3002</p>\n<p>GitHub \u5730\u5740\uff1a <a href=\"https://github.com/kangkona/pig-mono\" rel=\"nofollow\">https://github.com/kangkona/pig-mono</a> \uff08\u6b22\u8fce\u611f\u5174\u8da3\u670b\u53cb Star \u3001\u63d0 Issues \u3001PR \uff09</p>\n<h1>\u5148\u8bb2\u6e05\u695a\uff1apig-mono \u60f3\u505a\u4ec0\u4e48\uff1f</h1>\n<p>\u5728\u6211\u773c\u91cc\uff0cAgent \u5e94\u7528\u7684\u6838\u5fc3\u65e9\u5c31\u4e0d\u53ea\u662f\u201c\u5199 prompt\u201d\u4e86\uff0c\u800c\u662f\u8981\u628a\u5b83\u5f53\u6210\u4e00\u4e2a\u53ef\u8fed\u4ee3\u3001\u53ef\u6d4b\u8bd5\u3001\u53ef\u90e8\u7f72\u7684\u5de5\u7a0b\u7cfb\u7edf\u3002</p>\n<p>\u8fd9\u4e5f\u662f pig-mono \u6700\u6838\u5fc3\u7684\u8bbe\u8ba1\u7406\u5ff5\uff1a\u4e3a Python \u5f00\u53d1\u8005\u63d0\u4f9b\u4e00\u5957\u6807\u51c6\u7684 Agent Harness \uff08\u667a\u80fd\u4f53\u8fd0\u884c\u4e0e\u6d4b\u8bd5\u5e95\u5ea7\uff09\u3002</p>\n<p>\u4e0d\u641e\u9ed1\u76d2\u9b54\u6cd5\uff0c\u800c\u662f\u50cf\u4f20\u7edf\u7684\u6d4b\u8bd5/\u8fd0\u884c\u5e95\u5ea7\u4e00\u6837\uff0c\u628a\u4e0d\u53ef\u63a7\u7684 LLM \u201c\u5927\u8111\u201d\uff0c\u4e0e\u53ef\u63a7\u7684\u5de5\u5177\uff08 Hands \uff09\u3001\u8bb0\u5fc6\uff08 State \uff09\u3001\u73af\u5883\uff08 Environment \uff09\u5f7b\u5e95\u89e3\u8026\u3002\u5b83\u7684\u76ee\u6807\u662f\uff1a</p>\n<p>\u7edf\u4e00 LLM \u8bbf\u95ee\u5c42\uff1a\u540c\u4e00\u5957 API \u63a5 14 \u5bb6 Provider \uff08\u542b OpenRouter \u3001Bedrock \u3001DeepSeek \u7b49\uff09\u3002\n\u63d0\u4f9b\u5b8c\u6574\u7684 Agent \u8fd0\u884c\u65f6\uff1a\u5de5\u5177\u6ce8\u518c\u3001\u5de5\u5177\u53c2\u6570\u6821\u9a8c\u3001\u6d88\u606f\u5386\u53f2\u3001\u72b6\u6001\u7ba1\u7406\u3001\u5f02\u6b65\u6267\u884c\u2026\u2026\u80fd\u8dd1\u3001\u597d\u8c03\u3001\u53ef\u590d\u7528\u3002\n\u6253\u901a\u771f\u5b9e\u4e16\u754c\u7684\u201c\u5165\u53e3\u201d\uff1aCLI Coding Agent \u3001Web Chat UI \u3001\u591a\u5e73\u53f0\u6d88\u606f\u673a\u5668\u4eba\uff08 Slack/\u98de\u4e66/TG \u7b49\uff09\u3002\nMonorepo \u6a21\u5757\u5316\u6c89\u6dc0\uff1a\u6309\u9700\u5b89\u88c5\u3001\u6309\u9700\u7ec4\u5408\uff0c\u7edd\u4e0d\u5f3a\u4e70\u5f3a\u5356\u3002\n\u672a\u6765\u6269\u5c55\u66f4\u591a\u53ef\u76f4\u63a5\u63d2\u62d4\u7684\u3001\u6846\u67b6\u65e0\u5173\u7684 Agent \u80fd\u529b\u7ec4\u4ef6</p>\n<h1>\u6838\u5fc3\u6a21\u5757\u901f\u89c8\uff1a\u4ece\u54ea\u91cc\u5f00\u59cb\u73a9\uff1f</h1>\n<p>pig-mono \u76ee\u524d\u5305\u542b\u591a\u4e2a\u53ef\u72ec\u7acb\u5b89\u88c5\u7684\u5305\uff08\u5c31\u50cf\u4e50\u9ad8\u79ef\u6728\uff09\uff1a\n\u5305\u540d</p>\n<p>\u4f60\u4f1a\u7528\u5b83\u6765\u505a\u4ec0\u4e48</p>\n<p>pig-llm\uff1a\u7edf\u4e00 LLM API\uff1acomplete/stream \u3001\u7528\u91cf\u7edf\u8ba1\u3001\u5931\u8d25\u91cd\u8bd5/\u515c\u5e95\u7b49</p>\n<p>pig-agent-core\uff1aAgent \u8fd0\u884c\u65f6\uff1a\u5de5\u5177\u7cfb\u7edf\u3001\u6d88\u606f\u5386\u53f2\u3001\u72b6\u6001\u4fdd\u5b58/\u6062\u590d\u3001\u5f02\u6b65\u7b49</p>\n<p>pig-coding-agent\uff1a\u4ea4\u4e92\u5f0f Coding Agent CLI\uff1a\u8bfb\u5199\u6587\u4ef6\u3001\u6267\u884c\u547d\u4ee4\u3001\u91cd\u6784\u3001\u5206\u6790</p>\n<p>pig-web-ui\uff1aWeb Chat UI \uff08 FastAPI \uff09</p>\n<p>pig-tui\uff1a\u7ec8\u7aef UI \uff08\u66f4\u53cb\u597d\u5730\u4ea4\u4e92/\u5c55\u793a\uff09</p>\n<p>pig-messenger\uff1a\u591a\u5e73\u53f0 Bot \u6846\u67b6\uff1a\u540c\u4e00\u4e2a agent \u63a5\u591a\u6d88\u606f\u5e73\u53f0</p>\n<h2>1 \uff09 pig-llm\uff1a14 \u5bb6\u5927\u6a21\u578b\uff0c1 \u5957\u63a5\u53e3</h2>\n<p>\u5982\u679c\u4f60\u53ea\u60f3\u8981\u4e00\u4e2a\u201c\u597d\u7528\u3001\u80fd\u6362\u3001\u652f\u6301\u6d41\u5f0f\u201d\u7684 LLM \u5ba2\u6237\u7aef\uff0c\u8fd9\u662f\u6700\u8f7b\u91cf\u7684\u5165\u53e3\u3002\n\u5b89\u88c5\uff1a\n<code>pip install pig-llm</code></p>\n<p>\u6700\u5c0f\u793a\u4f8b\uff1a</p>\n<pre><code class=\"language-python\">from pig_llm import LLM\nllm = LLM(provider=\"openai\", api_key=\"sk-...\", model=\"gpt-4o-mini\")\nresp = llm.complete(\"What is the meaning of life?\")\nprint(resp.content)\n\n# \u652f\u6301\u4e1d\u6ed1\u7684\u6d41\u5f0f\u8f93\u51fa\nfor chunk in llm.stream(\"Tell me a story\"):\n  print(chunk.content, end=\"\", flush=True)\n</code></pre>\n<p>\u652f\u6301 OpenAI / Anthropic / Google / DeepSeek / OpenRouter / xAI \u7b49 14 \u5bb6\uff0c\u4e5f\u53ef\u81ea\u5b9a\u4e49\u3002</p>\n<h2>2 \uff09 pig-agent-core\uff1a\u8d70\u5411\u6807\u51c6\u5316\u7684 Agent Harness</h2>\n<p>\u5f88\u591a Agent \u5199\u5230\u6700\u540e\u53d8\u6210\u4e86\uff1a\u4e00\u5806\u624b\u5199 JSON schema \uff0c\u5916\u52a0\u201c\u4e0d\u77e5\u9053\u4e3a\u4ec0\u4e48\u53c8\u5fd8\u4e86\u4e0a\u4e0b\u6587\u201d\u7684\u7384\u5b66 bug \u3002\u66f4\u53ef\u6015\u7684\u662f\u2014\u2014\u6839\u672c\u6ca1\u6cd5\u505a\u81ea\u52a8\u5316 Evals \u3002</p>\n<p>\u8bf4\u5b9e\u8bdd\uff0c\u8981\u5728 Python \u751f\u6001\u91cc\u505a\u4e00\u4e2a 100% \u5b8c\u7f8e\u7684 Harness \u8fd8\u6709\u5f88\u957f\u7684\u8def\u8981\u8d70\uff0c\u4f46 pig-agent-core \u4ece\u7b2c\u4e00\u5929\u7684\u5e95\u5c42\u8bbe\u8ba1\uff0c\u5c31\u662f\u5954\u7740\u8fd9\u4e2a\u613f\u666f\u53bb\u7684\u3002\u5b83\u5e2e\u4f60\u628a\u201c\u5927\u6a21\u578b\u7684\u6587\u5b57\u63a5\u9f99\u201d\u5305\u88c5\u6210\u4e00\u4e2a\u8f93\u5165\u8f93\u51fa\u9ad8\u5ea6\u786e\u5b9a\u3001\u968f\u65f6\u53ef\u4ee5\u6253\u65ad/\u6062\u590d\u7684\u72b6\u6001\u673a\uff1a</p>\n<p>\u89e3\u8026\u6267\u884c\uff1aLLM \u53ea\u8d1f\u8d23\u601d\u8003\uff0cHarness \u8d1f\u8d23\u5b89\u5168\u7684\u5de5\u5177\u8c03\u5ea6\u548c\u53c2\u6570\u6821\u9a8c\uff08\u57fa\u4e8e Pydantic \uff09\u3002</p>\n<p>\u72b6\u6001\u4e0e\u8bb0\u5fc6\u5206\u79bb\uff1a\u5bf9\u8bdd\u5386\u53f2\u548c\u5185\u90e8\u72b6\u6001\u652f\u6301\u5e8f\u5217\u5316\u5b58\u53d6\uff0c\u968f\u65f6\u53ef\u4ee5 dump \u6210 JSON \u6062\u590d\u3002\u8fd9\u4f7f\u5f97\u9488\u5bf9 Agent \u7684\u5355\u6d4b\u548c\u56de\u5f52\u6d4b\u8bd5\u7ec8\u4e8e\u6210\u4e3a\u53ef\u80fd\u3002</p>\n<p>\u5b89\u88c5\uff1a\n<code>pip install pig-agent-core</code>\n\u5b9a\u4e49\u5de5\u5177\uff08\u88c5\u9970\u5668\u6ce8\u518c\uff09\uff1a</p>\n<pre><code>from pig_agent_core import Agent, tool\n\n@tool(description=\"Get current weather for a location\")\ndef get_weather(location: str) -&gt; str:\n  return f\"Weather in {location}: Sunny, 72\u00b0F\"\n\u628a\u5de5\u5177\u585e\u8fdb Agent \u7136\u540e\u8dd1\u8d77\u6765\uff1a\nfrom pig_llm import LLM\n\nagent = Agent(\n  name=\"WeatherBot\",\n  llm=LLM(provider=\"openai\"),\n  tools=[get_weather],\n  system_prompt=\"You are a helpful weather assistant.\",\n)\n\nresponse = agent.run(\"What's the weather in Paris?\")\nprint(response.content)\n</code></pre>\n<p>\u5b83\u8fd8\u652f\u6301\uff1a\n\u6d88\u606f\u5386\u53f2\uff1a\u591a\u8f6e\u5bf9\u8bdd\u8ffd\u8e2a\n\u5f02\u6b65\uff1aasync/await\n\u72b6\u6001\u4fdd\u5b58/\u6062\u590d\uff1a\u4f8b\u5982\u628a\u4f1a\u8bdd\u5b58\u6210 JSON \uff0c\u4e4b\u540e\u7ee7\u7eed\u8dd1\n\u53c2\u6570\u6821\u9a8c\uff1a\u4e5f\u652f\u6301\u7528 Pydantic \u5b9a\u4e49\u5de5\u5177\u53c2\u6570\u6a21\u578b</p>\n<h2>3 \uff09 pig-messenger\uff1a\u4e00\u5957 Agent \u540c\u65f6\u8dd1 Slack / Discord / Telegram / WhatsApp / \u98de\u4e66</h2>\n<p>\u8fd9\u662f\u6211\u4e2a\u4eba\u6700\u60f3\u8865\u9f50\u7684\u4e00\u5757\uff1a\u5165\u53e3\u7684\u591a\u6837\u6027\u3002\n\u5f88\u591a\u56e2\u961f\u7684 Agent \u4e0d\u662f\u7ed9\u201c\u7f51\u9875\u7528\u6237\u201d\u7528\u7684\uff0c\u800c\u662f\u8981\u8fdb\u5165\u6d88\u606f\u5e73\u53f0\uff0c\u6210\u4e3a\u56e2\u961f\u534f\u4f5c\u6d41\u7684\u4e00\u90e8\u5206\u3002pig-messenger \u63d0\u4f9b\u4e00\u4e2a\u7edf\u4e00\u7684 MessengerBot \uff0c\u518d\u901a\u8fc7 Adapter \u8fde\u63a5\u4e0d\u540c\u5e73\u53f0\uff1a\n\u5b89\u88c5\uff1a</p>\n<pre><code>pip install pig-messenger # \u57fa\u7840\npip install pig-messenger[slack] # \u5e26 Slack\npip install pig-messenger[all] # \u5168\u5e73\u53f0\n</code></pre>\n<p>Slack Bot \u793a\u4f8b\uff1a</p>\n<pre><code>import os\nfrom pig_messenger import MessengerBot\nfrom pig_messenger.adapters import SlackAdapter\nfrom pig_agent_core import Agent\nfrom pig_llm import LLM\n\nagent = Agent(\n  llm=LLM(\n    provider=\"openrouter\",\n    model=\"moonshotai/kimi-k2.5\",\n    api_key=os.environ[\"OPENROUTER_API_KEY\"],\n    ),\n)\nbot = MessengerBot(agent)\nbot.add_platform(\n  SlackAdapter(\n    app_token=os.environ[\"SLACK_APP_TOKEN\"],\n    bot_token=os.environ[\"SLACK_BOT_TOKEN\"],\n  )\n)\nbot.start()\n</code></pre>\n<p>\u591a\u5e73\u53f0\u5e76\u884c\u8dd1\uff08\u793a\u610f\uff09\uff1a</p>\n<pre><code>from pig_messenger.adapters import SlackAdapter, DiscordAdapter, TelegramAdapter\nbot = MessengerBot(agent)\nbot.add_platform(SlackAdapter(...))\nbot.add_platform(DiscordAdapter(bot_token=os.environ[\"DISCORD_BOT_TOKEN\"]))\nbot.add_platform(TelegramAdapter(bot_token=os.environ[\"TELEGRAM_BOT_TOKEN\"]))\nbot.start()\n</code></pre>\n<h2>4 \uff09 pig-coding-agent\uff1a\u4ea4\u4e92\u5f0f Coding CLI</h2>\n<p>\u5982\u679c\u4f60\u60f3\u4f53\u9a8c\u201cAgent \u5728\u672c\u5730\u771f\u5b9e\u5e72\u6d3b\u201d\uff0c\u53ef\u4ee5\u4ece pig-coding-agent \u5f00\u59cb\u3002\n\u5b89\u88c5\uff1a\n<code>pip install pig-coding-agent</code>\n\u542f\u52a8\u4ea4\u4e92\u5f0f\u4f1a\u8bdd\uff1a\n$ pig-code\n\u4f60\u53ef\u4ee5\u628a\u5b83\u5f53\u4f5c\u4e00\u4e2a\u201c\u4f1a\u8bfb\u5199\u6587\u4ef6\u3001\u80fd\u6267\u884c\u547d\u4ee4\u3001\u4f1a\u5206\u6790\u548c\u91cd\u6784\u201d\u7684\u7f16\u7a0b\u52a9\u624b\u3002\u9879\u76ee README \u91cc\u7ed9\u4e86\u4e00\u4e9b\u5178\u578b\u7528\u6cd5\uff0c\u4f8b\u5982\uff1a</p>\n<pre><code>pig-code gen \"Create a FastAPI hello world app\"\npig-code analyze main.py\npig-code refactor main.py \"Add type hints\"\n</code></pre>\n<p>5 \u5206\u949f\u4e0a\u624b\uff1a\u4ece\u6e90\u7801\u5f00\u53d1\uff08 Monorepo \uff09\n\u5982\u679c\u4f60\u60f3\u53c2\u4e0e\u5f00\u53d1\u6216\u76f4\u63a5\u4ece\u6e90\u7801\u8dd1\u8d77\u6765\uff1a</p>\n<pre><code>git clone\ncd pig-mono\n\n# \u5b89\u88c5\u5f00\u53d1\u4f9d\u8d56\npip install -e \".[dev]\"\n\n# \u5b89\u88c5\u6240\u6709 packages \uff08\u53ef\u7f16\u8f91\u6a21\u5f0f\uff09\n./scripts/install-dev.sh\n\n# \u8dd1\u6d4b\u8bd5\n./scripts/test.sh\n</code></pre>\n<h1>\u4f60\u53ef\u80fd\u4f1a\u95ee\uff1a\u4e3a\u4ec0\u4e48\u53eb \u201cpig-mono\u201d\uff1f</h1>\n<p>\u5176\u5b9e\u4e00\u5f00\u59cb\u60f3\u53eb py-mono \uff08\u76f4\u767d\u597d\u61c2\uff0cPython \u7248 pi-mono \uff09\u3002\u7ed3\u679c\u51c6\u5907\u53d1\u5305\u65f6\u53d1\u73b0\u540d\u5b57\u5728 PyPI \u88ab\u5360\u7528\u4e86\u3002</p>\n<p>\u4e8e\u662f\u987a\u52bf\u6539\u6210\u4e86 pig-mono \uff0c\u4e0d\u4ec5\u987a\u7406\u6210\u7ae0\u5730\u5f00\u542f\u4e86\u6211\u7684\u201c\u517b\u732a\u201d\u4e8b\u4e1a\uff0c\u6700\u91cd\u8981\u7684\u662f\uff0c\u53ef\u4ee5\u5149\u660e\u6b63\u5927\u73a9\u4e2a\u8c10\u97f3\u6897\uff1a</p>\n<p>\u867e\u4ec1\u732a\u5fc3\uff08 xi\u0101 r\u00e9n zh\u016b x\u012bn \uff09\u201c</p>\n<p>\u201c\u867e\u4eba\u4e0d\u662f\u4eba\uff0c\u732a\u5fc3\u5374\u8d70\u5fc3\u201d\uff0c \u5c31\u662f\u8fd9\u4e48\u79bb\u8c31\u3001\u4f46\u53c8\u5f88\u597d\u8bb0\u3002\n\u9002\u5408\u8c01\u7528\uff1f\n\u6211\u4f1a\u628a pig-mono \u63a8\u8350\u7ed9\u4e09\u7c7b\u4eba\uff1a</p>\n<ul>\n<li>\n<p>Python \u4e3b\u529b\u519b\uff1a\u60f3\u628a\u7c7b\u4f3c OpenClaw \u7684\u5185\u6838\u80fd\u529b\u79fb\u690d\u5230\u81ea\u5df1\u4e1a\u52a1\u7cfb\u7edf\u91cc\u7684\u4eba\u3002</p>\n</li>\n<li>\n<p>\u201c\u53cd\u201d\u91cd\u578b\u6846\u67b6\u8005\uff1a\u4e0d\u60f3\u88ab\u592a\u91cd\u3001\u592a\u9ed1\u76d2\u7684 Agent \u6846\u67b6\u7ed1\u67b6\uff0c\u5e0c\u671b\u80fd\u50cf\u4e50\u9ad8\u4e00\u6837\u81ea\u7531\u6269\u5c55\u3002</p>\n</li>\n<li>\n<p>\u591a\u7aef\u90e8\u7f72\u9700\u6c42\u8005\uff1a\u8981\u628a\u540c\u4e00\u4e2a Agent \u585e\u8fdb\u98de\u4e66\u3001Slack \u3001Discord \uff0c\u751a\u81f3\u81ea\u7814 IM \u7684\u56e2\u961f</p>\n</li>\n</ul>\n<h1>\u7ed3\u5c3e\uff1a\u5982\u679c\u4f60\u5bf9\u5b83\u611f\u5174\u8da3</h1>\n<p>\u5982\u679c\u4f60\u89c9\u5f97\u8fd9\u4e2a\u601d\u8def\u5bf9\u4f60\u7684\u80c3\u53e3\uff0c\u6b22\u8fce\u53bb GitHub \u901b\u901b\u3002\u9886\u517b\u4e00\u5934\u4f60\u7684\u4e13\u5c5e\u8d5b\u535a\u5c0f\u732a\uff08\u611f\u5174\u8da3\u670b\u53cb\u53ef\u4ee5 Star \u3001\u63d0 Issues \u3001PR \uff09\uff01</p>\n<p>\u9879\u76ee\u5730\u5740\uff1a <a href=\"https://github.com/kangkona/pig-mono\" rel=\"nofollow\">https://github.com/kangkona/pig-mono</a></p>\n<p>\u4e5f\u6b22\u8fce\u5728\u8bc4\u8bba\u533a\u804a\u804a\uff1a\u4f60\u6700\u60f3\u7528 Agent \u89e3\u51b3\u7684\u201c\u771f\u5b9e\u5de5\u4f5c\u6d41\u201d\u662f\u4ec0\u4e48\uff1f\u547c\u58f0\u9ad8\u7684\u573a\u666f\uff0c\u6211\u76f4\u63a5\u7528 pig-mono \u5199\u4e2a\u53ef\u590d\u7528\u7684\u793a\u4f8b\u53d1\u51fa\u6765\uff01</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/the3yellow", 
        "name": "the3yellow", 
        "avatar": "https://cdn.v2ex.com/gravatar/35814ff3be516a7f572939f7d01f794c?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1193196", 
      "title": "5090 \u672c\u5730\u90e8\u7f72 Dify+Blender+ComfyUI\uff0c\u6c42\u8001\u54e5\u534f\u52a9\u6253\u901a\u94fe\u8def", 
      "id": "https://www.v2ex.com/t/1193196", 
      "date_published": "2026-02-18T14:49:59+00:00", 
      "content_html": "\u8fc7\u53bb\u4e09\u4e2a\u6708\uff0c\u6211\u548c AI \u6df1\u5ea6\u7ed3\u5bf9\u7f16\u7a0b\uff0c\u6413\u4e86\u4e00\u5957 AIGC \u81ea\u52a8\u5316\u89c6\u9891\u5de5\u4f5c\u6d41\uff08\u76ee\u524d\u4ee3\u7801 V3.0 \uff09\u3002\u4e1a\u52a1\u903b\u8f91\u5df2\u901a\uff0c\u4f46\u5361\u5728\u4e86\u6700\u540e\u7684\u5de5\u7a0b\u5316\u843d\u5730\u548c\u73af\u5883\u6392\u96f7\u4e0a\uff0c\u60f3\u627e\u4f4d\u5b9e\u6218\u6d3e\u8001\u54e5\u5e26\u5e26\u8def\u3002<br /><br />\u200b [\u786c\u4ef6\u4e0e\u73af\u5883] <br />\u7b97\u529b\uff1a\u672c\u5730\u5355\u5361 RTX 5090 (32G)\u3002<br />\u7cfb\u7edf\uff1a\u673a\u5668\u76ee\u524d\u662f Win11 \u4e13\u4e1a\u7248\u3002\uff08\u91cd\u70b9\u58f0\u660e\uff1a\u73af\u5883\u600e\u4e48\u642d\u5168\u542c\u4f60\u7684\uff01\u4e0d\u7ba1\u662f\u7eaf Win \u90e8\u7f72\u3001\u8d70 WSL2 \uff0c\u8fd8\u662f\u9700\u8981\u6211\u660e\u5929\u91cd\u88c5\u6210\u539f\u751f Ubuntu \u641e Docker \u7a7f\u900f\uff0c\u4f60\u89c9\u5f97\u600e\u4e48\u7a33\u54b1\u4eec\u5c31\u600e\u4e48\u6765\uff01\uff09<br />\u5168\u94fe\u8def\uff1aDify (\u4e91\u7aef\u4e0b\u53d1\u4efb\u52a1/\u56de\u8c03) -&gt; Python \u603b\u63a7\u811a\u672c -&gt; Blender Headless (\u5904\u7406 3D) -&gt; ComfyUI (Flux \u6e32\u67d3)\u3002<br /><br />\u200b [ \u6838\u5fc3\u75db\u70b9\uff08\u9700\u8981\u4f60\u89e3\u51b3\u7684\uff09] <br />\u200b\u73af\u5883\u6392\u96f7\uff1a\u6309\u4f60\u7684\u6700\u4f73\u5b9e\u8df5\u641e\u5b9a\u5e95\u5c42\u73af\u5883\u548c\u663e\u5b58\u8c03\u5ea6\uff0c\u914d\u7a33 Blender \u65e0\u5934\u73af\u5883\u548c ComfyUI \u3002<br />\u200b\u901a\u8baf\u65ad\u6d41\uff08\u6700\u6838\u5fc3\uff09\uff1a\u89e3\u51b3 Dify \u5f02\u6b65\u56de\u8c03 / Webhook \u5728\u901a\u8baf\u65f6\u7684\u7f51\u7edc\u63e1\u624b\u8d85\u65f6\u4e0e\u65ad\u6d41\u95ee\u9898\u3002<br /><br />\u200b [\u5408\u4f5c\u8fb9\u754c\u4e0e\u62a5\u916c] <br />\u5e95\u5c42\u4ee3\u7801\u662f AI \u5199\u7684\uff0c\u80af\u5b9a\u6709\u901a\u8baf\u903b\u8f91\u5751\u3002\u54b1\u4eec\u4e0d\u6b7b\u78d5\u4e1a\u52a1\u7ec6\u8282\uff08\u89c6\u9891\u7f8e\u4e11\u4e0d\u7528\u4f60\u7ba1\uff09\uff0c\u4f60\u53ea\u8d1f\u8d23\u5e2e\u6211\u4fee\u7a33\u8fd9\u6761\u201c\u901a\u8baf\u901a\u9053\u201d\u3002\u53ea\u8981\u6307\u4ee4\u80fd\u4ece Dify \u987a\u7545\u6d41\u8f6c\u5230 ComfyUI \u51fa\u56fe\u95ed\u73af\uff0c\u4e0d\u62a5\u9519\u5361\u6b7b\uff0c\u5c31\u7b97\u5927\u529f\u544a\u6210\u3002<br /><br />\u8fd9\u662f\u4e2a\u4eba\u843d\u5730\u9879\u76ee\uff0c\u62a5\u916c\u79c1\u804a<br />\u4e3a\u4e86\u53cc\u65b9\u90fd\u5b89\u5fc3\uff0c\u54b1\u4eec\u4e00\u5f8b\u8d70\u95f2\u9c7c\u62c5\u4fdd\u4ea4\u6613\u3002\u8dd1\u901a\u4e00\u6b21\u95ed\u73af\uff0c\u5f53\u573a\u786e\u8ba4\u6536\u8d27\uff0c\u94b1\u79d2\u5230\u8d26\u3002<br />\u200b\u5bf9\u8fd9\u5957\u4ea4\u53c9\u6280\u672f\u6808\u6709\u7ecf\u9a8c\u3001\u60f3\u8d5a\u4e2a\u5b9e\u5728\u5916\u5feb\u7684\u8001\u54e5\uff0c\u6b22\u8fce\u89e3\u7801\u52a0\u5fae\u4fe1\u804a\u804a\u3002<br />\uff08\u52a0\u597d\u53cb\u9ebb\u70e6\u5907\u6ce8 V2EX \uff0c\u9a97\u5b9a\u91d1\u7684\u8bf7\u7ed5\u9053\uff09\uff1a<br />Base64 \ud83c\udf0f\uff1aVmljaW5jaGFu"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/GradyYoung", 
        "name": "GradyYoung", 
        "avatar": "https://cdn.v2ex.com/avatar/ff1e/0d13/767175_large.png?m=1763022221"
      }, 
      "url": "https://www.v2ex.com/t/1192229", 
      "title": "\u6c42\u89e3\uff0c PyInstaller \u4e3a\u4ec0\u4e48\u5728 GithubAction \u4e0a\u6253\u5305\u540e\u4f53\u79ef\u6bd4\u5728\u672c\u5730\u5927 5 \u500d\uff08Mac arm \u7aef\uff09", 
      "id": "https://www.v2ex.com/t/1192229", 
      "date_published": "2026-02-11T06:30:37+00:00", 
      "content_html": "<p>\u4ed3\u5e93\u5730\u5740\uff1a <a href=\"https://github.com/gradyyoung/lang-tool\" rel=\"nofollow\">https://github.com/gradyyoung/lang-tool</a></p>\n<p>\u5c31\u662f\u4e00\u4e2a\u7b80\u5355\u7684 demo \u9879\u76ee\uff0c\u6211\u5728\u6211\u81ea\u5df1\u7684 Mac M1 \u4e0a\u9762\u6253\u5305\u7684.app \u5927\u5c0f 100M \uff0c\u4f46\u662f\u5728 Github Action \u6253\u5305\u51fa\u6765\u5c31\u6709 500M \uff0c\u5dee\u8ddd\u592a\u5927</p>\n<p>\u73af\u5883\u4fe1\u606f\uff1aUV \u865a\u62df\u73af\u5883 + Pyside6 + Python3.12.9</p>\n<p>\u6c42\u5927\u4f6c\u5e2e\u5fd9\u770b\u770b\ud83d\ude4f</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/albertofwb", 
        "name": "albertofwb", 
        "avatar": "https://cdn.v2ex.com/avatar/89d5/0e8a/184669_large.png?m=1722493380"
      }, 
      "url": "https://www.v2ex.com/t/1189500", 
      "title": "\u4f7f\u7528 Python \u4e00\u952e\u53d1\u63a8", 
      "id": "https://www.v2ex.com/t/1189500", 
      "date_published": "2026-01-30T03:59:40+00:00", 
      "content_html": "<p>\u4e00\u4e2a\u7528 Python + Playwright \u5b9e\u73b0\u7684\u547d\u4ee4\u884c\u53d1\u63a8\u5de5\u5177\uff0c\u901a\u8fc7 Chrome CDP \u8fde\u63a5\u5df2\u767b\u5f55\u7684\u6d4f\u89c8\u5668\uff0c\u652f\u6301\u53d1\u63a8\u3001\u56de\u590d\u3001\u5e26\u56fe\u53d1\u9001\u3002</p>\n<p>\u7528\u6cd5\u5f88\u7b80\u5355\uff1a</p>\n<pre><code class=\"language-bash\">twpost \"Hello World!\"              # \u53d1\u65b0\u63a8\u6587\ntwpost -r URL \"\u56de\u590d\u5185\u5bb9\"            # \u56de\u590d\u63a8\u6587  \ntwpost -i photo.jpg \"\u5e26\u56fe\u63a8\u6587\"      # \u5e26\u56fe\u7247\n</code></pre>\n<p>GitHub: <a href=\"https://github.com/albertofwb/twpost\" rel=\"nofollow\">https://github.com/albertofwb/twpost</a></p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/SingeeKing", 
        "name": "SingeeKing", 
        "avatar": "https://cdn.v2ex.com/avatar/c8a2/3f84/173108_large.png?m=1769246969"
      }, 
      "url": "https://www.v2ex.com/t/1188077", 
      "title": "\u5728 Python \u4e2d\u590d\u73b0 Race Condition", 
      "id": "https://www.v2ex.com/t/1188077", 
      "date_published": "2026-01-24T09:29:32+00:00", 
      "content_html": "<a target=\"_blank\" href=\"https://blog.singee.me/2026/01/24/2f2f4bee3ed7809a98f1c7fe6bbd1c36/\" rel=\"nofollow noopener\">https://blog.singee.me/2026/01/24/2f2f4bee3ed7809a98f1c7fe6bbd1c36/</a>"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/Howiee", 
        "name": "Howiee", 
        "avatar": "https://cdn.v2ex.com/gravatar/4a61fc6b912cd102d8b765413d6caa05?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1187370", 
      "title": "[\u8e29\u5751] A \u80a1\u5f00\u76d8\u628a Python \u641e\u6302\u4e86\uff0c\u6012\u5207 Go \u91cd\u5199\u884c\u60c5\u7f51\u5173 (\u9644 pprof \u5206\u6790 + \u6e90\u7801)", 
      "id": "https://www.v2ex.com/t/1187370", 
      "date_published": "2026-01-21T08:22:59+00:00", 
      "content_html": "\ud83d\udca5 \u4e8b\u6545\u73b0\u573a<br />LZ \u6240\u5728\u7684\u91cf\u5316\u5c0f\u5382\uff0c\u65e9\u671f\u57fa\u7840\u8bbe\u65bd\u5168\u662f Python (Asyncio) \u4e00\u628a\u68ad\u3002 \u8dd1\u7f8e\u80a1\uff08 US \uff09\u7684\u65f6\u5019\u76f8\u5b89\u65e0\u4e8b\uff0c\u6bd5\u7adf Tick \u6d41\u662f\u5747\u5300\u7684\u3002 \u4e0a\u5468\u7b56\u7565\u7ec4\u8bf4\u8981\u52a0 A \u80a1 (CN) \u548c \u5916\u6c47 (FX) \u505a\u5b8f\u89c2\u5bf9\u51b2\uff0c\u6211\u5c31\u6309\u8001\u5957\u8def\u63a5\u4e86\u6570\u636e\u6e90\u3002<br /><br />\u7ed3\u679c\u4e0a\u7ebf\u7b2c\u4e00\u5929 9:30 \u5c31\u70b8\u4e86\u3002 \u76d1\u63a7\u62a5\u8b66 CPU 100%\uff0c\u63a5\u7740\u5c31\u662f TCP Recv-Q \u5806\u79ef\uff0c\u6700\u540e\u76f4\u63a5\u65ad\u8fde\u3002 \u7b56\u7565\u7aef\u6536\u5230\u884c\u60c5\u7684\u65f6\u5019\uff0c\u9ec4\u82b1\u83dc\u90fd\u51c9\u4e86\uff08\u5ef6\u8fdf &gt; 500ms \uff09\u3002<br /><br />\ud83d\udd0d \u6392\u67e5\u8fc7\u7a0b (Post-Mortem)<br />\u88ab Leader \u9a82\u5b8c\u540e\uff0c\u6302\u4e86 py-spy \u770b\u706b\u7130\u56fe\uff0c\u53d1\u73b0\u4e24\u4e2a\u5927\u5751\uff1a<br /><br />Snapshot \u8109\u51b2\uff1aA \u80a1\u8ddf\u7f8e\u80a1\u4e0d\u4e00\u6837\uff0c\u5b83\u662f 3 \u79d2\u4e00\u6b21\u7684\u5168\u5e02\u573a\u5feb\u7167\u3002\u51e0\u5343\u53ea\u80a1\u7968\u7684\u6570\u636e\u5728\u540c\u4e00\u6beb\u79d2\u6d8c\u8fdb\u6765\uff0c\u77ac\u95f4\u6d41\u91cf\u662f\u5e73\u65f6\u7684\u51e0\u5341\u500d\u3002<br /><br />GIL + GC \u6df7\u5408\u53cc\u6253\uff1a<br /><br />json.loads \u662f CPU \u5bc6\u96c6\u578b\uff0c\u628a GIL \u9501\u6b7b\u4e86\uff0c\u7f51\u7edc\u7ebf\u7a0b\u6839\u672c\u62a2\u4e0d\u5230 CPU \u8bfb\u6570\u636e\u3002<br /><br />\u77ed\u65f6\u95f4\u751f\u6210\u5927\u91cf dict \u5bf9\u8c61\uff0c\u89e6\u53d1 Python \u9891\u7e41 GC \uff0cStop-the-world \u3002<br /><br />\ud83d\udee0\ufe0f \u67b6\u6784\u91cd\u6784 (Python -&gt; Go)<br />\u4e3a\u4e86\u4fdd\u4f4f\u996d\u7897\uff0c\u8fde\u591c\u51b3\u5b9a\u628a Feed Handler \u5c42\u5265\u79bb\u51fa\u6765\u7528 Go \u91cd\u5199\u3002 \u76ee\u6807\u5f88\u660e\u786e\uff1a\u625b\u4f4f A \u80a1\u8109\u51b2\uff0c\u628a\u6570\u636e\u6d17\u5e72\u51c0\uff0c\u518d\u5582\u7ed9 Python \u7b56\u7565\u3002<br /><br />\u67b6\u6784\u903b\u8f91\uff1aWebSocket (Unified API) -&gt; Go Channel (Buffer) -&gt; Worker Pool (Sonic Decode) -&gt; Shm/ZMQ<br /><br />\u4e3a\u4ec0\u4e48\u7528 Go \uff1f<br /><br />Goroutine\uff1a\u51e0 KB \u5f00\u9500\uff0c\u968f\u5f00\u968f\u7528\u3002<br /><br />Channel\uff1a\u5929\u7136\u7684\u961f\u5217\uff0c\u505a Buffer \u6297\u8109\u51b2\u795e\u5668\u3002<br /><br />Sonic\uff1a\u5b57\u8282\u5f00\u6e90\u7684 JSON \u5e93\uff0c\u5e26 SIMD \u52a0\u901f\uff0c\u6bd4\u6807\u51c6\u5e93\u5feb 2-3 \u500d\uff08\u8fd9\u4e2a\u662f\u5173\u952e\uff09\u3002<br /><br />\ud83d\udcbb Show me the code<br />\u4e3a\u4e86\u89e3\u51b3 \u534f\u8bae\u5f02\u6784\uff08 A \u80a1 CTP \u3001\u7f8e\u80a1 FIX \u3001\u5916\u6c47 MT4 \uff09\uff0c\u6211\u63a5\u4e86\u4e2a\u805a\u5408\u6e90\uff08 TickDB \uff09\uff0c\u628a\u5168\u5e02\u573a\u6570\u636e\u6d17\u6210\u4e86\u7edf\u4e00\u7684 JSON \u3002\u8fd9\u6837 Go \u8fd9\u8fb9\u53ea\u7528\u7ef4\u62a4\u4e00\u4e2a Struct \u3002<br /><br />\u4ee5\u4e0b\u662f\u8131\u654f\u540e\u7684\u6838\u5fc3\u4ee3\u7801\uff0c\u590d\u5236\u53ef\u8dd1\uff08\u9700 go get \u4f9d\u8d56\uff09\u3002<br />package main<br /><br />import (<br />\t\"fmt\"<br />\t\"log\"<br />\t\"runtime\"<br />\t\"time\"<br /><br />\t\"<a target=\"_blank\" href=\"http://github.com/bytedance/sonic\" rel=\"nofollow noopener\">github.com/bytedance/sonic</a>\" // \u5b57\u8282\u7684\u5e93\uff0c\u89e3\u6790\u901f\u5ea6\u540a\u6253 encoding/json<br />\t\"<a target=\"_blank\" href=\"http://github.com/gorilla/websocket\" rel=\"nofollow noopener\">github.com/gorilla/websocket</a>\"<br />)<br /><br />// \u9632\u722c\u866b/\u9632\u98ce\u63a7\uff0cURL \u62c6\u4e00\u4e0b<br />const (<br />\tHost = \"<a target=\"_blank\" href=\"http://api.tickdb.ai\" rel=\"nofollow noopener\">api.tickdb.ai</a>\"<br />\tPath = \"/v1/realtime\"<br />\t// Key \u662f\u8585\u7684\u8bd5\u7528\u7248\uff0c\u5927\u5bb6\u62ff\u53bb\u538b\u6d4b\u6ca1\u95ee\u9898<br />\tKey  = \"?api_key=YOUR_V2EX_KEY\" <br />)<br /><br />// \u5185\u5b58\u5bf9\u9f50\u4f18\u5316\uff1a\u628a\u540c\u7c7b\u578b\u5b57\u6bb5\u653e\u4e00\u8d77<br />type MarketTick struct {<br />\tCmd  string `json:\"cmd\"`<br />\tData struct {<br />\t\tSymbol    string `json:\"symbol\"`<br />\t\tLastPrice string `json:\"last_price\"` // \u4ef7\u683c\u7edf\u4e00 string \uff0c\u4e0b\u6e38\u5904\u7406\u7cbe\u5ea6<br />\t\tVolume    string `json:\"volume_24h\"`<br />\t\tTimestamp int64  `json:\"timestamp\"`  // 8 byte<br />\t\tMarket    string `json:\"market\"`     // CN/US/HK/FX<br />\t} `json:\"data\"`<br />}<br /><br />func main() {<br />\t// 1. \u8dd1\u6ee1\u591a\u6838\uff0c\u522b\u6d6a\u8d39 AWS \u7684 CPU<br />\truntime.GOMAXPROCS(runtime.NumCPU())<br /><br />\turl := \"wss://\" + Host + Path + Key<br />\tconn, _, err := websocket.DefaultDialer.Dial(url, nil)<br />\tif err != nil {<br />\t\tlog.Fatal(\"Dial err:\", err)<br />\t}<br />\tdefer conn.Close()<br /><br />\t// 2. \u8ba2\u9605\u6307\u4ee4<br />\t// \u91cd\u70b9\u6d4b\u8bd5\uff1aA \u80a1(\u8109\u51b2) + \u8d35\u91d1\u5c5e(\u9ad8\u9891) + \u7f8e\u80a1/\u6e2f\u80a1<br />\tsubMsg := `{<br />\t\t\"cmd\": \"subscribe\", <br />\t\t\"data\": {<br />\t\t\t\"channel\": \"ticker\", <br />\t\t\t\"symbols\": [<br />\t\t\t\t\"<a target=\"_blank\" href=\"http://600519.SH\" rel=\"nofollow noopener\">600519.SH</a>\", \"<a target=\"_blank\" href=\"http://000001.SZ\" rel=\"nofollow noopener\">000001.SZ</a>\",   // A \u80a1\uff1a\u8305\u53f0\u3001\u5e73\u5b89 (9:30 \u538b\u529b\u6e90)<br />\t\t\t\t\"XAUUSD\", \"USDJPY\",         // \u5916\u6c47\uff1a\u9ec4\u91d1\u3001\u65e5\u5143 (\u9ad8\u9891\u6e90)<br />\t\t\t\t\"<a target=\"_blank\" href=\"http://NVDA.US\" rel=\"nofollow noopener\">NVDA.US</a>\", \"<a target=\"_blank\" href=\"http://AAPL.US\" rel=\"nofollow noopener\">AAPL.US</a>\",       // \u7f8e\u80a1\uff1a\u82f1\u4f1f\u8fbe<br />\t\t\t\t\"<a target=\"_blank\" href=\"http://00700.HK\" rel=\"nofollow noopener\">00700.HK</a>\", \"<a target=\"_blank\" href=\"http://09988.HK\" rel=\"nofollow noopener\">09988.HK</a>\",     // \u6e2f\u80a1\uff1a\u817e\u8baf<br />\t\t\t\t\"BTCUSDT\"                   // Crypto\uff1a\u62ff\u6765\u8dd1 7x24h \u7a33\u5b9a\u6027\u7684<br />\t\t\t]<br />\t\t}<br />\t}`<br />\tif err := conn.WriteMessage(websocket.TextMessage, []byte(subMsg)); err != nil {<br />\t\tlog.Fatal(\"Sub err:\", err)<br />\t}<br />\tfmt.Println(\"&gt;&gt;&gt; Go Engine Started...\")<br /><br />\t// 3. Ring Buffer<br />\t// \u5173\u952e\u70b9\uff1a8192 \u7684\u7f13\u51b2\uff0c\u4e13\u95e8\u4e3a\u4e86\u5403\u4e0b A \u80a1\u7684\u77ac\u95f4\u8109\u51b2<br />\tdataChan := make(chan []byte, 8192)<br /><br />\t// 4. Worker Pool<br />\t// \u7ecf\u9a8c\u503c\uff1aCPU \u6838\u6570 * 2<br />\tworkerNum := runtime.NumCPU() * 2<br />\tfor i := 0; i &lt; workerNum; i++ {<br />\t\tgo worker(i, dataChan)<br />\t}<br /><br />\t// 5. Producer Loop (IO Bound)<br />\t// \u53ea\u7ba1\u8bfb\uff0c\u8bfb\u5230\u5c31\u6254 Channel \uff0c\u7edd\u5bf9\u4e0d\u963b\u585e<br />\tfor {<br />\t\t_, msg, err := conn.ReadMessage()<br />\t\tif err != nil {<br />\t\t\tlog.Println(\"Read err:\", err)<br />\t\t\tbreak<br />\t\t}<br />\t\tdataChan &lt;- msg<br />\t}<br />}<br /><br />// Consumer (CPU Bound)<br />func worker(id int, ch &lt;-chan []byte) {<br />\tvar tick MarketTick<br />\tfor msg := range ch {<br />\t\t// \u7528 Sonic \u89e3\u6790\uff0c\u6027\u80fd\u8d77\u98de<br />\t\tif err := sonic.Unmarshal(msg, &amp;tick); err != nil {<br />\t\t\tcontinue<br />\t\t}<br /><br />\t\tif tick.Cmd == \"ticker\" {<br />\t\t\t// \u7b80\u5355\u7684\u76d1\u63a7\uff1a\u5168\u94fe\u8def\u5ef6\u8fdf<br />\t\t\tlatency := time.Now().UnixMilli() - tick.Data.Timestamp<br />\t\t\t<br />\t\t\t// \u62bd\u6837\u6253\u5370<br />\t\t\tif id == 0 {<br />\t\t\t\tfmt.Printf(\"[%s] %-8s | Price: %s | Lat: %d ms\\n\", <br />\t\t\t\t\ttick.Data.Market, tick.Data.Symbol, tick.Data.LastPrice, latency)<br />\t\t\t}<br />\t\t}<br />\t}<br />}<br /><br />\ud83d\udcca Benchmark (\u5b9e\u6d4b\u6570\u636e)<br />\u73af\u5883\uff1aAWS c5.xlarge (4C 8G)\uff0c\u8ba2\u9605 500 \u4e2a\u6d3b\u8dc3 Symbol \u3002 \u590d\u73b0\u4e86 9:30 A \u80a1\u5f00\u76d8 + \u975e\u519c\u6570\u636e\u516c\u5e03 \u7684\u6df7\u5408\u573a\u666f\u3002<br />\u6307\u6807,Python (Asyncio),Go (Sonic + Channel),\u8bc4\u4ef7<br />P99 Latency,480ms+,&lt; 4ms,\u7b80\u76f4\u662f\u964d\u7ef4\u6253\u51fb<br />Max Jitter,1.2s (GC Stop),15ms,\u7ec8\u4e8e\u4e0d\u4e22\u5305\u4e86<br />CPU Usage,98% (\u5355\u6838\u6253\u6ee1),18% (\u591a\u6838\u5747\u8861),\u673a\u5668\u90fd\u4e0d\u600e\u4e48\u8f6c<br />Mem,800MB,60MB,\u7701\u4e0b\u6765\u7684\u5185\u5b58\u53ef\u4ee5\u591a\u8dd1\u4e2a\u56de\u6d4b<br /><br />\ud83d\udcdd \u51e0\u70b9\u5fc3\u5f97<br />\u672f\u4e1a\u6709\u4e13\u653b\uff1aPython \u505a\u7b56\u7565\u903b\u8f91\u5f00\u53d1\u662f\u65e0\u654c\u7684\uff0c\u4f46\u8fd9\u79cd I/O + CPU \u6df7\u5408\u5bc6\u96c6\u578b\u7684\u63a5\u5165\u5c42\uff0c\u8fd8\u662f\u4ea4\u7ed9 Go/Rust \u5427\uff0c\u522b\u5934\u94c1\u3002<br /><br />\u522b\u9020\u8f6e\u5b50\uff1a\u4e4b\u524d\u60f3\u81ea\u5df1\u5199 CTP \u548c FIX \u7684\u89e3\u6790\u5668\uff0c\u5199\u4e86\u4e00\u5468\u53ea\u60f3\u8dd1\u8def\u3002\u540e\u6765\u5207\u5230 TickDB \u8fd9\u79cd Unified API \uff0c\u628a\u810f\u6d3b\u5916\u5305\u51fa\u53bb\uff0c\u77ac\u95f4\u6e05\u723d\u4e86\u3002<br /><br />Sonic \u662f\u795e\u5668\uff1a\u5982\u679c\u4f60\u7684 Go \u7a0b\u5e8f\u74f6\u9888\u5728 JSON \uff0c\u65e0\u8111\u6362 bytedance/sonic \uff0c\u7acb\u7aff\u89c1\u5f71\u3002<br /><br />\u4ee3\u7801\u5927\u5bb6\u968f\u4fbf\u62ff\u53bb\u6539\uff0c\u5e0c\u671b\u80fd\u5e2e\u5230\u540c\u6837\u88ab Python \u5ef6\u8fdf\u6298\u78e8\u7684\u5144\u5f1f\u3002 (Key \u662f\u8bd5\u7528\u7248\u7684\uff0c\u522b\u62ff\u53bb\u8dd1\u5927\u8d44\u91d1\u5b9e\u76d8\u54c8\uff0c\u88ab\u9650\u6d41\u4e86\u522b\u627e\u6211)"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/lo5252", 
        "name": "lo5252", 
        "avatar": "https://cdn.v2ex.com/gravatar/1071ea398dc00dc1b4ce2c384a31f8e9?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1185791", 
      "title": "\u6709\u4e2a\u89c6\u9891\u7f51\u7ad9\u5b66\u4e60\u7684\u65f6\u5019\u4e0d\u70b9\u6682\u505c\u6216\u8005\u89c6\u9891\u5b66\u5b8c\uff0c\u5c31\u4e00\u76f4\u6ca1\u6709\u4efb\u4f55\u5305\uff0c\u4e5f\u6ca1\u6709\u5fc3\u8df3\u5305\uff0c\u4e5f\u4e0d\u4f1a\u66f4\u65b0\u89c6\u9891\u5b66\u4e60\u8fdb\u5ea6\u5b66\u65f6\u3002", 
      "id": "https://www.v2ex.com/t/1185791", 
      "date_published": "2026-01-15T01:37:09+00:00", 
      "content_html": "<p>\u6709\u4e2a\u89c6\u9891\u7f51\u7ad9\u5b66\u4e60\u7684\u65f6\u5019\u4e0d\u70b9\u6682\u505c\u6216\u8005\u89c6\u9891\u5b66\u5b8c\uff0c\u5c31\u4e00\u76f4\u6ca1\u6709\u4efb\u4f55\u5305\uff0c\u4e5f\u6ca1\u6709\u5fc3\u8df3\u5305\uff0c\u4e5f\u4e0d\u4f1a\u66f4\u65b0\u89c6\u9891\u5b66\u4e60\u8fdb\u5ea6\u5b66\u65f6\u3002</p>\n<p>\u70b9\u6682\u505c\u6216\u8005\u89c6\u9891\u5b66\u4e60\u5b8c\u4e86\uff0c\u5c31\u4f1a\u66f4\u65b0\u89c6\u9891\u5b66\u4e60\u7684\u8fdb\u5ea6\u5b66\u65f6\u3002</p>\n<p>\u70b9\u6682\u505c\u6ca1\u6709\u5305\uff0c\u70b9\u7ee7\u7eed\u5c31\u4e5f\u53ea\u4f1a\u6709\u4e00\u4e2a\u4e0b\u8f7d mp4 \u7684\u5305\u3002</p>\n<p>\u6709\u5927\u795e\u77e5\u9053\u8fd9\u662f\u5982\u4f55\u66f4\u65b0\u89c6\u9891\u5b66\u4e60\u8fdb\u5ea6 \u7684\u5417</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/tangkikodo", 
        "name": "tangkikodo", 
        "avatar": "https://cdn.v2ex.com/avatar/1771/1b39/291907_large.png?m=1720273866"
      }, 
      "url": "https://www.v2ex.com/t/1184657", 
      "date_modified": "2026-01-11T10:29:52+00:00", 
      "content_html": "<h1>\u57fa\u4e8e Pydantic-Resolve \u548c FastAPI-Voyager \u7684 Clean Architecture \u5b9e\u8df5</h1>\n<p>\u7bc7\u5e45\u8f83\u957f\u65e0\u6cd5\u7c98\u8d34\u5168\u6587\uff0c\u539f\u6587\u94fe\u63a5\uff1a\n<a href=\"https://github.com/allmonday/A-Python-web-development-methodology-for-complex-business-scenarios/blob/main/README.zh.md\" rel=\"nofollow\">https://github.com/allmonday/A-Python-web-development-methodology-for-complex-business-scenarios/blob/main/README.zh.md</a></p>\n<blockquote>\n<p>\u4e00\u5957\u9762\u5411\u590d\u6742\u4e1a\u52a1\u573a\u666f\u7684 Python Web \u5f00\u53d1\u65b9\u6cd5\u8bba</p>\n</blockquote>\n<h2>\u76ee\u5f55</h2>\n<ul>\n<li>\n<a href=\"#%E5%9F%BA%E4%BA%8E-pydantic-resolve-%E5%92%8C-fastapi-voyager-%E7%9A%84-clean-architecture-%E5%AE%9E%E8%B7%B5\" rel=\"nofollow\">\u57fa\u4e8e Pydantic-Resolve \u548c FastAPI-Voyager \u7684 Clean Architecture \u5b9e\u8df5</a><ul>\n<li><a href=\"#%E7%9B%AE%E5%BD%95\" rel=\"nofollow\">\u76ee\u5f55</a></li>\n<li>\n<a href=\"#1-%E8%83%8C%E6%99%AF%E4%B8%8E%E9%97%AE%E9%A2%98\" rel=\"nofollow\">1. \u80cc\u666f\u4e0e\u95ee\u9898</a><ul>\n<li>\n<a href=\"#11-%E5%BD%93%E5%89%8D%E4%B8%BB%E6%B5%81%E5%81%9A%E6%B3%95%E5%8F%8A%E5%85%B6%E7%97%9B%E7%82%B9\" rel=\"nofollow\">1.1 \u5f53\u524d\u4e3b\u6d41\u505a\u6cd5\u53ca\u5176\u75db\u70b9</a><ul>\n<li>[\u6a21\u5f0f\u4e00\uff1a\u76f4\u63a5\u4f7f\u7528 ORM \uff08\u5982 SQLAlchemy \uff09](#\u6a21\u5f0f\u4e00\u76f4\u63a5\u4f7f\u7528-orm \u5982-sqlalchemy)</li>\n<li><a href=\"#%E6%A8%A1%E5%BC%8F%E4%BA%8C%E4%BD%BF%E7%94%A8-orm-%E7%9A%84-eager-loading\" rel=\"nofollow\">\u6a21\u5f0f\u4e8c\uff1a\u4f7f\u7528 ORM \u7684 Eager Loading</a></li>\n<li><a href=\"#%E6%A8%A1%E5%BC%8F%E4%B8%89%E6%89%8B%E5%8A%A8%E7%BB%84%E8%A3%85%E6%95%B0%E6%8D%AE\" rel=\"nofollow\">\u6a21\u5f0f\u4e09\uff1a\u624b\u52a8\u7ec4\u88c5\u6570\u636e</a></li>\n<li><a href=\"#%E6%A8%A1%E5%BC%8F%E5%9B%9B%E4%BD%BF%E7%94%A8-graphql\" rel=\"nofollow\">\u6a21\u5f0f\u56db\uff1a\u4f7f\u7528 GraphQL</a></li>\n</ul>\n</li>\n<li>\n<a href=\"#12-%E9%97%AE%E9%A2%98%E6%A0%B9%E6%BA%90%E5%88%86%E6%9E%90\" rel=\"nofollow\">1.2 \u95ee\u9898\u6839\u6e90\u5206\u6790</a><ul>\n<li>[\u95ee\u9898 1\uff1a\u4e1a\u52a1\u6a21\u578b\u4e0e\u6570\u636e\u6a21\u578b\u6df7\u6dc6](#\u95ee\u9898-1 \u4e1a\u52a1\u6a21\u578b\u4e0e\u6570\u636e\u6a21\u578b\u6df7\u6dc6)</li>\n<li>[\u95ee\u9898 2\uff1a\u4f9d\u8d56\u65b9\u5411\u9519\u8bef](#\u95ee\u9898-2 \u4f9d\u8d56\u65b9\u5411\u9519\u8bef)</li>\n<li>[\u95ee\u9898 3\uff1a\u7f3a\u5c11\u4e1a\u52a1\u5173\u7cfb\u7684\u663e\u5f0f\u58f0\u660e](#\u95ee\u9898-3 \u7f3a\u5c11\u4e1a\u52a1\u5173\u7cfb\u7684\u663e\u5f0f\u58f0\u660e)</li>\n<li>[\u95ee\u9898 4\uff1a\u4e2d\u95f4\u8868\u7684\u6280\u672f\u66b4\u9732](#\u95ee\u9898-4 \u4e2d\u95f4\u8868\u7684\u6280\u672f\u66b4\u9732)</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>\n<a href=\"#2-clean-architecture-%E6%80%9D%E6%83%B3\" rel=\"nofollow\">2. Clean Architecture \u601d\u60f3</a><ul>\n<li>\n<a href=\"#21-%E6%A0%B8%E5%BF%83%E5%8E%9F%E5%88%99\" rel=\"nofollow\">2.1 \u6838\u5fc3\u539f\u5219</a><ul>\n<li>[\u539f\u5219 1\uff1a\u4f9d\u8d56\u89c4\u5219](#\u539f\u5219-1 \u4f9d\u8d56\u89c4\u5219)</li>\n<li>[\u539f\u5219 2\uff1a\u4e1a\u52a1\u89c4\u5219\u72ec\u7acb](#\u539f\u5219-2 \u4e1a\u52a1\u89c4\u5219\u72ec\u7acb)</li>\n<li>[\u539f\u5219 3\uff1a\u8de8\u8fb9\u754c\u7684\u6570\u636e\u4f20\u9012](#\u539f\u5219-3 \u8de8\u8fb9\u754c\u7684\u6570\u636e\u4f20\u9012)</li>\n</ul>\n</li>\n<li><a href=\"#22-%E4%BE%9D%E8%B5%96%E8%A7%84%E5%88%99\" rel=\"nofollow\">2.2 \u4f9d\u8d56\u89c4\u5219</a></li>\n<li>\n<a href=\"#23-%E5%9C%A8-web-%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8\" rel=\"nofollow\">2.3 \u5728 Web \u5f00\u53d1\u4e2d\u7684\u5e94\u7528</a><ul>\n<li><a href=\"#%E4%BC%A0%E7%BB%9F%E6%9E%B6%E6%9E%84%E7%9A%84%E9%97%AE%E9%A2%98\" rel=\"nofollow\">\u4f20\u7edf\u67b6\u6784\u7684\u95ee\u9898</a></li>\n</ul>\n</li>\n</ul>\n</li>\n<li>\n[3. Pydantic-Resolve\uff1a\u4e1a\u52a1\u6a21\u578b\u5c42](#3-pydantic-resolve \u4e1a\u52a1\u6a21\u578b\u5c42)<ul>\n<li>\n<a href=\"#31-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5\" rel=\"nofollow\">3.1 \u6838\u5fc3\u6982\u5ff5</a><ul>\n<li><a href=\"#%E6%A0%B8%E5%BF%83%E6%80%9D%E6%83%B3\" rel=\"nofollow\">\u6838\u5fc3\u601d\u60f3</a></li>\n</ul>\n</li>\n<li>\n[3.2 ERD\uff1a\u4e1a\u52a1\u5173\u7cfb\u7684\u58f0\u660e](#32-erd \u4e1a\u52a1\u5173\u7cfb\u7684\u58f0\u660e)<ul>\n<li><a href=\"#%E5%AE%9A%E4%B9%89%E5%AE%9E%E4%BD%93%E5%85%B3%E7%B3%BB%E5%9B%BE\" rel=\"nofollow\">\u5b9a\u4e49\u5b9e\u4f53\u5173\u7cfb\u56fe</a></li>\n<li><a href=\"#erd-%E7%9A%84%E5%85%B3%E9%94%AE%E7%89%B9%E6%80%A7\" rel=\"nofollow\">ERD \u7684\u5173\u952e\u7279\u6027</a></li>\n</ul>\n</li>\n<li>\n[3.3 DataLoader\uff1a\u6279\u91cf\u52a0\u8f7d\u7684\u79d8\u5bc6](#33-dataloader \u6279\u91cf\u52a0\u8f7d\u7684\u79d8\u5bc6)<ul>\n<li>[\u95ee\u9898\uff1aN+1 \u67e5\u8be2](#\u95ee\u9898 n1-\u67e5\u8be2)</li>\n<li>[\u89e3\u51b3\u65b9\u6848\uff1aDataLoader](#\u89e3\u51b3\u65b9\u6848 dataloader)</li>\n<li>[DefineSubset\uff1a\u5b57\u6bb5\u9009\u62e9\u4e0e\u590d\u7528](#definesubset \u5b57\u6bb5\u9009\u62e9\u4e0e\u590d\u7528)</li>\n</ul>\n</li>\n<li>\n[3.4 Resolve \u4e0e Post\uff1a\u6570\u636e\u7ec4\u88c5\u4e0e\u8ba1\u7b97](#34-resolve-\u4e0e-post \u6570\u636e\u7ec4\u88c5\u4e0e\u8ba1\u7b97)<ul>\n<li>[Resolve\uff1a\u58f0\u660e\u6570\u636e\u4f9d\u8d56](#resolve \u58f0\u660e\u6570\u636e\u4f9d\u8d56)</li>\n<li>[Post\uff1a\u6570\u636e\u540e\u5904\u7406](#post \u6570\u636e\u540e\u5904\u7406)</li>\n</ul>\n</li>\n<li>\n<a href=\"#35-%E8%B7%A8%E5%B1%82%E6%95%B0%E6%8D%AE%E4%BC%A0%E9%80%92\" rel=\"nofollow\">3.5 \u8de8\u5c42\u6570\u636e\u4f20\u9012</a><ul>\n<li>[Expose\uff1a\u7236\u8282\u70b9\u5411\u5b50\u8282\u70b9\u66b4\u9732\u6570\u636e](#expose \u7236\u8282\u70b9\u5411\u5b50\u8282\u70b9\u66b4\u9732\u6570\u636e)</li>\n<li>[Collect\uff1a\u5b50\u8282\u70b9\u5411\u7236\u8282\u70b9\u6536\u96c6\u6570\u636e](#collect \u5b50\u8282\u70b9\u5411\u7236\u8282\u70b9\u6536\u96c6\u6570\u636e)</li>\n</ul>\n</li>\n<li><a href=\"#36-%E5%B0%8F%E7%BB%93\" rel=\"nofollow\">3.6 \u5c0f\u7ed3</a></li>\n</ul>\n</li>\n<li>\n[4. FastAPI-Voyager\uff1a\u67b6\u6784\u53ef\u89c6\u5316](#4-fastapi-voyager \u67b6\u6784\u53ef\u89c6\u5316)<ul>\n<li><a href=\"#40-%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E6%9E%B6%E6%9E%84%E5%8F%AF%E8%A7%86%E5%8C%96\" rel=\"nofollow\">4.0 \u4e3a\u4ec0\u4e48\u9700\u8981\u67b6\u6784\u53ef\u89c6\u5316\uff1f</a></li>\n<li>\n<a href=\"#41-%E6%A0%B8%E5%BF%83%E5%8A%9F%E8%83%BD\" rel=\"nofollow\">4.1 \u6838\u5fc3\u529f\u80fd</a><ul>\n<li><a href=\"#1-%E8%87%AA%E5%8A%A8%E6%89%AB%E6%8F%8F-api-%E7%BB%93%E6%9E%84\" rel=\"nofollow\">1. \u81ea\u52a8\u626b\u63cf API \u7ed3\u6784</a></li>\n<li><a href=\"#2-%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84%E5%B1%95%E7%A4%BA\" rel=\"nofollow\">2. \u4e09\u5c42\u67b6\u6784\u5c55\u793a</a></li>\n</ul>\n</li>\n<li>\n<a href=\"#42-erd-%E4%B8%8E-api-route-%E7%9A%84%E7%BB%93%E5%90%88\" rel=\"nofollow\">4.2 ERD \u4e0e API Route \u7684\u7ed3\u5408</a><ul>\n<li><a href=\"#%E6%A0%B8%E5%BF%83%E4%B8%9A%E5%8A%A1-%E6%8A%80%E6%9C%AF%E6%98%A0%E5%B0%84%E5%9B%BE\" rel=\"nofollow\">\u6838\u5fc3\uff1a\u4e1a\u52a1-\u6280\u672f\u6620\u5c04\u56fe</a></li>\n</ul>\n</li>\n<li>\n<a href=\"#43-%E5%AE%9E%E6%88%98%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF\" rel=\"nofollow\">4.3 \u5b9e\u6218\u5e94\u7528\u573a\u666f</a><ul>\n<li>[\u573a\u666f 1\uff1a\u53d1\u73b0\u67b6\u6784\u504f\u79bb](#\u573a\u666f-1 \u53d1\u73b0\u67b6\u6784\u504f\u79bb)</li>\n<li>[\u573a\u666f 2\uff1a\u53d1\u73b0\u8fc7\u5ea6\u5d4c\u5957](#\u573a\u666f-2 \u53d1\u73b0\u8fc7\u5ea6\u5d4c\u5957)</li>\n<li>[\u573a\u666f 3\uff1a\u65b0\u4eba\u5feb\u901f\u7406\u89e3\u7cfb\u7edf](#\u573a\u666f-3 \u65b0\u4eba\u5feb\u901f\u7406\u89e3\u7cfb\u7edf)</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>\n<a href=\"#5-%E5%AE%8C%E6%95%B4%E7%9A%84%E5%BC%80%E5%8F%91%E6%B5%81%E7%A8%8B\" rel=\"nofollow\">5. \u5b8c\u6574\u7684\u5f00\u53d1\u6d41\u7a0b</a><ul>\n<li>\n<a href=\"#51-%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E9%98%B6%E6%AE%B5\" rel=\"nofollow\">5.1 \u67b6\u6784\u8bbe\u8ba1\u9636\u6bb5</a><ul>\n<li>[\u6b65\u9aa4 1\uff1a\u8bc6\u522b\u6838\u5fc3\u4e1a\u52a1\u5b9e\u4f53](#\u6b65\u9aa4-1 \u8bc6\u522b\u6838\u5fc3\u4e1a\u52a1\u5b9e\u4f53)</li>\n<li>[\u6b65\u9aa4 2\uff1a\u5b9a\u4e49\u5b9e\u4f53\u5173\u7cfb](#\u6b65\u9aa4-2 \u5b9a\u4e49\u5b9e\u4f53\u5173\u7cfb)</li>\n</ul>\n</li>\n<li>\n<a href=\"#52-%E5%AE%9E%E4%BD%93%E5%AE%9A%E4%B9%89%E9%98%B6%E6%AE%B5\" rel=\"nofollow\">5.2 \u5b9e\u4f53\u5b9a\u4e49\u9636\u6bb5</a><ul>\n<li><a href=\"#%E5%AE%9A%E4%B9%89-erd\" rel=\"nofollow\">\u5b9a\u4e49 ERD</a></li>\n</ul>\n</li>\n<li>\n<a href=\"#53-%E6%95%B0%E6%8D%AE%E5%B1%82%E5%AE%9E%E7%8E%B0\" rel=\"nofollow\">5.3 \u6570\u636e\u5c42\u5b9e\u73b0</a><ul>\n<li>[\u5b9a\u4e49 ORM Models \uff08\u4ee5 ERD \u4e3a\u6307\u5bfc\uff09](#\u5b9a\u4e49-orm-models \u4ee5-erd-\u4e3a\u6307\u5bfc)</li>\n<li><a href=\"#%E5%AE%9E%E7%8E%B0-loaders\" rel=\"nofollow\">\u5b9e\u73b0 Loaders</a></li>\n</ul>\n</li>\n<li>\n<a href=\"#54-api-%E5%AE%9E%E7%8E%B0%E9%98%B6%E6%AE%B5\" rel=\"nofollow\">5.4 API \u5b9e\u73b0\u9636\u6bb5</a><ul>\n<li><a href=\"#%E5%AE%9A%E4%B9%89-response-models\" rel=\"nofollow\">\u5b9a\u4e49 Response Models</a></li>\n<li><a href=\"#%E5%AE%9E%E7%8E%B0-api-routes\" rel=\"nofollow\">\u5b9e\u73b0 API Routes</a></li>\n</ul>\n</li>\n<li>\n<a href=\"#55-%E5%8F%AF%E8%A7%86%E5%8C%96%E9%AA%8C%E8%AF%81\" rel=\"nofollow\">5.5 \u53ef\u89c6\u5316\u9a8c\u8bc1</a><ul>\n<li><a href=\"#%E9%9B%86%E6%88%90-fastapi-voyager\" rel=\"nofollow\">\u96c6\u6210 FastAPI-Voyager</a></li>\n<li><a href=\"#%E9%AA%8C%E8%AF%81%E6%9E%B6%E6%9E%84\" rel=\"nofollow\">\u9a8c\u8bc1\u67b6\u6784</a></li>\n</ul>\n</li>\n</ul>\n</li>\n<li>\n<a href=\"#6-%E4%B8%8E%E5%85%B6%E4%BB%96%E6%96%B9%E6%A1%88%E7%9A%84%E5%AF%B9%E6%AF%94\" rel=\"nofollow\">6. \u4e0e\u5176\u4ed6\u65b9\u6848\u7684\u5bf9\u6bd4</a><ul>\n<li><a href=\"#61-vs-%E4%BC%A0%E7%BB%9F-orm\" rel=\"nofollow\">6.1 vs \u4f20\u7edf ORM</a></li>\n<li><a href=\"#62-vs-graphql\" rel=\"nofollow\">6.2 vs GraphQL</a></li>\n<li><a href=\"#63-vs-ddd-%E6%A1%86%E6%9E%B6\" rel=\"nofollow\">6.3 vs DDD \u6846\u67b6</a></li>\n</ul>\n</li>\n<li>\n<a href=\"#7-%E6%80%BB%E7%BB%93\" rel=\"nofollow\">7. \u603b\u7ed3</a><ul>\n<li>\n<a href=\"#%E6%A0%B8%E5%BF%83%E4%BB%B7%E5%80%BC\" rel=\"nofollow\">\u6838\u5fc3\u4ef7\u503c</a><ul>\n<li><a href=\"#1-%E4%B8%9A%E5%8A%A1%E6%A8%A1%E5%9E%8B%E4%BC%98%E5%85%88\" rel=\"nofollow\">1. \u4e1a\u52a1\u6a21\u578b\u4f18\u5148</a></li>\n<li><a href=\"#2-clean-architecture-%E5%AE%9E%E7%8E%B0\" rel=\"nofollow\">2. Clean Architecture \u5b9e\u73b0</a></li>\n<li><a href=\"#3-%E8%87%AA%E5%8A%A8%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96\" rel=\"nofollow\">3. \u81ea\u52a8\u6027\u80fd\u4f18\u5316</a></li>\n<li><a href=\"#4-%E6%9E%B6%E6%9E%84%E5%8F%AF%E8%A7%86%E5%8C%96\" rel=\"nofollow\">4. \u67b6\u6784\u53ef\u89c6\u5316</a></li>\n<li><a href=\"#5-%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E6%8F%90%E5%8D%87\" rel=\"nofollow\">5. \u5f00\u53d1\u6548\u7387\u63d0\u5347</a></li>\n<li><a href=\"#6-%E6%9B%B4%E6%98%93%E6%B5%8B%E8%AF%95%E5%92%8C%E8%B0%83%E8%AF%95\" rel=\"nofollow\">6. \u66f4\u6613\u6d4b\u8bd5\u548c\u8c03\u8bd5</a></li>\n</ul>\n</li>\n<li>\n<a href=\"#%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF\" rel=\"nofollow\">\u9002\u7528\u573a\u666f</a><ul>\n<li><a href=\"#%E6%8E%A8%E8%8D%90%E4%BD%BF%E7%94%A8\" rel=\"nofollow\">\u63a8\u8350\u4f7f\u7528</a></li>\n<li><a href=\"#%E4%B8%8D%E6%8E%A8%E8%8D%90%E4%BD%BF%E7%94%A8\" rel=\"nofollow\">\u4e0d\u63a8\u8350\u4f7f\u7528</a></li>\n</ul>\n</li>\n<li><a href=\"#%E7%BB%93%E8%AF%AD\" rel=\"nofollow\">\u7ed3\u8bed</a></li>\n</ul>\n</li>\n<li><a href=\"#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99\" rel=\"nofollow\">\u53c2\u8003\u8d44\u6599</a></li>\n</ul>\n</li>\n</ul>\n<hr/>\n<h2>1. \u80cc\u666f\u4e0e\u95ee\u9898</h2>\n<h3>1.1 \u5f53\u524d\u4e3b\u6d41\u505a\u6cd5\u53ca\u5176\u75db\u70b9</h3>\n<p>\u5728 Python Web \u5f00\u53d1\u4e2d\uff0c\u5904\u7406\u590d\u6742\u4e1a\u52a1\u573a\u666f\u65f6\uff0c\u5f00\u53d1\u8005\u901a\u5e38\u91c7\u7528\u4ee5\u4e0b\u51e0\u79cd\u6a21\u5f0f\uff1a</p>\n<h4>\u6a21\u5f0f\u4e00\uff1a\u76f4\u63a5\u4f7f\u7528 ORM \uff08\u5982 SQLAlchemy \uff09</h4>\n<pre><code class=\"language-python\">@router.get(\"/teams/{team_id}\", response_model=TeamDetail)\nasync def get_team(team_id: int, session: AsyncSession = Depends(get_session)):\n    # \u83b7\u53d6\u56e2\u961f\u57fa\u672c\u4fe1\u606f\n    team = await session.get(Team, team_id)\n\n    # \u83b7\u53d6 Sprint \u5217\u8868\n    sprints = await session.execute(\n        select(Sprint).where(Sprint.team_id == team_id)\n    )\n    team.sprints = sprints.scalars().all()\n\n    # \u83b7\u53d6\u6bcf\u4e2a Sprint \u7684 Story\n    for sprint in team.sprints:\n        stories = await session.execute(\n            select(Story).where(Story.sprint_id == sprint.id)\n        )\n        sprint.stories = stories.scalars().all()\n\n        # \u83b7\u53d6\u6bcf\u4e2a Story \u7684 Task\n        for story in sprint.stories:\n            tasks = await session.execute(\n                select(Task).where(Task.story_id == story.id)\n            )\n            story.tasks = tasks.scalars().all()\n\n            # \u83b7\u53d6\u6bcf\u4e2a Task \u7684\u8d1f\u8d23\u4eba\n            for task in story.tasks:\n                task.owner = await session.get(User, task.owner_id)\n\n    return team\n</code></pre>\n<p>\u8fd9\u79cd\u505a\u6cd5\u5728\u7b80\u5355\u573a\u666f\u4e0b\u786e\u5b9e\u5f88\u76f4\u89c2\uff0c\u80fd\u591f\u5feb\u901f\u4e0a\u624b\u3002ORM \u7684\u7c7b\u578b\u5b89\u5168\u7279\u6027\u4e5f\u80fd\u5728\u7f16\u8bd1\u65f6\u53d1\u73b0\u4e00\u4e9b\u9519\u8bef\uff0c\u800c\u4e14\u4e0e\u6570\u636e\u5e93\u8868\u7ed3\u6784\u7684\u4e00\u4e00\u5bf9\u5e94\u5173\u7cfb\u8ba9\u4ee3\u7801\u5bb9\u6613\u7406\u89e3\u3002\u4f46\u5f53\u6211\u4eec\u9762\u5bf9\u771f\u6b63\u7684\u4e1a\u52a1\u573a\u666f\u65f6\uff0c\u8fd9\u79cd\u65b9\u5f0f\u7684\u7f3a\u9677\u5f88\u5feb\u5c31\u66b4\u9732\u51fa\u6765\u4e86\u3002</p>\n<p>\u6700\u81f4\u547d\u7684\u95ee\u9898\u662f N+1 \u67e5\u8be2\u3002\u867d\u7136\u4ee3\u7801\u770b\u8d77\u6765\u5f88\u6e05\u6670\uff0c\u4f46\u6267\u884c\u65f6\u4f1a\u4ea7\u751f\u5927\u91cf\u7684\u6570\u636e\u5e93\u67e5\u8be2\u3002\u6bcf\u5f53\u6211\u4eec\u8bbf\u95ee\u4e00\u4e2a\u5173\u8054\u5173\u7cfb\u65f6\uff0cORM \u5c31\u4f1a\u53d1\u8d77\u4e00\u6b21\u65b0\u7684\u67e5\u8be2\u3002\u5728\u6df1\u5c42\u5d4c\u5957\u7684\u60c5\u51b5\u4e0b\uff0c\u67e5\u8be2\u6570\u91cf\u4f1a\u5448\u6307\u6570\u7ea7\u589e\u957f\u3002\u66f4\u7cdf\u7cd5\u7684\u662f\uff0c\u8fd9\u79cd\u6027\u80fd\u95ee\u9898\u5728\u5f00\u53d1\u9636\u6bb5\u4e0d\u5bb9\u6613\u53d1\u73b0\uff0c\u53ea\u6709\u5f53\u6570\u636e\u91cf\u79ef\u7d2f\u5230\u4e00\u5b9a\u7a0b\u5ea6\u540e\u624d\u4f1a\u663e\u73b0\u51fa\u6765\uff0c\u90a3\u65f6\u5019\u5f80\u5f80\u5df2\u7ecf\u592a\u665a\u4e86\u3002</p>\n<p>\u4ee3\u7801\u7684\u7ec4\u7ec7\u65b9\u5f0f\u4e5f\u662f\u4e2a\u95ee\u9898\u3002\u6570\u636e\u83b7\u53d6\u7684\u903b\u8f91\u6563\u843d\u5728\u5404\u4e2a\u5d4c\u5957\u7684\u5faa\u73af\u4e2d\uff0c\u4e1a\u52a1\u903b\u8f91\u548c\u6570\u636e\u83b7\u53d6\u903b\u8f91\u6df7\u5728\u4e00\u8d77\uff0c\u96be\u4ee5\u9605\u8bfb\u548c\u7ef4\u62a4\u3002\u5f53\u9700\u8981\u4fee\u6539\u4e1a\u52a1\u89c4\u5219\u65f6\uff0c\u5f00\u53d1\u8005\u4e0d\u5f97\u4e0d\u5728\u590d\u6742\u7684\u5d4c\u5957\u7ed3\u6784\u4e2d\u5bfb\u627e\u4fee\u6539\u70b9\uff0c\u5f88\u5bb9\u6613\u5f15\u5165\u65b0\u7684 bug \u3002\u6027\u80fd\u66f4\u662f\u4e0d\u53ef\u63a7\uff0c\u968f\u7740\u6570\u636e\u91cf\u7684\u589e\u957f\uff0c\u67e5\u8be2\u6548\u7387\u4f1a\u6025\u5267\u4e0b\u964d\uff0c\u800c\u8fd9\u4e9b\u6027\u80fd\u74f6\u9888\u5f88\u96be\u5728\u4ee3\u7801\u5c42\u9762\u76f4\u63a5\u89c2\u5bdf\u5230\u3002</p>\n<p>\u6b64\u5916\uff0c\u76f8\u4f3c\u7684\u6570\u636e\u83b7\u53d6\u903b\u8f91\u4f1a\u5728\u591a\u4e2a API \u4e2d\u91cd\u590d\u51fa\u73b0\uff0c\u5bfc\u81f4\u5927\u91cf\u4ee3\u7801\u5197\u4f59\u3002\u5f53\u4e00\u4e2a API \u9700\u8981\u83b7\u53d6\"\u56e2\u961f\u53ca\u5176 Sprint\"\uff0c\u53e6\u4e00\u4e2a API \u9700\u8981\"\u56e2\u961f\u53ca\u5176\u6210\u5458\"\u65f6\uff0c\u5373\u4f7f\u5b83\u4eec\u7684\u67e5\u8be2\u903b\u8f91\u975e\u5e38\u76f8\u4f3c\uff0c\u4e5f\u4e0d\u5f97\u4e0d\u91cd\u590d\u7f16\u5199\u3002\u8fd9\u8fdd\u53cd\u4e86 DRY \uff08 Don't Repeat Yourself \uff09\u539f\u5219\uff0c\u589e\u52a0\u4e86\u7ef4\u62a4\u6210\u672c\u3002</p>\n<h4>\u6a21\u5f0f\u4e8c\uff1a\u4f7f\u7528 ORM \u7684 Eager Loading</h4>\n<pre><code class=\"language-python\">@router.get(\"/teams/{team_id}\", response_model=TeamDetail)\nasync def get_team(team_id: int, session: AsyncSession = Depends(get_session)):\n    # \u4f7f\u7528 joinedload \u9884\u52a0\u8f7d\u5173\u8054\u6570\u636e\n    result = await session.execute(\n        select(Team)\n        .options(\n            joinedload(Team.sprints)\n            .joinedload(Sprint.stories)\n            .joinedload(Story.tasks)\n            .joinedload(Task.owner)\n        )\n        .where(Team.id == team_id)\n    )\n    return result.scalar_one()\n</code></pre>\n<p>\u4e3a\u4e86\u89e3\u51b3 N+1 \u67e5\u8be2\u95ee\u9898\uff0cORM \u63d0\u4f9b\u4e86 Eager Loading \u673a\u5236\uff0c\u8ba9\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7 <code>joinedload</code>\u3001<code>selectinload</code> \u7b49\u65b9\u5f0f\u9884\u5148\u52a0\u8f7d\u5173\u8054\u6570\u636e\u3002\u4ee3\u7801\u53d8\u5f97\u66f4\u7b80\u6d01\u4e86\uff0c\u6027\u80fd\u95ee\u9898\u4e5f\u5f97\u5230\u4e86\u7f13\u89e3\u3002\u4f46\u8fd9\u79cd\u65b9\u6848\u4e5f\u5e26\u6765\u4e86\u65b0\u7684\u6311\u6218\u3002</p>\n<p>\u6700\u660e\u663e\u7684\u95ee\u9898\u662f\u7b1b\u5361\u5c14\u79ef\u3002\u5f53\u6211\u4eec\u4f7f\u7528\u591a\u5c42 JOIN \u9884\u52a0\u8f7d\u5173\u8054\u6570\u636e\u65f6\uff0c\u6570\u636e\u5e93\u8fd4\u56de\u7684\u6570\u636e\u91cf\u4f1a\u6025\u5267\u81a8\u80c0\u3002\u6bd4\u5982\u4e00\u4e2a\u56e2\u961f\u6709 10 \u4e2a Sprint \uff0c\u6bcf\u4e2a Sprint \u6709 10 \u4e2a Story \uff0c\u6bcf\u4e2a Story \u6709 10 \u4e2a Task \uff0c\u90a3\u4e48 JOIN \u7684\u7ed3\u679c\u96c6\u4f1a\u5305\u542b 1000 \u884c\u6570\u636e\uff0c\u5373\u4f7f\u6bcf\u884c\u7684\u6570\u636e\u91cf\u4e0d\u5927\uff0c\u4e5f\u4f1a\u7ed9\u7f51\u7edc\u4f20\u8f93\u548c\u5185\u5b58\u5360\u7528\u5e26\u6765\u538b\u529b\u3002</p>\n<p>\u66f4\u4e25\u91cd\u7684\u95ee\u9898\u662f\u7075\u6d3b\u6027\u5dee\u3002Eager Loading \u7684\u7b56\u7565\u662f\u5728\u4ee3\u7801\u4e2d\u786c\u7f16\u7801\u7684\uff0c\u6240\u6709\u4f7f\u7528\u540c\u4e00\u4e2a Model \u7684 API \u90fd\u4f1a\u6267\u884c\u76f8\u540c\u7684\u9884\u52a0\u8f7d\u903b\u8f91\u3002\u4f46\u4e0d\u540c\u7684 API \u5f80\u5f80\u9700\u8981\u4e0d\u540c\u7684\u6570\u636e\u3002\u6bd4\u5982\u4e00\u4e2a API \u53ea\u9700\u8981\u56e2\u961f\u7684\u57fa\u672c\u4fe1\u606f\uff0c\u53e6\u4e00\u4e2a API \u9700\u8981\u56e2\u961f\u7684 Sprint \uff0c\u8fd8\u6709\u4e00\u4e2a API \u9700\u8981\u56e2\u961f\u7684\u6210\u5458\u3002\u5982\u679c\u7edf\u4e00\u4f7f\u7528 Eager Loading \u52a0\u8f7d\u6240\u6709\u5173\u8054\u6570\u636e\uff0c\u5c31\u4f1a\u51fa\u73b0\u8fc7\u5ea6\u83b7\u53d6\u7684\u95ee\u9898\uff0c\u524d\u7aef\u4e0d\u9700\u8981\u7684\u6570\u636e\u4e5f\u88ab\u67e5\u8be2\u548c\u4f20\u8f93\u4e86\uff0c\u6d6a\u8d39\u4e86\u8d44\u6e90\u3002</p>\n<p>\u914d\u7f6e Eager Loading \u672c\u8eab\u5c31\u5f88\u590d\u6742\u3002\u5f00\u53d1\u8005\u9700\u8981\u7406\u89e3 <code>lazy</code>\u3001<code>joinedload</code>\u3001<code>selectinload</code>\u3001<code>subquery</code> \u7b49\u591a\u79cd\u52a0\u8f7d\u7b56\u7565\u7684\u533a\u522b\uff0c\u77e5\u9053\u4ec0\u4e48\u65f6\u5019\u7528\u54ea\u4e00\u79cd\uff0c\u4ee5\u53ca\u5b83\u4eec\u5404\u81ea\u4f1a\u6709\u4ec0\u4e48\u526f\u4f5c\u7528\u3002\u8fd9\u79cd\u914d\u7f6e\u9519\u8bef\u5f88\u5bb9\u6613\u5bfc\u81f4\u6027\u80fd\u95ee\u9898\u6216\u610f\u5916\u7684\u6570\u636e\u52a0\u8f7d\u884c\u4e3a\u3002\u800c\u4e14\uff0c\u8fd9\u79cd\"\u4e00\u5200\u5207\"\u7684\u914d\u7f6e\u65b9\u5f0f\u610f\u5473\u7740\u6240\u6709 API \u90fd\u4f7f\u7528\u76f8\u540c\u7684\u52a0\u8f7d\u7b56\u7565\uff0c\u65e0\u6cd5\u9488\u5bf9\u7279\u5b9a\u573a\u666f\u8fdb\u884c\u4f18\u5316\u3002</p>\n<h4>\u6a21\u5f0f\u4e09\uff1a\u624b\u52a8\u7ec4\u88c5\u6570\u636e</h4>\n<pre><code class=\"language-python\">@router.get(\"/teams/{team_id}\", response_model=TeamDetail)\nasync def get_team(team_id: int, session: AsyncSession = Depends(get_session)):\n    # 1. \u6279\u91cf\u83b7\u53d6\u6240\u6709\u9700\u8981\u7684\u6570\u636e\n    team = await session.get(Team, team_id)\n\n    sprints_result = await session.execute(\n        select(Sprint).where(Sprint.team_id == team_id)\n    )\n    sprint_ids = [s.id for s in sprints_result.scalars().all()]\n\n    stories_result = await session.execute(\n        select(Story).where(Story.sprint_id.in_(sprint_ids))\n    )\n    story_ids = [s.id for s in stories_result.scalars().all()]\n\n    tasks_result = await session.execute(\n        select(Task).where(Story.id.in_(story_ids))\n    )\n    tasks = tasks_result.scalars().all()\n\n    owner_ids = list(set(t.owner_id for t in tasks))\n    owners_result = await session.execute(\n        select(User).where(User.id.in_(owner_ids))\n    )\n    owners = {u.id: u for u in owners_result.scalars().all()}\n\n    # 2. \u624b\u52a8\u7ec4\u88c5\u6570\u636e\u7ed3\u6784\n    sprint_dict = {s.id: s for s in sprints_result.scalars().all()}\n    story_dict = {s.id: s for s in stories_result.scalars().all()}\n\n    for story in story_dict.values():\n        story.tasks = [t for t in tasks if t.story_id == story.id]\n        for task in story.tasks:\n            task.owner = owners.get(task.owner_id)\n\n    for sprint in sprint_dict.values():\n        sprint.stories = [s for s in story_dict.values() if s.sprint_id == sprint.id]\n\n    team.sprints = list(sprint_dict.values())\n\n    return team\n</code></pre>\n<p>\u4e3a\u4e86\u83b7\u5f97\u6700\u4f18\u7684\u6027\u80fd\u548c\u7cbe\u786e\u7684\u6570\u636e\u63a7\u5236\uff0c\u6709\u7ecf\u9a8c\u7684\u5f00\u53d1\u8005\u4f1a\u9009\u62e9\u624b\u52a8\u7ec4\u88c5\u6570\u636e\u3002\u8fd9\u79cd\u65b9\u5f0f\u5b8c\u5168\u638c\u63a7\u67e5\u8be2\u903b\u8f91\uff0c\u53ef\u4ee5\u7cbe\u786e\u63a7\u5236\u6bcf\u4e2a\u67e5\u8be2\u7684 SQL \u8bed\u53e5\uff0c\u907f\u514d\u4e0d\u5fc5\u8981\u7684\u6570\u636e\u5e93\u8bbf\u95ee\u3002\u901a\u8fc7\u6279\u91cf\u67e5\u8be2\u548c\u667a\u80fd\u7684\u6570\u636e\u7ec4\u88c5\uff0c\u53ef\u4ee5\u83b7\u5f97\u6700\u4f73\u7684\u6027\u80fd\uff0c\u800c\u4e14\u6ca1\u6709\u5197\u4f59\u6570\u636e\u3002</p>\n<p>\u4f46\u8fd9\u79cd\u65b9\u5f0f\u7684\u4ee3\u4ef7\u662f\u4ee3\u7801\u53d8\u5f97\u975e\u5e38\u5197\u957f\u3002\u5982\u4e0a\u9762\u7684\u4f8b\u5b50\u6240\u793a\uff0c\u4e3a\u4e86\u83b7\u53d6\u4e00\u4e2a\u56e2\u961f\u7684\u5b8c\u6574\u4fe1\u606f\uff0c\u6211\u4eec\u9700\u8981\u7f16\u5199\u591a\u4e2a\u67e5\u8be2\uff0c\u624b\u52a8\u6784\u5efa\u6570\u636e\u5b57\u5178\uff0c\u7136\u540e\u901a\u8fc7\u5d4c\u5957\u5faa\u73af\u7ec4\u88c5\u6570\u636e\u3002\u4ee3\u7801\u7684\u957f\u5ea6\u548c\u590d\u6742\u5ea6\u90fd\u5927\u5e45\u589e\u52a0\uff0c\u800c\u771f\u6b63\u8868\u8fbe\u4e1a\u52a1\u903b\u8f91\u7684\u4ee3\u7801\u53cd\u800c\u88ab\u6df9\u6ca1\u5728\u6570\u636e\u7ec4\u88c5\u7684\u7ec6\u8282\u4e2d\u3002</p>\n<p>\u66f4\u5bb9\u6613\u51fa\u9519\u4e5f\u662f\u4e2a\u5927\u95ee\u9898\u3002\u624b\u52a8\u7ec4\u88c5\u6570\u636e\u6d89\u53ca\u5230\u5927\u91cf\u7684\u7d22\u5f15\u64cd\u4f5c\u548c\u5faa\u73af\u5d4c\u5957\uff0c\u5f88\u5bb9\u6613\u51fa\u73b0\u7d22\u5f15\u9519\u8bef\u3001\u7a7a\u6307\u9488\u5f15\u7528\u7b49 bug \u3002\u800c\u4e14\u8fd9\u4e9b\u9519\u8bef\u5f80\u5f80\u53ea\u6709\u5728\u8fd0\u884c\u65f6\u3001\u7279\u5b9a\u6570\u636e\u6761\u4ef6\u4e0b\u624d\u4f1a\u66b4\u9732\uff0c\u96be\u4ee5\u5728\u5f00\u53d1\u9636\u6bb5\u53d1\u73b0\u3002</p>\n<p>\u7ef4\u62a4\u6210\u672c\u66f4\u662f\u9ad8\u6602\u3002\u5f53\u4e1a\u52a1\u89c4\u5219\u53d1\u751f\u53d8\u5316\u65f6\uff08\u6bd4\u5982\u9700\u8981\u6dfb\u52a0\u4e00\u4e2a\u65b0\u7684\u5173\u8054\u5173\u7cfb\uff09\uff0c\u5f00\u53d1\u8005\u9700\u8981\u5728\u6240\u6709\u76f8\u5173\u7684 API \u4e2d\u4fee\u6539\u6570\u636e\u7ec4\u88c5\u903b\u8f91\u3002\u5982\u679c\u9057\u6f0f\u4e86\u67d0\u4e2a\u5730\u65b9\uff0c\u5c31\u4f1a\u5bfc\u81f4\u6570\u636e\u4e0d\u4e00\u81f4\u3002\u800c\u4e14\uff0c\u76f8\u4f3c\u7684\u6570\u636e\u7ec4\u88c5\u903b\u8f91\u4f1a\u5728\u591a\u4e2a API \u4e2d\u91cd\u590d\u51fa\u73b0\uff0c\u8fdd\u53cd\u4e86 DRY \u539f\u5219\u3002</p>\n<p>\u6700\u6839\u672c\u7684\u95ee\u9898\u662f\uff0c\u8fd9\u79cd\u4ee3\u7801\u5df2\u7ecf\u53d8\u6210\u4e86\u7eaf\u7cb9\u7684\u6570\u636e\u642c\u8fd0\u5de5\uff0c\u770b\u4e0d\u51fa\u4efb\u4f55\u4e1a\u52a1\u610f\u56fe\u3002\u4ee3\u7801\u4e2d\u5145\u6ee1\u4e86\u5b57\u5178\u64cd\u4f5c\u3001\u5faa\u73af\u5d4c\u5957\u3001\u7d22\u5f15\u67e5\u627e\uff0c\u800c\u8fd9\u4e9b\u90fd\u662f\u6280\u672f\u7ec6\u8282\uff0c\u4e0e\u4e1a\u52a1\u9700\u6c42\u6beb\u65e0\u5173\u7cfb\u3002\u65b0\u52a0\u5165\u7684\u56e2\u961f\u6210\u5458\u5f88\u96be\u4ece\u8fd9\u4e9b\u4ee3\u7801\u4e2d\u7406\u89e3\u4e1a\u52a1\u903b\u8f91\uff0c\u4e1a\u52a1\u77e5\u8bc6\u7684\u4f20\u9012\u53d8\u5f97\u5f02\u5e38\u56f0\u96be\u3002</p>\n<h4>\u6a21\u5f0f\u56db\uff1a\u4f7f\u7528 GraphQL</h4>\n<pre><code class=\"language-python\">type Query {\n    team(id: ID!): Team\n}\n\ntype Team {\n    id: ID!\n    name: String!\n    sprints: [Sprint!]!\n}\n\ntype Sprint {\n    id: ID!\n    name: String!\n    stories: [Story!]!\n}\n\ntype Story {\n    id: ID!\n    name: String!\n    tasks: [Task!]!\n}\n\ntype Task {\n    id: ID!\n    name: String!\n    owner: User!\n}\n</code></pre>\n<p>GraphQL \u786e\u5b9e\u662f\u4e00\u4e2a\u5f88\u6709\u5438\u5f15\u529b\u7684\u65b9\u6848\u3002\u524d\u7aef\u53ef\u4ee5\u6309\u9700\u83b7\u53d6\u6570\u636e\uff0c\u9700\u8981\u4ec0\u4e48\u5b57\u6bb5\u5c31\u67e5\u4ec0\u4e48\u5b57\u6bb5\uff0c\u4e0d\u4f1a\u6709\u8fc7\u5ea6\u83b7\u53d6\u7684\u95ee\u9898\u3002\u5b83\u63d0\u4f9b\u4e86\u7c7b\u578b\u5b89\u5168\u7684\u67e5\u8be2\u63a5\u53e3\uff0c\u800c\u4e14\u901a\u8fc7 DataLoader \u53ef\u4ee5\u81ea\u52a8\u89e3\u51b3 N+1 \u67e5\u8be2\u95ee\u9898\u3002\u8fd9\u4e9b\u7279\u6027\u8ba9 GraphQL \u5728\u524d\u7aef\u5f00\u53d1\u4e2d\u5e7f\u53d7\u6b22\u8fce\u3002</p>\n<p>\u4f46 GraphQL \u7684\u5b66\u4e60\u66f2\u7ebf\u975e\u5e38\u9661\u5ced\u3002\u5f00\u53d1\u8005\u9700\u8981\u5b66\u4e60\u5168\u65b0\u7684\u67e5\u8be2\u8bed\u8a00\u3001Schema \u5b9a\u4e49\u3001Resolver \u7f16\u5199\u3001DataLoader \u914d\u7f6e\u7b49\u4e00\u5806\u6982\u5ff5\uff0c\u8fd9\u4e0e REST API \u7684\u76f4\u89c2\u6027\u5f62\u6210\u4e86\u9c9c\u660e\u5bf9\u6bd4\u3002\u66f4\u9ebb\u70e6\u7684\u662f\uff0cGraphQL \u7684\u8fc7\u5ea6\u7075\u6d3b\u6027\u7ed9\u540e\u7aef\u5e26\u6765\u4e86\u5de8\u5927\u7684\u6311\u6218\u3002\u524d\u7aef\u53ef\u4ee5\u6784\u9020\u4efb\u610f\u590d\u6742\u7684\u67e5\u8be2\uff0c\u6709\u4e9b\u67e5\u8be2\u751a\u81f3\u53ef\u80fd\u662f\u5f00\u53d1\u8005\u6ca1\u6709\u60f3\u5230\u8fc7\u7684\uff0c\u8fd9\u5bfc\u81f4\u540e\u7aef\u5f88\u96be\u8fdb\u884c\u9488\u5bf9\u6027\u7684\u4f18\u5316\u3002\u5f53\u4e00\u4e2a\u67e5\u8be2\u5d4c\u5957\u4e86 10 \u5c42\uff0c\u8fd4\u56de\u4e86\u6570\u767e\u4e07\u6761\u6570\u636e\u65f6\uff0c\u6570\u636e\u5e93\u548c\u670d\u52a1\u5668\u90fd\u4f1a\u9762\u4e34\u5de8\u5927\u7684\u538b\u529b\u3002</p>\n<p>\u8c03\u8bd5 GraphQL API \u4e5f\u6bd4\u8c03\u8bd5 REST API \u590d\u6742\u5f97\u591a\u3002\u5f53\u4e00\u4e2a GraphQL \u67e5\u8be2\u51fa\u9519\u65f6\uff0c\u9519\u8bef\u4fe1\u606f\u5f80\u5f80\u5f88\u96be\u5b9a\u4f4d\u5230\u5177\u4f53\u7684\u95ee\u9898\u6e90\u5934\u3002\u800c\u4e14 GraphQL \u9700\u8981\u989d\u5916\u7684\u670d\u52a1\u5668\u548c\u5de5\u5177\u94fe\u652f\u6301\uff0c\u65e0\u6cd5\u76f4\u63a5\u5229\u7528\u73b0\u6709\u7684 FastAPI \u751f\u6001\u7cfb\u7edf\u3002\u6bd4\u5982 FastAPI \u7684\u4f9d\u8d56\u6ce8\u5165\u3001\u4e2d\u95f4\u4ef6\u3001\u81ea\u52a8\u6587\u6863\u751f\u6210\u7b49\u7279\u6027\uff0c\u5728 GraphQL \u4e2d\u90fd\u65e0\u6cd5\u76f4\u63a5\u4f7f\u7528\u3002</p>\n<p>\u8fd8\u6709\u4e00\u4e2a\u66f4\u6df1\u5c42\u6b21\u7684\u95ee\u9898\u662f ERD \u548c\u7528\u4f8b\u7684\u754c\u9650\u6a21\u7cca\u3002GraphQL \u7684 Schema \u540c\u65f6\u626e\u6f14\u4e86\u5b9e\u4f53\u6a21\u578b\u548c\u67e5\u8be2\u63a5\u53e3\u4e24\u4e2a\u89d2\u8272\u3002\u5f53\u6211\u4eec\u8bbe\u8ba1\u4e00\u4e2a GraphQL Schema \u65f6\uff0c\u5f88\u96be\u786e\u5b9a\u5e94\u8be5\u6309\u7167\u5b9e\u4f53\u6765\u7ec4\u7ec7\uff08\u4e00\u4e2a Type \u5bf9\u5e94\u4e00\u4e2a\u6570\u636e\u5e93\u8868\uff09\uff0c\u8fd8\u662f\u6309\u7167\u7528\u4f8b\u6765\u7ec4\u7ec7\uff08\u4e0d\u540c\u7684\u4e1a\u52a1\u573a\u666f\u9700\u8981\u4e0d\u540c\u7684\u5b57\u6bb5\uff09\u3002\u8fd9\u5bfc\u81f4\u6700\u4f73\u5b9e\u8df5\u4e0d\u6e05\u6670\uff0c\u4e0d\u540c\u7684\u9879\u76ee\u3001\u4e0d\u540c\u7684\u5f00\u53d1\u8005\u53ef\u80fd\u6709\u5b8c\u5168\u4e0d\u540c\u7684\u7ec4\u7ec7\u65b9\u5f0f\u3002</p>\n<p>\u800c\u4e14\u968f\u7740\u4e1a\u52a1\u589e\u957f\uff0c\u6240\u6709\u7684\u7528\u4f8b\u90fd\u4f1a\u5806\u780c\u5728\u540c\u4e00\u4e2a Schema \u4e2d\uff0c\u5bfc\u81f4 Schema \u81a8\u80c0\uff0c\u96be\u4ee5\u7ef4\u62a4\u3002\u6743\u9650\u63a7\u5236\u4e5f\u53d8\u5f97\u5f02\u5e38\u590d\u6742\u3002\u4e0d\u540c\u7684 API \u7aef\u70b9\u53ef\u80fd\u6709\u4e0d\u540c\u7684\u6743\u9650\u8981\u6c42\uff0c\u4f46\u5b83\u4eec\u53ef\u80fd\u90fd\u67e5\u8be2\u540c\u4e00\u4e2a\u5b9e\u4f53\uff08\u6bd4\u5982 User \uff09\uff0c\u5728 GraphQL \u4e2d\u5f88\u96be\u9488\u5bf9\u4e0d\u540c\u7684\u67e5\u8be2\u573a\u666f\u5e94\u7528\u4e0d\u540c\u7684\u6743\u9650\u89c4\u5219\u3002</p>\n<h3>1.2 \u95ee\u9898\u6839\u6e90\u5206\u6790</h3>\n<p>\u4e0a\u9762\u6211\u4eec\u63a2\u8ba8\u7684\u6240\u6709\u6a21\u5f0f\uff0c\u867d\u7136\u8868\u9762\u4e0a\u7684\u95ee\u9898\u5404\u4e0d\u76f8\u540c\uff0c\u4f46\u5b83\u4eec\u7684\u6838\u5fc3\u56f0\u5883\u5176\u5b9e\u662f\u4e00\u81f4\u7684\u3002</p>\n<h4>\u95ee\u9898 1\uff1a\u4e1a\u52a1\u6a21\u578b\u4e0e\u6570\u636e\u6a21\u578b\u6df7\u6dc6</h4>\n<pre><code class=\"language-python\"># SQLAlchemy ORM \u540c\u65f6\u626e\u6f14\u4e24\u4e2a\u89d2\u8272\uff1a\n# 1. \u6570\u636e\u6a21\u578b\uff08\u5982\u4f55\u5b58\u50a8\uff09\n# 2. \u4e1a\u52a1\u6a21\u578b\uff08\u4e1a\u52a1\u6982\u5ff5\uff09\n\nclass Team(Base):\n    __tablename__ = 'teams'\n\n    id = Column(Integer, primary_key=True)\n    name = Column(String)\n\n    # \u8fd9\u662f\u6570\u636e\u5e93\u7684\u5916\u952e\u5173\u7cfb\uff0c\u8fd8\u662f\u4e1a\u52a1\u5173\u7cfb\uff1f\n    sprints = relationship(\"Sprint\", back_populates=\"team\")\n</code></pre>\n<p>\u5728\u4f20\u7edf\u7684 ORM \u5f00\u53d1\u4e2d\uff0c\u4e1a\u52a1\u6a21\u578b\u548c\u6570\u636e\u6a21\u578b\u662f\u6df7\u5728\u4e00\u8d77\u7684\u3002\u770b\u770b\u8fd9\u4e2a\u4f8b\u5b50\uff0c<code>Team</code> \u7c7b\u65e2\u8868\u8fbe\u4e86\u4e1a\u52a1\u6982\u5ff5\uff08\u56e2\u961f\u662f\u4ec0\u4e48\uff09\uff0c\u53c8\u627f\u8f7d\u4e86\u6570\u636e\u6a21\u578b\u7684\u7ec6\u8282\uff08\u5982\u4f55\u5728\u6570\u636e\u5e93\u4e2d\u5b58\u50a8\uff09\u3002\u5f53\u6211\u4eec\u5728 <code>sprints</code> \u5b57\u6bb5\u4e0a\u5b9a\u4e49 <code>relationship</code> \u65f6\uff0c\u8fd9\u5230\u5e95\u662f\u5728\u63cf\u8ff0\u4e00\u4e2a\u4e1a\u52a1\u5173\u7cfb\uff08\u56e2\u961f\u6709\u591a\u4e2a Sprint \uff09\uff0c\u8fd8\u662f\u5728\u58f0\u660e\u4e00\u4e2a\u6570\u636e\u5e93\u5916\u952e\u7ea6\u675f\uff1f\u8fd9\u79cd\u6a21\u7cca\u6027\u4f1a\u5bfc\u81f4\u5f88\u591a\u95ee\u9898\u3002</p>\n<p>\u6570\u636e\u5e93\u7684\u8bbe\u8ba1\u7ea6\u675f\u4f1a\u76f4\u63a5\u5f71\u54cd\u6211\u4eec\u7684\u4e1a\u52a1\u5efa\u6a21\u3002\u6bd4\u5982\uff0c\u5982\u679c\u6570\u636e\u5e93\u4e2d\u7684 <code>teams</code> \u8868\u6ca1\u6709\u76f4\u63a5\u5230 <code>users</code> \u7684\u5916\u952e\uff0c\u800c\u662f\u901a\u8fc7\u4e2d\u95f4\u8868 <code>team_members</code> \u5173\u8054\uff0c\u90a3\u4e48\u5728 ORM \u4e2d\u6211\u4eec\u4e5f\u5fc5\u987b\u901a\u8fc7\u8fd9\u4e2a\u4e2d\u95f4\u8868\u6765\u5b9a\u4e49\u5173\u7cfb\u3002\u8fd9\u610f\u5473\u7740\u4e1a\u52a1\u6a21\u578b\u88ab\u8feb\u9002\u5e94\u6570\u636e\u5e93\u7684\u5b9e\u73b0\u7ec6\u8282\uff0c\u800c\u4e0d\u662f\u53cd\u8fc7\u6765\u3002</p>\n<p>\u66f4\u4e25\u91cd\u7684\u662f\uff0c\u8fd9\u79cd\u65b9\u5f0f\u65e0\u6cd5\u8868\u8fbe\u8de8\u5e93\u3001\u8de8\u670d\u52a1\u7684\u4e1a\u52a1\u5173\u7cfb\u3002\u73b0\u4ee3\u7cfb\u7edf\u4e2d\uff0c\u6570\u636e\u53ef\u80fd\u5206\u5e03\u5728\u4e0d\u540c\u7684\u6570\u636e\u5e93\u4e2d\uff0c\u751a\u81f3\u5b58\u50a8\u5728\u5916\u90e8\u670d\u52a1\u91cc\u3002\u6bd4\u5982\u7528\u6237\u7684\u57fa\u672c\u4fe1\u606f\u5728 PostgreSQL \uff0c\u800c\u7528\u6237\u7684\u504f\u597d\u8bbe\u7f6e\u5728 MongoDB \uff0c\u7528\u6237\u7684\u5b9e\u65f6\u72b6\u6001\u5728 Redis \u4e2d\u3002ORM \u7684 <code>relationship</code> \u65e0\u6cd5\u8de8\u8d8a\u8fd9\u4e9b\u8fb9\u754c\uff0c\u4e1a\u52a1\u6a21\u578b\u56e0\u6b64\u88ab\u9650\u5236\u5728\u4e86\u5355\u4e00\u6570\u636e\u5e93\u7684\u8303\u56f4\u5185\u3002</p>\n<h4>\u95ee\u9898 2\uff1a\u4f9d\u8d56\u65b9\u5411\u9519\u8bef</h4>\n<pre><code>\u4f20\u7edf\u67b6\u6784\u7684\u4f9d\u8d56\u65b9\u5411\uff1a\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502   API Layer \u2502  \u2190 \u4f9d\u8d56\u4e8e\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n       \u2502\n       \u2193\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 ORM Models  \u2502  \u2190 \u4f9d\u8d56\u4e8e\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n       \u2502\n       \u2193\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502  Database   \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\u95ee\u9898\uff1a\u4e1a\u52a1\u89c4\u5219\u4f9d\u8d56\u4e8e\u6570\u636e\u5e93\u5b9e\u73b0\uff01\n</code></pre>\n<p>\u8fd9\u8fdd\u53cd\u4e86 Clean Architecture \u7684\u4f9d\u8d56\u89c4\u5219\u3002\u6b63\u786e\u7684\u4f9d\u8d56\u5173\u7cfb\u5e94\u8be5\u662f\uff1a\u4e1a\u52a1\u89c4\u5219\u6700\u7a33\u5b9a\uff0c\u4e0d\u4f9d\u8d56\u4efb\u4f55\u5916\u5c42\uff1b\u6570\u636e\u5e93\u662f\u5b9e\u73b0\u7ec6\u8282\uff0c\u5e94\u8be5\u4f9d\u8d56\u4e1a\u52a1\u89c4\u5219\uff1b\u5f53\u6570\u636e\u5e93\u53d8\u5316\u65f6\uff0c\u4e1a\u52a1\u89c4\u5219\u4e0d\u5e94\u8be5\u53d7\u5f71\u54cd\u3002\u4f46\u4f20\u7edf\u67b6\u6784\u7684\u4f9d\u8d56\u65b9\u5411\u6070\u6070\u76f8\u53cd\uff0c\u4e1a\u52a1\u89c4\u5219\u88ab\u6570\u636e\u5e93\u7684\u5b9e\u73b0\u7ec6\u8282\u6240\u7ed1\u67b6\u3002</p>\n<h4>\u95ee\u9898 3\uff1a\u7f3a\u5c11\u4e1a\u52a1\u5173\u7cfb\u7684\u663e\u5f0f\u58f0\u660e</h4>\n<pre><code class=\"language-python\"># \u4f20\u7edf\u65b9\u5f0f\uff1a\u4e1a\u52a1\u5173\u7cfb\u9690\u85cf\u5728\u67e5\u8be2\u4e2d\nasync def get_team_tasks(team_id: int):\n    # \"\u56e2\u961f\u7684\u4efb\u52a1\"\u8fd9\u4e2a\u4e1a\u52a1\u6982\u5ff5\u9690\u85cf\u5728 SQL WHERE \u4e2d\n    result = await session.execute(\n        select(Task)\n        .join(Sprint, Sprint.id == Task.sprint_id)\n        .where(Sprint.team_id == team_id)\n    )\n    return result.scalars().all()\n</code></pre>\n<p>\u4e1a\u52a1\u5173\u7cfb\u6ca1\u6709\u88ab\u663e\u5f0f\u58f0\u660e\u51fa\u6765\uff0c\u8fd9\u662f\u4e2a\u5f88\u9690\u853d\u4f46\u5371\u5bb3\u5f88\u5927\u7684\u95ee\u9898\u3002\u770b\u770b\u8fd9\u4e2a\u4f8b\u5b50\uff0c\"\u56e2\u961f\u7684\u4efb\u52a1\"\u662f\u4e00\u4e2a\u6e05\u6670\u7684\u4e1a\u52a1\u6982\u5ff5\uff0c\u4f46\u8fd9\u4e2a\u6982\u5ff5\u88ab\u9690\u85cf\u5728 SQL \u7684 JOIN \u548c WHERE \u5b50\u53e5\u4e2d\u3002\u65b0\u52a0\u5165\u56e2\u961f\u7684\u6210\u5458\u9700\u8981\u9605\u8bfb\u5927\u91cf\u4ee3\u7801\u624d\u80fd\u7406\u89e3\u7cfb\u7edf\u4e2d\u6709\u54ea\u4e9b\u4e1a\u52a1\u5173\u7cfb\uff0c\u8fd9\u4e9b\u5173\u7cfb\u662f\u5982\u4f55\u5b9a\u4e49\u7684\u3002\u66f4\u7cdf\u7cd5\u7684\u662f\uff0c\u6ca1\u6709\u81ea\u52a8\u5316\u7684\u65b9\u5f0f\u6765\u68c0\u67e5\u4e1a\u52a1\u5173\u7cfb\u7684\u4e00\u81f4\u6027\u3002\u5f53\u9700\u6c42\u53d8\u5316\u9700\u8981\u4fee\u6539\u67d0\u4e2a\u5173\u7cfb\u65f6\uff0c\u5f00\u53d1\u8005\u5f88\u96be\u627e\u5230\u6240\u6709\u76f8\u5173\u7684\u4ee3\u7801\uff0c\u5f88\u5bb9\u6613\u9057\u6f0f\u67d0\u4e2a\u5730\u65b9\uff0c\u5bfc\u81f4\u4e1a\u52a1\u903b\u8f91\u7684\u4e0d\u4e00\u81f4\u3002</p>\n<h4>\u95ee\u9898 4\uff1a\u4e2d\u95f4\u8868\u7684\u6280\u672f\u66b4\u9732</h4>\n<p>\u5728 SQLAlchemy ORM \u4e2d\uff0c\u591a\u5bf9\u591a\u5173\u7cfb\u9700\u8981\u663e\u5f0f\u5b9a\u4e49\u4e2d\u95f4\u8868\uff0c\u8fd9\u5bfc\u81f4\u6280\u672f\u7ec6\u8282\u6cc4\u6f0f\u5230\u4e1a\u52a1\u5c42\u3002</p>\n<pre><code class=\"language-python\"># SQLAlchemy ORM\uff1a\u5fc5\u987b\u5b9a\u4e49\u4e2d\u95f4\u8868\nclass Team(Base):\n    __tablename__ = 'teams'\n    id = Column(Integer, primary_key=True)\n    name = Column(String)\n\n    # ORM relationship \u9700\u8981\u6307\u5b9a\u4e2d\u95f4\u8868\n    members = relationship(\"User\",\n                          secondary=\"team_members\",  # \u5fc5\u987b\u6307\u5b9a\u4e2d\u95f4\u8868\n                          back_populates=\"teams\")\n\nclass User(Base):\n    __tablename__ = 'users'\n    id = Column(Integer, primary_key=True)\n    name = Column(String)\n\n    teams = relationship(\"Team\",\n                        secondary=\"team_members\",  # \u5fc5\u987b\u6307\u5b9a\u4e2d\u95f4\u8868\n                        back_populates=\"members\")\n\n# \u4e2d\u95f4\u8868\uff08\u6280\u672f\u5b9e\u73b0\u7ec6\u8282\uff09\nclass TeamMember(Base):\n    __tablename__ = 'team_members'\n    team_id = Column(Integer, ForeignKey('teams.id'), primary_key=True)\n    user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)\n    role = Column(String)  # \u53ef\u80fd\u8fd8\u6709\u989d\u5916\u5b57\u6bb5\n\n# \u67e5\u8be2\u65f6\u9700\u8981\u5173\u5fc3\u4e2d\u95f4\u8868\u7684\u5b58\u5728\n@router.get(\"/teams/{team_id}\")\nasync def get_team_members(team_id: int, session: AsyncSession):\n    # \u5fc5\u987b\u901a\u8fc7\u4e2d\u95f4\u8868\u67e5\u8be2\n    result = await session.execute(\n        select(User)\n        .join(TeamMember, TeamMember.user_id == User.id)  # \u4e2d\u95f4\u8868\u66b4\u9732\n        .where(TeamMember.team_id == team_id)\n    )\n    return result.scalars().all()\n</code></pre>\n<p>\u8fd9\u4e2a\u95ee\u9898\u7684\u6839\u6e90\u5728\u4e8e\uff0cORM \u7684\u591a\u5bf9\u591a\u5173\u7cfb\u9700\u8981\u663e\u5f0f\u5b9a\u4e49\u4e2d\u95f4\u8868\uff0c\u8fd9\u5bfc\u81f4\u6280\u672f\u7ec6\u8282\u76f4\u63a5\u6cc4\u6f0f\u5230\u4e1a\u52a1\u5c42\u4ee3\u7801\u4e2d\u3002\u4e1a\u52a1\u4ee3\u7801\u5fc5\u987b\u77e5\u9053 <code>team_members</code> \u4e2d\u95f4\u8868\u7684\u5b58\u5728\uff0c\u67e5\u8be2\u65f6\u4e5f\u9700\u8981\u663e\u5f0f\u5730 join \u8fd9\u4e2a\u4e2d\u95f4\u8868\u3002\u8fd9\u589e\u52a0\u4e86\u4ee3\u7801\u590d\u6742\u5ea6\uff0c\u66f4\u91cd\u8981\u7684\u662f\uff0c\u4e1a\u52a1\u903b\u8f91\u88ab\u6570\u636e\u5e93\u7684\u5b9e\u73b0\u7ec6\u8282\u6240\u7ed1\u67b6\u3002</p>\n<p>\u66f4\u6df1\u5c42\u7684\u95ee\u9898\u662f\u4e1a\u52a1\u8bed\u4e49\u53d8\u5f97\u6a21\u7cca\u3002<code>TeamMember</code> \u5230\u5e95\u662f\u4e00\u4e2a\u6709\u610f\u4e49\u7684\u4e1a\u52a1\u6982\u5ff5\uff0c\u8fd8\u662f\u7eaf\u7cb9\u7684\u6280\u672f\u5b9e\u73b0\uff1f\u5982\u679c\u4e2d\u95f4\u8868\u8fd8\u6709\u989d\u5916\u7684\u5b57\u6bb5\uff08\u6bd4\u5982 <code>role</code> \u8868\u793a\u7528\u6237\u5728\u56e2\u961f\u4e2d\u7684\u89d2\u8272\uff0c<code>joined_at</code> \u8868\u793a\u52a0\u5165\u65f6\u95f4\uff09\uff0c\u8fd9\u4e9b\u5b57\u6bb5\u5e94\u8be5\u88ab\u5efa\u6a21\u4e3a\u72ec\u7acb\u7684\u5b9e\u4f53\u5417\uff1f\u4e0d\u540c\u7684\u5f00\u53d1\u8005\u53ef\u80fd\u7ed9\u51fa\u4e0d\u540c\u7684\u7b54\u6848\uff0c\u7f3a\u4e4f\u7edf\u4e00\u7684\u6307\u5bfc\u539f\u5219\u3002</p>\n<p>\u6570\u636e\u7ec4\u88c5\u4e5f\u56e0\u6b64\u53d8\u5f97\u590d\u6742\u3002\u67e5\u8be2\"\u56e2\u961f\u7684\u6240\u6709\u6210\u5458\"\u9700\u8981 join \u4e2d\u95f4\u8868\uff0c\u67e5\u8be2\"\u7528\u6237\u6240\u5c5e\u7684\u56e2\u961f\"\u4e5f\u9700\u8981 join \u4e2d\u95f4\u8868\u3002\u6240\u6709\u6d89\u53ca\u591a\u5bf9\u591a\u5173\u7cfb\u7684\u67e5\u8be2\u90fd\u53d8\u5f97\u5197\u957f\u548c\u96be\u4ee5\u7406\u89e3\u3002\u5f53\u4e1a\u52a1\u89c4\u5219\u8981\u6c42\"\u83b7\u53d6\u7528\u6237\u5728\u6240\u6709\u56e2\u961f\u4e2d\u7684\u89d2\u8272\"\u65f6\uff0c\u60c5\u51b5\u5c31\u66f4\u52a0\u590d\u6742\u4e86\u3002\u8fd9\u4e9b\u6280\u672f\u7ec6\u8282\u8ba9\u4e1a\u52a1\u903b\u8f91\u7684\u5b9e\u73b0\u53d8\u5f97\u5f02\u5e38\u6c89\u91cd\u3002</p>\n<p><strong>\u5bf9\u6bd4\uff1aPydantic-Resolve ERD \u7684\u65b9\u5f0f</strong></p>\n<pre><code class=\"language-python\"># ERD\uff1a\u4e1a\u52a1\u6982\u5ff5\u6e05\u6670\uff0c\u65e0\u9700\u5173\u5fc3\u4e2d\u95f4\u8868\nclass TeamEntity(BaseModel, BaseEntity):\n    \"\"\"\u56e2\u961f\u5b9e\u4f53 - \u4e1a\u52a1\u6982\u5ff5\"\"\"\n    __relationships__ = [\n        # \u76f4\u63a5\u8868\u8fbe\"\u56e2\u961f\u6709\u591a\u4e2a\u6210\u5458\"\u7684\u4e1a\u52a1\u5173\u7cfb\n        Relationship(\n            field='id',\n            target_kls=list[UserEntity],\n            loader=team_to_users_loader  # loader \u5185\u90e8\u5904\u7406\u4e2d\u95f4\u8868\n        ),\n    ]\n    id: int\n    name: str\n\nclass UserEntity(BaseModel, BaseEntity):\n    \"\"\"\u7528\u6237\u5b9e\u4f53 - \u4e1a\u52a1\u6982\u5ff5\"\"\"\n    __relationships__ = [\n        # \u76f4\u63a5\u8868\u8fbe\"\u7528\u6237\u5c5e\u4e8e\u591a\u4e2a\u56e2\u961f\"\u7684\u4e1a\u52a1\u5173\u7cfb\n        Relationship(\n            field='id',\n            target_kls=list[TeamEntity],\n            loader=user_to_teams_loader\n        ),\n    ]\n    id: int\n    name: str\n\n# Loader \u5b9e\u73b0\u7ec6\u8282\uff1a\u4e2d\u95f4\u8868\u53ea\u5728\u8fd9\u91cc\u51fa\u73b0\nasync def team_to_users_loader(team_ids: list[int]):\n    \"\"\"\u52a0\u8f7d\u56e2\u961f\u6210\u5458 - \u5185\u90e8\u5904\u7406\u4e2d\u95f4\u8868\"\"\"\n    async with get_session() as session:\n        # \u53ea\u6709\u8fd9\u91cc\u9700\u8981\u77e5\u9053\u4e2d\u95f4\u8868\u7684\u5b58\u5728\n        result = await session.execute(\n            select(User)\n            .join(TeamMember, TeamMember.user_id == User.id)\n            .where(TeamMember.team_id.in_(team_ids))\n        )\n        users = result.scalars().all()\n\n        # \u6784\u5efa\u6620\u5c04\n        users_by_team = {}\n        for user in users:\n            for tm in user.team_memberships:\n                if tm.team_id not in users_by_team:\n                    users_by_team[tm.team_id] = []\n                users_by_team[tm.team_id].append(user)\n\n        return [users_by_team.get(tid, []) for tid in team_ids]\n</code></pre>\n<p><strong>\u5173\u952e\u5dee\u5f02</strong>\uff1a</p>\n<table>\n<thead>\n<tr>\n<th>\u7ef4\u5ea6</th>\n<th>SQLAlchemy ORM</th>\n<th>Pydantic-Resolve ERD</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>\u4e2d\u95f4\u8868\u4f4d\u7f6e</strong></td>\n<td>\u66b4\u9732\u5728\u4e1a\u52a1\u5c42</td>\n<td>\u9690\u85cf\u5728 loader \u5b9e\u73b0\u4e2d</td>\n</tr>\n<tr>\n<td><strong>\u4e1a\u52a1\u8bed\u4e49</strong></td>\n<td>\u6280\u672f\u5173\u7cfb (<code>secondary</code>)</td>\n<td>\u4e1a\u52a1\u5173\u7cfb (<code>\u56e2\u961f\u5305\u542b\u6210\u5458</code>)</td>\n</tr>\n<tr>\n<td><strong>\u67e5\u8be2\u4ee3\u7801</strong></td>\n<td>\u9700\u8981 join \u4e2d\u95f4\u8868</td>\n<td><code>loader.load(team_id)</code></td>\n</tr>\n<tr>\n<td><strong>\u4ee3\u7801\u4f4d\u7f6e</strong></td>\n<td>\u5206\u6563\u5728\u591a\u5904</td>\n<td>\u96c6\u4e2d\u5728 loader</td>\n</tr>\n<tr>\n<td><strong>\u6d4b\u8bd5</strong></td>\n<td>\u4f9d\u8d56\u6570\u636e\u5e93\u8868\u7ed3\u6784</td>\n<td>\u53ef mock loader</td>\n</tr>\n</tbody></table><p><strong>\u67b6\u6784\u4f18\u52bf</strong>\uff1a</p>\n<pre><code>\u4f20\u7edf\u65b9\u5f0f\uff1a\nTeam \u2192 TeamMember (\u4e2d\u95f4\u8868) \u2192 User\n\u4e1a\u52a1\u5c42\u9700\u8981\u77e5\u9053\u4e2d\u95f4\u8868\u7684\u5b58\u5728\n\nPydantic-Resolve \u65b9\u5f0f\uff1a\nTeam \u2192 User (\u4e1a\u52a1\u5173\u7cfb)\n\u4e2d\u95f4\u8868\u662f\u6570\u636e\u5c42\u7684\u5b9e\u73b0\u7ec6\u8282\uff0c\u4e1a\u52a1\u5c42\u4e0d\u5173\u5fc3\n</code></pre>\n<p>\u8fd9\u610f\u5473\u7740\uff1a</p>\n<ol>\n<li>\n<p><strong>\u4e1a\u52a1\u6a21\u578b\u7eaf\u51c0</strong>\uff1aTeam \u548c User \u7684\u5173\u7cfb\u76f4\u63a5\u8868\u8fbe\u4e1a\u52a1\u8bed\u4e49</p>\n</li>\n<li>\n<p><strong>\u6280\u672f\u7ec6\u8282\u5c01\u88c5</strong>\uff1a\u4e2d\u95f4\u8868\u7684\u5b58\u5728\u88ab\u5c01\u88c5\u5728 loader \u4e2d</p>\n</li>\n<li>\n<p><strong>\u7075\u6d3b\u7684\u5b58\u50a8\u7b56\u7565</strong>\uff1a</p>\n<ul>\n<li>\u6570\u636e\u5e93\u53ef\u4ee5\u7528\u4e2d\u95f4\u8868\u5b9e\u73b0</li>\n<li>\u4e5f\u53ef\u4ee5\u7528 JSON \u5b57\u6bb5\u5b58\u50a8</li>\n<li>\u751a\u81f3\u53ef\u4ee5\u662f\u5916\u90e8\u670d\u52a1\uff08\u5982 LDAP \uff09</li>\n<li>\u4e1a\u52a1\u5c42\u4ee3\u7801\u65e0\u9700\u4fee\u6539</li>\n</ul>\n</li>\n<li>\n<p><strong>\u6613\u4e8e\u7406\u89e3</strong>\uff1a\u65b0\u4eba\u770b\u5230 ERD \u5c31\u80fd\u7406\u89e3\u4e1a\u52a1\u5173\u7cfb\uff0c\u4e0d\u9700\u8981\u5148\u5b66\u4e60\u6570\u636e\u5e93\u8bbe\u8ba1</p>\n</li>\n</ol>\n<hr/>\n<h2>2. Clean Architecture \u601d\u60f3</h2>\n<h3>2.1 \u6838\u5fc3\u539f\u5219</h3>\n<p>Clean Architecture \u7531 Robert C. Martin (Uncle Bob) \u63d0\u51fa\uff0c\u6838\u5fc3\u601d\u60f3\u662f\uff1a</p>\n<blockquote>\n<p><strong>\"Software architecture is the art of drawing lines that I call boundaries.\"</strong>\n<strong>\u8f6f\u4ef6\u67b6\u6784\u7684\u827a\u672f\u5728\u4e8e\u753b\u754c\u7ebf\u3002</strong></p>\n</blockquote>\n<h4>\u539f\u5219 1\uff1a\u4f9d\u8d56\u89c4\u5219</h4>\n<pre><code>\u5916\u5c42\u4f9d\u8d56\u5185\u5c42\uff0c\u5185\u5c42\u4e0d\u4f9d\u8d56\u5916\u5c42\u3002\n\n                \u2193 \u4f9d\u8d56\u65b9\u5411\n    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n    \u2502   Frameworks &amp;      \u2502  \u5916\u5c42\n    \u2502   Drivers           \u2502  (\u5b9e\u73b0\u7ec6\u8282)\n    \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n    \u2502   Interface         \u2502\n    \u2502   Adapters          \u2502\n    \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n    \u2502   Use Cases         \u2502\n    \u2502   (Application)     \u2502\n    \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n    \u2502   Entities          \u2502  \u5185\u5c42\n    \u2502   (Business Rules)  \u2502  (\u6838\u5fc3)\n    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n</code></pre>\n<p>\u9075\u5faa\u4f9d\u8d56\u89c4\u5219\u6709\u51e0\u4e2a\u5173\u952e\u70b9\u9700\u8981\u6ce8\u610f\u3002\u9996\u5148\uff0c\u5185\u5c42\u4e0d\u77e5\u9053\u5916\u5c42\u7684\u5b58\u5728\uff0c\u8fd9\u610f\u5473\u7740\u6838\u5fc3\u4e1a\u52a1\u903b\u8f91\u4e0d\u4f9d\u8d56\u4e8e\u4efb\u4f55\u6846\u67b6\u3001\u6570\u636e\u5e93\u6216 UI \u7684\u7ec6\u8282\u3002\u5176\u6b21\uff0c\u5185\u5c42\u4e0d\u5305\u542b\u5916\u5c42\u7684\u4fe1\u606f\uff0c\u6bd4\u5982\u4e1a\u52a1\u89c4\u5219\u4e0d\u5e94\u8be5\u77e5\u9053\u6570\u636e\u662f\u7528 PostgreSQL \u8fd8\u662f MongoDB \u5b58\u50a8\u7684\u3002\u6700\u540e\uff0c\u5916\u5c42\u7684\u5b9e\u73b0\u53ef\u4ee5\u968f\u65f6\u66ff\u6362\u800c\u4e0d\u5f71\u54cd\u5185\u5c42\uff0c\u8fd9\u610f\u5473\u7740\u6211\u4eec\u53ef\u4ee5\u4ece SQLAlchemy \u5207\u6362\u5230 MongoDB \uff0c\u6216\u8005\u4ece FastAPI \u5207\u6362\u5230 Django \uff0c\u800c\u4e1a\u52a1\u903b\u8f91\u4ee3\u7801\u65e0\u9700\u4fee\u6539\u3002</p>\n<h4>\u539f\u5219 2\uff1a\u4e1a\u52a1\u89c4\u5219\u72ec\u7acb</h4>\n<pre><code class=\"language-python\"># \u274c \u9519\u8bef\uff1a\u4e1a\u52a1\u89c4\u5219\u4f9d\u8d56\u6570\u636e\u5e93\nclass Task:\n    def calculate_priority(self, session):\n        # \u4e1a\u52a1\u903b\u8f91\u88ab\u6570\u636e\u5e93\u5b9e\u73b0\u7ec6\u8282\u6c61\u67d3\n        if self.assignee_id in session.query(TeamMember).filter_by(role='lead'):\n            return 'high'\n\n# \u2705 \u6b63\u786e\uff1a\u4e1a\u52a1\u89c4\u5219\u72ec\u7acb\nclass Task:\n    def calculate_priority(self, assignee_roles):\n        # \u4e1a\u52a1\u903b\u8f91\u53ea\u4f9d\u8d56\u4e1a\u52a1\u6982\u5ff5\n        if 'lead' in assignee_roles:\n            return 'high'\n</code></pre>\n<h4>\u539f\u5219 3\uff1a\u8de8\u8fb9\u754c\u7684\u6570\u636e\u4f20\u9012</h4>\n<pre><code class=\"language-python\"># \u5185\u5c42\u5b9a\u4e49\u6570\u636e\u7ed3\u6784\nclass TaskEntity(BaseModel):\n    id: int\n    name: str\n    assignee_id: int\n\n# \u5916\u5c42\u8d1f\u8d23\u8f6c\u6362\ndef task_entity_to_orm(entity: TaskEntity) -&gt; Task:\n    return Task(\n        id=entity.id,\n        name=entity.name,\n        assignee_id=entity.assignee_id\n    )\n</code></pre>\n<h3>2.2 \u4f9d\u8d56\u89c4\u5219</h3>\n<p>\u5728 Web \u5f00\u53d1\u4e2d\uff0c\u4f9d\u8d56\u89c4\u5219\u53ef\u4ee5\u8fd9\u6837\u7406\u89e3\uff1a</p>\n<pre><code>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502         Presentation Layer (\u5916\u5c42)                   \u2502\n\u2502  - FastAPI Routes                                   \u2502\n\u2502  - Request/Response Models                          \u2502\n\u2502  - \u4f9d\u8d56: Application Layer                          \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                    \u2193\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502      Application Layer (Use Cases)                 \u2502\n\u2502  - \u4e1a\u52a1\u7528\u4f8b\uff08\u83b7\u53d6\u7528\u6237\u3001\u521b\u5efa\u8ba2\u5355\uff09                    \u2502\n\u2502  - \u4f9d\u8d56: Domain Layer                               \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                    \u2193\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502           Domain Layer (\u5185\u5c42)                      \u2502\n\u2502  - Entities (\u4e1a\u52a1\u5b9e\u4f53)                              \u2502\n\u2502  - Business Rules (\u4e1a\u52a1\u89c4\u5219)                        \u2502\n\u2502  - Value Objects (\u503c\u5bf9\u8c61)                           \u2502\n\u2502  - \u4e0d\u4f9d\u8d56\u4efb\u4f55\u5916\u5c42                                    \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                    \u2193\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502    Infrastructure Layer (\u6700\u5916\u5c42)                   \u2502\n\u2502  - Database (SQLAlchemy)                           \u2502\n\u2502  - External Services                               \u2502\n\u2502  - File System                                     \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n</code></pre>\n<p><strong>\u5173\u952e\u6d1e\u5bdf</strong>\uff1a</p>\n<ul>\n<li><strong>Entities \u4e0d\u5e94\u8be5\u77e5\u9053 SQLAlchemy \u7684\u5b58\u5728</strong></li>\n<li><strong>Business Rules \u4e0d\u5e94\u8be5\u77e5\u9053\u6570\u636e\u5e93\u8868\u7ed3\u6784</strong></li>\n<li><strong>Use Cases \u4e0d\u5e94\u8be5\u77e5\u9053 HTTP \u534f\u8bae\u7684\u7ec6\u8282</strong></li>\n</ul>\n<h3>2.3 \u5728 Web \u5f00\u53d1\u4e2d\u7684\u5e94\u7528</h3>\n<h4>\u4f20\u7edf\u67b6\u6784\u7684\u95ee\u9898</h4>\n<pre><code class=\"language-python\"># \u4f20\u7edf\u65b9\u5f0f\uff1a\u6240\u6709\u5c42\u6b21\u8026\u5408\n\n# Domain Layer (\u5e94\u8be5\u72ec\u7acb\uff0c\u4f46\u5b9e\u9645\u4e0a\u4f9d\u8d56\u4e86 ORM)\nclass User(Base):  # \u2190 SQLAlchemy Base\n    __tablename__ = 'users'\n    id = Column(Integer, primary_key=True)\n\n# Application Layer (\u5e94\u8be5\u53ea\u4f9d\u8d56 Domain \uff0c\u4f46\u76f4\u63a5\u4f7f\u7528\u4e86 ORM)\nasync def create_user(data: dict, session: AsyncSession):\n    user = User(**data)  # \u2190 \u76f4\u63a5\u4f7f\u7528 ORM Model\n    session.add(user)\n    await session.commit()\n\n# Presentation Layer\n@router.post(\"/users\")\nasync def api_create_user(data: dict, session=Depends(get_session)):\n    return await create_user(data, session)  # \u2190 \u66b4\u9732\u4e86\u6570\u636e\u5e93\u7ec6\u8282\n</code></pre>\n<p>\u8fd9\u6bb5\u4ee3\u7801\u66b4\u9732\u4e86\u4f20\u7edf\u67b6\u6784\u7684\u6838\u5fc3\u95ee\u9898\u3002SQLAlchemy \u867d\u7136\u5efa\u7acb\u4e86\u5bf9\u8c61\u5173\u7cfb\u6620\u5c04\uff08 ORM \uff09\uff0c\u8ba9\u6570\u636e\u5e93\u8868\u53ef\u4ee5\u901a\u8fc7 Python \u5bf9\u8c61\u6765\u64cd\u4f5c\uff0c\u4f46\u8fd9\u79cd\u6620\u5c04\u5173\u7cfb\u8fc7\u4e8e\u7d27\u5bc6\u3002ORM Model \u65e2\u627f\u62c5\u4e86\u6570\u636e\u6301\u4e45\u5316\u7684\u804c\u8d23\uff0c\u53c8\u8981\u8868\u8fbe\u4e1a\u52a1\u6982\u5ff5\uff0c\u5bfc\u81f4\u5bf9\u8c61\u65e0\u6cd5\u81ea\u7531\u5730\u4ee3\u8868\u4e1a\u52a1\u6a21\u578b\u3002\u4e1a\u52a1\u5b9e\u4f53\u88ab\u6570\u636e\u5e93\u7684\u5b9e\u73b0\u7ec6\u8282\u6240\u7ed1\u67b6\uff0c\u6bcf\u4e2a\u5b57\u6bb5\u3001\u6bcf\u4e2a\u5173\u7cfb\u90fd\u5fc5\u987b\u4e0e\u6570\u636e\u5e93\u8868\u7ed3\u6784\u4e00\u4e00\u5bf9\u5e94\uff0c\u5b8c\u5168\u5931\u53bb\u4e86\u4f5c\u4e3a\u72ec\u7acb\u4e1a\u52a1\u6982\u5ff5\u5b58\u5728\u7684\u81ea\u7531\u3002</p>\n<p>\u66f4\u6df1\u5c42\u6b21\u7684\u95ee\u9898\u5305\u62ec\uff1a</p>\n<ol>\n<li><strong>Domain Layer \u88ab SQLAlchemy \u7ed1\u5b9a</strong>\uff1a\u4e1a\u52a1\u5b9e\u4f53\u7ee7\u627f\u4e86 SQLAlchemy \u7684 Base \uff0c\u65e0\u6cd5\u72ec\u7acb\u4e8e\u6570\u636e\u5e93\u5b58\u5728</li>\n<li><strong>\u4e1a\u52a1\u903b\u8f91\u65e0\u6cd5\u8131\u79bb\u6570\u636e\u5e93\u6d4b\u8bd5</strong>\uff1a\u7f16\u5199\u5355\u5143\u6d4b\u8bd5\u65f6\u5fc5\u987b\u542f\u52a8\u5b8c\u6574\u7684\u6570\u636e\u5e93\u73af\u5883\uff0c\u5927\u5927\u964d\u4f4e\u4e86\u6d4b\u8bd5\u6548\u7387</li>\n<li><strong>\u5207\u6362\u6570\u636e\u5e93\u9700\u8981\u4fee\u6539\u6240\u6709\u5c42</strong>\uff1a\u5f53\u4ece PostgreSQL \u8fc1\u79fb\u5230 MongoDB \u65f6\uff0c\u6240\u6709\u4f7f\u7528 ORM Model \u7684\u4ee3\u7801\u90fd\u9700\u8981\u91cd\u5199</li>\n</ol>\n<hr/>\n<p>\u3002\u3002\u3002</p>\n", 
      "date_published": "2026-01-11T10:26:15+00:00", 
      "title": "\u57fa\u4e8e Pydantic-Resolve \u548c FastAPI-Voyager \u7684 Clean Architecture \u5b9e\u8df5 -- \u4e00\u5957\u9762\u5411\u590d\u6742\u4e1a\u52a1\u573a\u666f\u7684 Python Web \u5f00\u53d1\u65b9\u6cd5\u8bba", 
      "id": "https://www.v2ex.com/t/1184657"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/gyinbj", 
        "name": "gyinbj", 
        "avatar": "https://cdn.v2ex.com/avatar/e2d4/6877/360949_large.png?m=1645611391"
      }, 
      "url": "https://www.v2ex.com/t/1181506", 
      "date_modified": "2025-12-26T16:57:32+00:00", 
      "content_html": "fastapi \u6846\u67b6\u3002 \u5f88\u660e\u786e\u4e86\u5c31\u662f\u8981\u5b66\u8fd9\u4e2a\u3002<br /><br />\u539f\u6765\u662f phper \uff0c\u540e\u6765\u4e00\u76f4\u641e\u6570\u636e\u5e93 \u670d\u52a1\u5668\u4e86...<br /><br />py \u9700\u8981\u4ece\u54ea\u91cc\u5165\u624b\uff0c\u5c31\u662f\u5e0c\u671b\u627e\u4e00\u4e9b\u83b7\u5f97\u6bd4\u8f83\u5b9e\u7528\u7684\u5b66\u4e60\u8d44\u6599\u7684\u6377\u5f84 \u63a8\u8350\u4e00\u4e0b\u4e66\u7c4d \u535a\u4e3b\u4e4b\u7c7b\u7684\u3002<br /><br />\u8981\u5e26\u5a03 \u5b9e\u5728\u662f\u6ca1\u6709\u592a\u591a\u7684\u65f6\u95f4  \u611f\u6fc0\u4e0d\u5c3d\uff01\uff01\uff01", 
      "date_published": "2025-12-26T16:55:38+00:00", 
      "title": "\u5b66\u4e60\u80fd\u529b\u4e0d\u662f\u5f88\u5f3a \u73b0\u5728\u9700\u8981\u5b66 py \u6709\u4ec0\u4e48\u597d\u7684\u63a8\u8350\u5417\uff1f", 
      "id": "https://www.v2ex.com/t/1181506"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/simple2025", 
        "name": "simple2025", 
        "avatar": "https://cdn.v2ex.com/gravatar/d8be1ee891483e0287a7350aae996608?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1177771", 
      "date_modified": "2025-12-09T05:27:55+00:00", 
      "content_html": "<p>\u7cfb\u7edf\u662f win10,</p>\n<p>\u6211\u73b0\u5728\u9047\u5230\u7684\u95ee\u9898,\u6211\u5728 invoke \u91cc\u6709\u4e2a check_alive \u7684\u51fd\u6570,\u91cc\u9762\u662f\u68c0\u67e5\n\u6211\u865a\u62df\u673a\u7684\u67d0\u4e2a\u7f51\u5740\u7f51\u9875\u80fd\u4e0d\u80fd\u8bbf\u95ee,\u5982\u679c\u4e0d\u80fd,\u90a3\u4e48\u8c03\u7528 vagrant reload \u91cd\u542f\u865a\u62df\u673a.</p>\n<p>\u73b0\u5728\u9047\u5230\u4e86\u95ee\u9898, \u8c03\u7528 vagrant reload \u6709\u65f6\u5019\u4f1a\u5361\u5728\u90a3\u91cc.\n\u6240\u4ee5\u6211\u60f3\u60f3\u8981\u4e0d\u8981\u6dfb\u52a0\u4e00\u4e2a\u8d85\u65f6,\u7136\u540e\u6211\u8bd5\u4e86\u51e0\u79cd</p>\n<ol>\n<li>\n<p>\u4f7f\u7528 subprocess.popen,\u7136\u540e communicate \u90a3\u91cc\u52a0 timeout \u53c2\u6570,\u6ca1\u7528</p>\n</li>\n<li>\n<p>\u4f7f\u7528 func_timeout \u5e93,\u4e5f\u6ca1\u6709\u7528.</p>\n</li>\n</ol>\n<p>\u4eca\u5929\u53c8\u51fa\u73b0\u4e86,\u6211\u5c1d\u8bd5\u4f7f\u7528 invoke \u7684 run,\u518d\u8bd5\u8bd5,\u4f60\u4eec\u6709\u6ca1\u6709\u4ec0\u4e48\u597d\u7684\u65b9\u6cd5,\u8fd9\u4e2a\u8c03\u7528\u5916\u90e8\u547d\u4ee4\u600e\u4e48\u8fd9\u4e48\u9ebb\u70e6\u5462?</p>\n", 
      "date_published": "2025-12-09T05:27:39+00:00", 
      "title": "Python \u6709\u6ca1\u6709 subprocess \u7684\u66ff\u4ee3\u54c1,\u88ab subprocess \u6298\u817e\u6b7b\u4e86.", 
      "id": "https://www.v2ex.com/t/1177771"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/guoguobaba", 
        "name": "guoguobaba", 
        "avatar": "https://cdn.v2ex.com/avatar/48a4/7fbb/307012_large.png?m=1742526918"
      }, 
      "url": "https://www.v2ex.com/t/1176823", 
      "title": "\u95ee\u4e2a\u6c49\u5b57\u5904\u7406\u7684\u95ee\u9898", 
      "id": "https://www.v2ex.com/t/1176823", 
      "date_published": "2025-12-04T02:14:43+00:00", 
      "content_html": "<p>\u6709\u4e2a\u6587\u4ef6\uff0c\u5305\u542b'\u2edd' 11997 \u548c\u98df\u662f\u76f8\u540c\u7684\u5b57\uff0c\u4f46\u662f\u662f\u4e0d\u540c\u7684\u7f16\u7801\uff0c</p>\n<p>\u2edd (U+2EDD)\u548c \u98df (U+98DF)\u7684\u5173\u7cfb\u662f\uff1a\u5b83\u4eec\u662f\u540c\u4e00\u4e2a\u5b57\u7684\u4e0d\u540c\u89c6\u89c9\u8868\u73b0\u5f62\u5f0f\uff0c\u4f46 Unicode \u6307\u5b9a U+98DF \u4e3a\u6807\u51c6\u5f62\u5f0f\uff0cU+2EDD \u4e3a\u5176\u5f02\u4f53\u5f62\u5f0f\uff08\u7279\u522b\u662f\u4f5c\u4e3a\u90e8\u9996\u65f6\uff09\u3002</p>\n<p>\u95ee\u4e86\u4e00\u4e0b AI \uff0c\u90fd\u662f\u8ba9\u679a\u4e3e\u505a\u4e00\u4e2a map \uff0c\u6709\u6ca1\u6709\u7edf\u4e00\u7684\u8f6c\u5316\u8fd9\u6837\u6c49\u5b57\u7684\u65b9\u6848\uff0cunicodedata.normalize \u4e0d\u884c\u3002</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/Leon6868", 
        "name": "Leon6868", 
        "avatar": "https://cdn.v2ex.com/avatar/14ed/48e6/438369_large.png?m=1748955348"
      }, 
      "url": "https://www.v2ex.com/t/1175519", 
      "title": "\u6709\u54ea\u4e9b Python 3.14 Free-Threading \u591a\u7ebf\u7a0b\u6027\u80fd\u5206\u6790\u65b9\u6848\uff1f", 
      "id": "https://www.v2ex.com/t/1175519", 
      "date_published": "2025-11-27T15:07:31+00:00", 
      "content_html": "<p>\u5982\u9898\uff0c\u6ca1\u627e\u5230\u5bf9\u4ee3\u7801\u5165\u4fb5\u5c0f\u3001\u80fd\u5904\u7406\u591a\u7ebf\u7a0b\u7684\u89e3\u51b3\u65b9\u6848\uff0c\u8bf7\u95ee\u5927\u5bb6\u90fd\u662f\u600e\u4e48\u89e3\u51b3\u7684\u5462\uff1f</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/Daming798", 
        "name": "Daming798", 
        "avatar": "https://cdn.v2ex.com/avatar/b2b5/a321/577749_large.png?m=1765431970"
      }, 
      "url": "https://www.v2ex.com/t/1175211", 
      "date_modified": "2025-11-26T08:00:12+00:00", 
      "content_html": "<h3>\u6211\u4eec\u5728\u505a\u4ec0\u4e48</h3>\n<p>\u6211\u4eec\u6b63\u9762\u5411\u4e2d\u5c0f\u4f01\u4e1a\u548c\u521d\u521b\u56e2\u961f\uff0c\u505a\u4e00\u6b3e\u4e13\u6ce8\u4e8e\u7528\u6237\u53cd\u9988\u7684\u7ba1\u7406\u5206\u6790\u5de5\u5177 \u2014\u2014 Feedalyze \u3002</p>\n<p>\u5b83\u80fd\u591a\u6e20\u9053\u7edf\u4e00\u6536\u96c6\u3001\u9ad8\u6548\u5904\u7406\u3001\u53ef\u89c6\u5316\u5206\u6790\u7528\u6237\u53cd\u9988\uff0c\u5e2e\u52a9\u4e2d\u5c0f\u4f01\u4e1a\u6d1e\u5bdf\u7528\u6237\u9700\u6c42\uff0c\u52a0\u901f\u4ea7\u54c1\u8fed\u4ee3\u3002\u5730\u5740\uff1a <a href=\"https://feedalyze.com/\" rel=\"nofollow\">https://feedalyze.com/</a></p>\n<p>Feedalyze \u5bf9\u6807\u56fd\u5916\u7684 <a href=\"https://www.uservoice.com/product\" rel=\"nofollow\">UserVoice</a> + <a href=\"https://dovetail.com/\" rel=\"nofollow\">Dovetail</a> \uff0c\u5e0c\u671b\u6210\u4e3a\u56fd\u5185\u4f01\u4e1a\u9996\u9009\u7684\u7528\u6237\u53cd\u9988\u7ba1\u7406\u5206\u6790\u5e73\u53f0\u3002</p>\n<h3>\u89e3\u51b3\u7684\u95ee\u9898</h3>\n<p>1 \u3001\u4ea7\u54c1\u5f00\u53d1\u8fed\u4ee3\u5e38\u9760\u731c\u6d4b\u6216\u7ecf\u9a8c\uff0c\u800c\u975e\u7528\u6237\u771f\u5b9e\u9700\u6c42\u3002\uff08\u5982\u679c\u53c2\u4e0e\u8fc7\u4e2d\u5c0f\u4f01\u4e1a\u4ea7\u54c1\u7814\u53d1\uff0c\u5c31\u80fd\u7406\u89e3\u8fd9\u70b9\uff09</p>\n<p>2 \u3001\u5728\u6536\u96c6\u3001\u5904\u7406\u548c\u5206\u6790\u7528\u6237\u53cd\u9988\uff0c\u8c03\u67e5\u7528\u6237\u9700\u6c42\u65f6\uff0c\u4f01\u4e1a\u6210\u672c\u9ad8\u6602</p>\n<p>3 \u3001\u96be\u4ee5\u53ca\u65f6\u6d1e\u5bdf\u7528\u6237\u9700\u6c42\uff0c\u5f71\u54cd\u4ea7\u54c1\u8fed\u4ee3\u901f\u5ea6\u548c\u51b3\u7b56\u6b63\u786e\u7387</p>\n<p>4 \u3001\u7f3a\u4e4f\u53ef\u91cf\u5316\u7684\u6570\u636e\u652f\u6491\uff0c\u4ea7\u54c1\u65b9\u5411\u5bb9\u6613\u504f\u79bb\u5e02\u573a\u9700\u6c42\u7b49</p>\n<h3>\u4e3a\u4ec0\u4e48\u503c\u5f97\u52a0\u5165\uff1f</h3>\n<ul>\n<li>\u76ee\u524d\u4ea7\u54c1\u548c demo \u672a\u4e0a\u7ebf\uff0c\u5df2\u6709 50 \u4f59\u4f4d\u8bd5\u7528\u8005\uff0c\u6570\u4f4d\u613f\u610f\u4ed8\u8d39\u7684\u4f01\u4e1a\u7528\u6237\u3002</li>\n<li>\u5e02\u573a\u5de8\u5927\uff0c\u56fd\u5185\u505a\u6b64\u7c7b\u5de5\u5177\u7684\u5f88\u5c11\uff0c\u56fd\u5916\u4ea7\u54c1\u4e0d\u9002\u5e94\u672c\u571f</li>\n<li>\u7c97\u653e\u65f6\u4ee3\u5df2\u8fc7\u53bb\uff0c\u4e14\u5927\u73af\u5883\u4e0d\u4f73\u7684\u60c5\u51b5\u4e0b\uff0c\u7528\u6237\u81f3\u4e0a\u7684\u7406\u5ff5\u6b63\u9010\u6b65\u6df1\u5165\u4f01\u4e1a</li>\n<li>\u5ba2\u6237\u7fa4\u4f53\u6781\u4e3a\u5e7f\u6cdb\uff0c\u9664\u4e92\u8054\u7f51\u884c\u4e1a\u5916\uff0c\u8fd8\u9002\u5408\u5236\u9020\u4e1a\u3001\u96f6\u552e\u4e1a\u3001\u91d1\u878d\u4e1a\u3001\u533b\u836f\u4e1a\u7b49\u7b49</li>\n<li>\u4f60\u5c06\u8d1f\u8d23\u6574\u4e2a\u540e\u7aef\u7684\u6280\u672f\u67b6\u6784\uff0c\u662f\u4ea7\u54c1\u7684\u6838\u5fc3\u6280\u672f\u521b\u5efa\u8005</li>\n</ul>\n<h3>\u4f60\u5c06\u8d1f\u8d23</h3>\n<ul>\n<li>0-1 \u5229\u7528 FastAPI \u6784\u5efa\u4ea7\u54c1\u540e\u7aef</li>\n<li>\u8d1f\u8d23\u4ea7\u54c1\u7684\u529f\u80fd\u5b9e\u73b0\uff0c\u8c03\u8bd5\u4e0e\u4f18\u5316</li>\n<li>\u6280\u672f\u8def\u7ebf\u89c4\u5212\u53ca\u67b6\u6784\u8bbe\u8ba1\uff0c\u4fdd\u8bc1\u7cfb\u7edf\u53ef\u6269\u5c55\u6027\u4e0e\u7a33\u5b9a\u6027</li>\n</ul>\n<h3>\u5e0c\u671b\u4f60</h3>\n<ul>\n<li>\u5728\u676d\u5dde\uff0c\u6709\u4e00\u5230\u4e09\u5e74\u4ee5\u4e0a\u7684 FastAPI \u5f00\u53d1\u7ecf\u9a8c</li>\n<li>\u719f\u6089 PostgreSQL \u3001Redis \u7b49\u5e38\u7528\u6570\u636e\u5e93</li>\n<li>\u826f\u597d\u7684\u7f16\u7a0b\u4e60\u60ef\uff0c\u6ce8\u91cd\u6548\u7387\uff0c\u4e3a\u4eba\u8bda\u5b9e\u5f00\u653e</li>\n</ul>\n<h3>\u6211\u4eec\u63d0\u4f9b</h3>\n<ul>\n<li><strong>\u5206\u7ea2\u671f\u6743\uff1a</strong>1-3%\uff0c\u4ee5\u603b\u51c0\u5229\u6da6\uff0c\u6309\u6301\u6709\u6bd4\u4f8b\u6bcf\u5e74\u5e95\u5206\u7ea2</li>\n<li>\u7ecf\u8425\uff1a\u4f60\u5c06\u53c2\u4e0e\u516c\u53f8\u7ecf\u8425\uff0c\u6709\u6295\u7968\u6743</li>\n<li>\u9ad8\u5ea6\u81ea\u4e3b\u6743\uff0c\u8d1f\u8d23\u6574\u4e2a\u540e\u7aef\u6280\u672f\u65b9\u5411</li>\n<li>\u85aa\u8d44\uff1a7k \u5de6\u53f3\uff0c\u534a\u5e74\u8c03\u6574\u4e00\u6b21</li>\n<li>\u6210\u4e3a\u4ea7\u54c1\u7684\u6838\u5fc3\u521b\u5efa\u8005</li>\n</ul>\n<h3>\u6211\u4eec\u6682\u672a\u63d0\u4f9b</h3>\n<ul>\n<li>\u4f18\u6e25\u7684\u529e\u516c\u73af\u5883</li>\n<li>\u6781\u4f73\u7684\u786c\u4ef6\u8bbe\u5907\uff08\u4f46\u591f\u7528\uff09</li>\n</ul>\n<h3>\u4f46\u6211\u4eec\u63d0\u4f9b\u66f4\u591a</h3>\n<ul>\n<li>\u4e00\u4e2a\u6301\u6709\u751f\u4ea7\u8d44\u6599\u7684\u673a\u4f1a</li>\n<li>\u4e00\u4e2a\u98ce\u9669\u6781\u4f4e\u7684\u521b\u4e1a\u673a\u4f1a</li>\n<li>\u4e00\u6b21\u521b\u4e1a\u6838\u5fc3\u6210\u5458\u7684\u5c65\u5386</li>\n</ul>\n<h3>\u5907\u6ce8</h3>\n<ul>\n<li>\u4e3a\u4ec0\u4e48\u662f\u5206\u7ea2\u671f\u6743\u800c\u4e0d\u662f\u80a1\u4efd\uff0c\u56e0\u4e3a\u6301\u80a1\u610f\u5473\u7740\u4f60\u4e5f\u8981\u627f\u62c5\u7ecf\u8425\u5931\u8d25\u548c\u4e8f\u635f\u7684\u98ce\u9669\u3002\u5982\u679c\u4f60\u6709\u52c7\u6c14\uff0c\u6211\u5f88\u4e50\u610f\u5c06\u5206\u7ea2\u671f\u6743\u8f6c\u4e3a\u80a1\u4efd\u3002</li>\n<li>7k \u7684\u85aa\u8d44\u592a\u5783\u573e\u4e86\u3002\u85aa\u8d44\u786e\u5b9e\u4e0d\u9ad8\uff0c\u4f46\u4f60\u62e5\u6709\u5206\u7ea2\u671f\u6743\uff0c\u8fd9\u8fd8\u53ef\u80fd\u662f\u4e00\u6b21\u957f\u8fdc\u89e3\u51b3\u7ecf\u6d4e\u95ee\u9898\u7684\u673a\u4f1a\u3002\u5982\u679c\u671f\u671b\u9ad8\u85aa\u804c\u4f4d\uff0c\u6216\u6709\u7ecf\u6d4e\u538b\u529b\uff0c\u8bf7\u5bfb\u627e\u5176\u5b83\u673a\u4f1a\u3002</li>\n<li>\u4e3a\u4ec0\u4e48\u662f 1-3% \u7684\u603b\u51c0\u5229\u6da6\u4f5c\u4e3a\u5206\u7ea2\u671f\u6743\uff1f\u521b\u4e1a\u521d\u671f\u4f1a\u9047\u5230\u5f88\u591a\u98ce\u9669\uff0c\u4e09\u5e74\u5185\u6211\u4f1a\u5c06 50% \u7684\u51c0\u5229\u6da6\u4f5c\u4e3a\u907f\u9669\u8d44\u91d1\uff0c\u975e\u5230\u5fc5\u8981\u65f6\u523b\u4e0d\u4f1a\u52a8\u300227% \u4f5c\u4e3a\u7ecf\u8425\u8d44\u91d1\uff0c23% \u4f5c\u4e3a\u5168\u4f53\u6210\u5458\u5206\u7ea2\u6c60\uff0c\u6211\u81ea\u5df1\u7684\u5206\u7ea2\u6bd4\u4f8b\u5728 3-5%\u3002\u5982\u679c\u5230\u4e86\u65e0\u53ef\u633d\u56de\u7684\u90a3\u4e00\u5929\uff0c\u6211\u5c06\u628a\u907f\u9669\u8d44\u91d1\u6309\u6bd4\u4f8b\u5206\u914d\u7ed9\u6240\u6709\u6210\u5458\u3002</li>\n<li>\u6709\u610f\u4e49\u7684\u5de5\u4f5c\u548c\u4eba\u9645\u5173\u7cfb\u3002\u6211\u6240\u8ffd\u6c42\u7684\u4e00\u662f\u6709\u610f\u4e49\u7684\u5de5\u4f5c\uff0c\u4e8c\u662f\u6709\u610f\u4e49\u7684\u3001\u6709\u4ef7\u503c\u7684\u56e2\u961f\u4eba\u9645\u5173\u7cfb\uff0c\u8fd9\u662f\u56e2\u961f\u7684\u57fa\u672c\u539f\u5219\u3002</li>\n<li>\u56e2\u961f\u89c4\u6a21\uff1a\u672a\u6765\u5c06\u5c3d\u53ef\u80fd\u514b\u5236\u56e2\u961f\u4eba\u5458\u5728 20 \u4eba\u4ee5\u5185\u3002</li>\n<li>\u521b\u4e1a\u8270\u8f9b\uff1a\u8fd9\u662f\u4e00\u6b21\u82e6\u903c\u7684\u521b\u4e1a\u4e4b\u65c5\uff0c\u9700\u8981\u4e3b\u52a8\u51fa\u51fb\uff0c\u8fce\u63a5\u6311\u6218\u3002\u5728\u6b64\u4e4b\u524d\uff0c\u8bf7\u505a\u597d\u5fc3\u7406\u51c6\u5907\u3002</li>\n</ul>\n<p>\u5982\u679c\u4f60\u5bf9\u8fd9\u4e2a\u673a\u4f1a\u611f\u5174\u8da3\uff0c\u6b22\u8fce\u52a0\u5fae\u4fe1\u4e00\u8d77\u804a\u804a\uff0c\u5403\u4e2a\u996d\u3002\u6dfb\u52a0\u65f6\u8bf7\u5907\u6ce8\uff1a\u59d3\u540d + FastAPI </p>\n<p>\u6211\u7684\u5fae\u4fe1\uff1aM-ing2020</p>\n", 
      "date_published": "2025-11-26T07:59:46+00:00", 
      "title": "\u62db\u52df\u4e00\u4f4d FastAPI \u5de5\u7a0b\u5e08\uff0c\u4f5c\u4e3a\u6280\u672f\u4f19\u4f34", 
      "id": "https://www.v2ex.com/t/1175211"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/arnoldnuo", 
        "name": "arnoldnuo", 
        "avatar": "https://cdn.v2ex.com/avatar/e65a/afe1/219837_large.png?m=1759371311"
      }, 
      "url": "https://www.v2ex.com/t/1174867", 
      "date_modified": "2025-11-26T00:17:16+00:00", 
      "content_html": "<p>chatgpt \u4e5f\u6ca1\u7ed9\u4e00\u4e2a\u5b9e\u7528\u7684\u65b9\u6848\uff0c\u4f60\u4eec\u90fd\u662f\u600e\u4e48\u7ba1\u7406\u81ea\u5df1\u7684 python \u73af\u5883\u7684\uff1f</p>\n", 
      "date_published": "2025-11-25T03:18:14+00:00", 
      "title": "\u4e3a\u4ec0\u4e48 Python \u7684\u5305\u7ba1\u7406\u8fd9\u4e48\u96be\u7528\uff0c\u6bd4 node \u7684 npm \u96be\u7528\u4e00\u4e07\u500d\uff0c\u6bcf\u6b21\u8fdb\u5165\u9879\u76ee\u90fd\u8981\u624b\u52a8\u6267\u884c\u4e00\u4e0b conda activate xxx\uff0c\u96be\u9053\u5c31\u6ca1\u6709\u9ed8\u8ba4\u7684 Python \u9879\u76ee\u7ea7\u522b\u7684\u4f9d\u8d56\u5417\uff1f", 
      "id": "https://www.v2ex.com/t/1174867"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/wxmomomowx", 
        "name": "wxmomomowx", 
        "avatar": "https://cdn.v2ex.com/gravatar/0f52ae06c426eec78cf5043021132462?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1174561", 
      "title": "\u7f51\u4e0a\u627e\u4e86\u4e2a\u6d41\u51fa\u7684 Python \u7a0b\u5e8f\uff0c\u60f3\u8fc7\u4e0b\u662f\u5426\u88ab\u6c61\u67d3\u4e86 \u90fd\u6709\u4ec0\u4e48\u63a8\u8350\u7684\u529e\u6cd5\u554a\uff1f", 
      "id": "https://www.v2ex.com/t/1174561", 
      "date_published": "2025-11-23T16:51:47+00:00", 
      "content_html": "\u7f51\u7ad9/\u7f51\u7edc\u7a0b\u5e8f\u6765\u7684\uff0c\u4f1a\u653e\u5c0f\u9e21\u8dd1\u8dd1\u770b\u3002<br />\u6709\u4ec0\u4e48\u76d1\u63a7/\u626b\u63cf\u7a0b\u5e8f\u63a8\u8350\u5417\uff1f python \u8bed\u8a00"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/susunus", 
        "name": "susunus", 
        "avatar": "https://cdn.v2ex.com/gravatar/f3db7707ceed2f547353eede641f5bf8?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1173212", 
      "title": "\u6709\u611f\u4e8e Golang\uff0c\u5982\u4f55\u7528 3~5 \u4e2a\u95ee\u9898\uff0c\u8bc6\u522b Python \u5f00\u53d1\u8005\u7684\u6280\u672f\u7d20\u517b\uff1f", 
      "id": "https://www.v2ex.com/t/1173212", 
      "date_published": "2025-11-17T02:57:54+00:00", 
      "content_html": "\u9762\u8bd5\u548c\u88ab\u9762\u8bd5\u4e2d\uff0c\u5de5\u7a0b\u5316\u80fd\u529b\uff08\u80fd\u5e72\u6d3b\uff09\u3001\u4ea4\u6d41\u80fd\u529b\uff08\u80fd\u914d\u5408\uff09\u3001\u6280\u672f\u6df1\u5ea6\uff08\u89e3\u51b3\u95ee\u9898\u80fd\u529b\uff09\u90fd\u5f88\u91cd\u8981\uff0c\u4f5c\u4e3a\u666e\u901a\u5f00\u53d1\u8005\u666e\u901a\u9762\u8bd5\u5b98\u5e94\u8be5\u600e\u4e48\u505a\u597d\u8fd9\u573a\u9762\u8bd5"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/guoguobaba", 
        "name": "guoguobaba", 
        "avatar": "https://cdn.v2ex.com/avatar/48a4/7fbb/307012_large.png?m=1742526918"
      }, 
      "url": "https://www.v2ex.com/t/1173180", 
      "title": "fastapi tortoise orm \u524d\u7aef\u7ba1\u7406\u6846\u67b6\u6709\u54ea\u4e2a\u597d\u7528\u7684", 
      "id": "https://www.v2ex.com/t/1173180", 
      "date_published": "2025-11-17T01:50:10+00:00", 
      "content_html": "<p>\u76ee\u524d\u662f fastapi+tortoise orm \uff0c\u8003\u5bdf\u4e86\u51e0\u4e2a</p>\n<p>django admin \u6709\u70b9\u91cd</p>\n<p>fastapi-admin\uff1a \u6298\u817e\u5f97\u5f88\u9ebb\u70e6\uff0c</p>\n<p>fastadmin: \u8fd9\u4e2a\u662f\u6700\u7b80\u5355\u7684\uff0c\u4f46\u662f\u529f\u80fd\u6709\u70b9\u7b80\u964b\uff0c\u5f88\u591a\u524d\u7aef\u7684\u529f\u80fd\u6ca1\u6709\u5b9e\u73b0\u3002</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/dylyft", 
        "name": "dylyft", 
        "avatar": "https://cdn.v2ex.com/gravatar/8975f667291303f787181c55b320a651?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1171662", 
      "title": "\u5c0f\u767d\u6c42\u6559\u4e2a\u5faa\u73af\u5bfc\u5165\u7684\u95ee\u9898", 
      "id": "https://www.v2ex.com/t/1171662", 
      "date_published": "2025-11-10T03:24:39+00:00", 
      "content_html": "<p>\u6700\u8fd1\u521a\u5f00\u59cb\u5b66 python \u548c fastapi, \u6309\u7167 fastapi \u6559\u7a0b\u5b66\u4e60\u65f6\u9047\u5230\u4e2a\u95ee\u9898, \u56e0\u4e3a\u4f7f\u7528 sqlmodel \u7684 relationship, \u5bfc\u81f4\u4e24\u4e2a\u6a21\u578b\u6587\u4ef6\u76f8\u4e92\u5bfc\u5165, \u7136\u540e\u62a5\u9519\u4e86, \u6839\u636e\u5b98\u65b9\u6587\u6863\u7684\u89e3\u51b3\u529e\u6cd5, \u4f7f\u7528 TYPE_CHECKING \u548c\u5b57\u7b26\u4e32\u7248\u672c\u7c7b\u578b\u540e\u6682\u65f6\u89e3\u51b3\u4e86\u62a5\u9519.<br/>\n\u4f46\u662f\u5f53\u63a5\u53e3\u901a\u8fc7 response_model \u6307\u5b9a\u4e86\u6570\u636e\u6a21\u578b(\u975e\u8868\u6a21\u578b)\u4f5c\u4e3a\u8fd4\u56de\u7c7b\u578b\u65f6, \u53c8\u51fa\u73b0\u4e86\u62a5\u9519, \u62a5\u9519\u63d0\u793a\u5982\u4e0b:</p>\n<pre><code>`TypeAdapter[typing.Annotated[app.models.teams.TeamPublicWithUser, FieldInfo(annotation=TeamPublicWithUser, required=True)]]` is not fully defined; you should define `typing.Annotated[app.models.teams.TeamPublicWithUser, FieldInfo(annotation=TeamPublicWithUser, required=True)]` and all referenced types, then call `.rebuild()` on the instance.\n</code></pre>\n<p>\u6c42\u5404\u4f4d\u5927\u4f6c\u5e2e\u5fd9\u770b\u770b, \u54ea\u91cc\u6709\u95ee\u9898.\npython \u7248\u672c\u7528\u7684\u662f 3.12, \u5177\u4f53\u4ee3\u7801\u5982\u4e0b<br/>\nmodels/<a href=\"http://users.py\" rel=\"nofollow\">users.py</a></p>\n<pre><code>from typing import TYPE_CHECKING, Any, Optional\n\nfrom sqlmodel import Field, Relationship, SQLModel\n\nif TYPE_CHECKING:\n    from .teams import Team, TeamPublic\n\n\nclass UserBase(SQLModel):\n    username: str = Field(index=True, max_length=255, unique=True)\n    email: str | None = Field(default=None, index=True, max_length=255)\n    is_active: bool = True\n    is_superuser: bool = False\n    full_name: str | None = Field(default=None, max_length=255)\n    team_id: int | None = Field(default=None, foreign_key=\"team.id\")\n\n\nclass User(UserBase, table=True):\n    id: int | None = Field(default=None, primary_key=True)\n    hashed_password: str\n    team: Optional[\"Team\"] = Relationship(back_populates=\"members\")\n\n\nclass UserPublic(UserBase):\n    id: int\n\n\nclass UserPublicWithTeam(UserPublic):\n    team: Optional[\"TeamPublic\"] = None\n</code></pre>\n<p>models/<a href=\"http://teams.py\" rel=\"nofollow\">teams.py</a></p>\n<pre><code>from typing import TYPE_CHECKING, Any, List\n\nfrom sqlmodel import Field, Relationship, SQLModel\n\nif TYPE_CHECKING:\n    from .users import User, UserPublic\n\n\nclass TeamBase(SQLModel):\n    name: str = Field(max_length=255)\n\n\nclass Team(TeamBase, table=True):\n    id: int | None = Field(default=None, primary_key=True)\n    members: List[\"User\"] = Relationship(back_populates=\"team\")\n\n\nclass TeamPublic(TeamBase):\n    id: int\n\n\nclass TeamPublicWithUser(TeamPublic):\n    members: List[\"UserPublic\"] = []\n</code></pre>\n<p>router/<a href=\"http://users.py\" rel=\"nofollow\">users.py</a></p>\n<pre><code># \u7528\u6237\u76f8\u5173\u63a5\u53e3\nfrom sqlmodel import and_, select\n\nfrom app.api.deps import SessionDep\nfrom app.models.users import (\n    User,\n    UserPublicWithTeam,\n)\nfrom app.new_router import new_router\n\nrouter = new_router(prefix=\"/users\", tags=[\"\u7528\u6237\u7ba1\u7406\"])\n\n\n@router.get(\"/{id}\", summary=\"\u83b7\u53d6\u7528\u6237\u8be6\u60c5\", response_model=UserPublicWithTeam)\nasync def get_user_api(db: SessionDep, id: int):\n    user = db.exec(select(User).where(and_(User.id == id, User.is_active))).first()\n    return user\n</code></pre>\n<p>router/<a href=\"http://teams.py\" rel=\"nofollow\">teams.py</a></p>\n<pre><code># \u56e2\u961f\u76f8\u5173\u63a5\u53e3\nfrom sqlmodel import select\n\nfrom app.api.deps import SessionDep\nfrom app.models.teams import (\n    Team,\n    TeamPublicWithUser,\n)\nfrom app.new_router import new_router\n\nrouter = new_router(prefix=\"/teams\", tags=[\"\u56e2\u961f\u7ba1\u7406\"])\n\n\n@router.get(\"/{id}\", summary=\"\u83b7\u53d6\u56e2\u961f\u8be6\u60c5\", response_model=TeamPublicWithUser)\nasync def get_team_api(db: SessionDep, id: int):\n    team = db.exec(select(Team).where(Team.id == id)).first()\n    return team\n</code></pre>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/enrolls", 
        "name": "enrolls", 
        "avatar": "https://cdn.v2ex.com/avatar/5e2c/35af/114646_large.png?m=1763529309"
      }, 
      "url": "https://www.v2ex.com/t/1171056", 
      "title": "\u7528 Python \u5199\u4e86\u4e2a\u5f02\u6b65 MCP Filesystem\uff0c\u5c0f\u800c\u7b80\uff0c\u6b22\u8fce\u62cd\u7816", 
      "id": "https://www.v2ex.com/t/1171056", 
      "date_published": "2025-11-06T11:48:53+00:00", 
      "content_html": "<p>\u4e3a\u4ec0\u4e48\u8fd8\u8981\u505a\u4e00\u4e2a MCP Filesystem \u7684 Python \u5b9e\u73b0\uff1f</p>\n<p>\u76ee\u524d MCP \u751f\u6001\u91cc\uff0c\u5f88\u591a\u80fd\u529b\u662f Node.js \u5b9e\u73b0\u7684\uff0cPython \u8fd9\u8fb9\u8981\u4e48\u53ea\u662f\u5c01\u88c5\u4e00\u5c42\uff0c\u8981\u4e48\u504f\u540c\u6b65\u963b\u585e\u3002\u5bf9\u90a3\u79cd\u201c\u6211\u5c31\u60f3\u5199\u4e2a async \u5c0f\u5de5\u5177\u201d\u7684\u573a\u666f\uff0c\u5176\u5b9e\u4e0d\u592a\u53cb\u597d\uff1a</p>\n<ul>\n<li>\u6574\u6761\u94fe\u8def\u6700\u597d\u90fd\u662f <strong>async I/O</strong>\uff0c\u8ddf\u4e3b\u6d41\u751f\u6001\u5bf9\u9f50  </li>\n<li>\u4e0d\u60f3\u4e3a\u4e86\u8fd9\u70b9\u80fd\u529b\u989d\u5916\u5f15\u4e00\u5806\u4f9d\u8d56  </li>\n<li>\u8fd4\u56de\u7ed3\u6784\u8981\u89c4\u6574\uff0c<strong>\u4eba\u80fd\u770b\u61c2\u8fd8\u4e0d\u591f\uff0cAI \u4e5f\u5f97\u8bfb\u5f97\u8212\u670d</strong></li>\n</ul>\n<p>\u6240\u4ee5\u5728\u81ea\u5df1\u7684 MCP \u5de5\u5177\u91cc\uff0c\u57fa\u4e8e\u5b98\u65b9\u534f\u8bae\u641e\u4e86\u4e00\u4e2a\u300c\u7cbe\u7b80\u7248\u300d\u5b9e\u73b0\uff0c\u4e3b\u8981\u7279\u70b9\uff1a</p>\n<ul>\n<li><strong>\u5f02\u6b65 I/O \u5b9e\u73b0</strong>\uff1a\u63a5\u53e3\u5168\u90e8\u662f <code>async</code>\uff0c\u65b9\u4fbf\u76f4\u63a5\u63a5\u5230\u73b0\u6709\u7684 async Web \u6846\u67b6 / \u4efb\u52a1\u7cfb\u7edf  </li>\n<li><strong>\u65e0\u7b2c\u4e09\u65b9\u4f9d\u8d56\uff08\u57fa\u7840\u64cd\u4f5c\uff09</strong>\uff1a\u53ea\u7528\u6807\u51c6\u5e93\uff0c\u9002\u5408\u53d7\u9650\u73af\u5883\u6216\u8005\u7b80\u5355\u811a\u672c\u9879\u76ee  </li>\n<li><strong>\u7ed3\u6784\u4f53\u53cb\u597d</strong>\uff1a\u8fd4\u56de\u4f53\u8bbe\u8ba1\u5f97\u6bd4\u8f83\u89c4\u6574\uff0c\u65b9\u4fbf\u540e\u9762\u518d\u5c01\u88c5\u6210 dataclass / Pydantic \u7b49  </li>\n<li><strong>\u4ee3\u7801\u7b80\u5355\u6709\u6ce8\u91ca</strong>\uff1a\u903b\u8f91\u523b\u610f\u5199\u5f97\u76f4\u767d\uff0c\u6ca1\u6709\u505a\u91cd\u5ea6\u4f18\u5316\uff0c\u66f4\u9002\u5408 fork \u4e0b\u6765\u6309\u81ea\u5df1\u9700\u6c42\u9b54\u6539</li>\n</ul>\n<p>\u53ef\u4ee5\u81ea\u884c TODO / \u4e8c\u6b21\u5f00\u53d1\u7684\u65b9\u5411\u5305\u62ec\uff1a</p>\n<ul>\n<li>\u66f4\u4e25\u683c\u7684\u8bfb\u5199\u4fdd\u8bc1\u4e0e\u9519\u8bef\u5904\u7406\uff08\u91cd\u8bd5\u3001\u9650\u6d41\u3001\u5e42\u7b49\u7b49\uff09  </li>\n<li>\n\u76ee\u5f55\u8fb9\u754c\u7b56\u7565\u8c03\u6574\uff1a  <ul>\n<li>\u76ee\u524d\u901a\u8fc7 <code>DATA_DIR</code> \u505a\u76ee\u5f55\u6c99\u7bb1\uff0c\u9632\u6b62\u8d8a\u754c\u8bbf\u95ee\uff0c\u907f\u514d\u4e00\u4e0a\u6765\u5c31\u6478\u6574\u4e2a\u78c1\u76d8  </li>\n<li>\u4e5f\u53ef\u4ee5\u6839\u636e\u4e1a\u52a1\u9700\u8981\u53bb\u6389\u6216\u66ff\u6362\u4e3a\u81ea\u5b9a\u4e49\u6839\u76ee\u5f55\u3001\u751a\u81f3\u5f00\u653e\u6574\u76d8\u8bbf\u95ee</li>\n</ul>\n</li>\n</ul>\n<p>Github \u9879\u76ee\u5730\u5740\uff1a</p>\n<p><a href=\"https://github.com/harmonsir/mcp-filesystem-python\" rel=\"nofollow\">mcp-filesystem-python</a></p>\n<p>\u6280\u672f\u542b\u91cf\u4e0d\u591a\uff0c\u7eaf\u7cb9\u6709\u76f8\u540c\u9700\u6c42\u7684\u4eba\u53ef\u4ee5\u4e0d\u7528\u518d\u601d\u8003\u592a\u591a\uff0c\u76f4\u63a5\u7528\uff0c\u4e5f\u53ef\u4ee5\u5f53\u4f5c\u7ec4\u4ef6\u62fc\u63a5\u5230\u5176\u4ed6\u5730\u65b9\u3002</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/superhxl", 
        "name": "superhxl", 
        "avatar": "https://cdn.v2ex.com/avatar/9104/9ee3/437853_large.png?m=1652336459"
      }, 
      "url": "https://www.v2ex.com/t/1170555", 
      "title": "Neovim \u7684\u4ee3\u7801\u68c0\u67e5\u3001\u8865\u5168\u95ee\u9898", 
      "id": "https://www.v2ex.com/t/1170555", 
      "date_published": "2025-11-05T01:01:10+00:00", 
      "content_html": "<p>Neovim \u91c7\u7528 nvim-cmp + pyright \u8fdb\u884c Python \u4ee3\u7801\u68c0\u67e5\u3001\u8865\u5168\uff0c\u4f46\u662f\u5bf9\u4e8e\u590d\u6742\u7684\u4ee3\u7801\uff0c\u8b6c\u5982\u81ea\u5b9a\u4e49\u7c7b\uff0c\u7ee7\u627f\u81ea\u7236\u7c7b\u7684\u4e00\u4e9b\u5c5e\u6027\u3001\u65b9\u6cd5\u5c31\u68c0\u6d4b\u4e0d\u5230\uff0c\u63d0\u793a\u9519\u8bef\u3002\u5982\u56fe\uff1a\n<img alt=\"\" class=\"embedded_image\" loading=\"lazy\" referrerpolicy=\"no-referrer\" rel=\"noreferrer\" src=\"https://s2.loli.net/2025/11/05/49LhwI6R3AlFW7T.png\"/>\n\u81ea\u5b9a\u4e49\u7c7b MoneyAgent \u4e2d\u6709\u7ee7\u627f\u81ea\u7236\u7c7b CellAgent \u7684\u5bf9\u8c61 cell \uff0ccell \u5177\u6709\u5c5e\u6027 neighborhood \uff0cpyright \u5b8c\u5168\u610f\u8bc6\u4e0d\u5230\u3002\n\u8bf7\u6559\uff1a\n1 \u3001\u662f\u4e0d\u662f\u6709\u8bbe\u7f6e\u65b9\u6cd5\u53ef\u4ee5\u6539\u8fdb\u3001\u63d0\u5347\n2 \u3001\u9664\u4e86 pyright \u6216\u8005 nvim-cmp \uff0c\u662f\u4e0d\u662f\u8fd8\u6709\u66f4\u597d\u7684\u8865\u5168\u5de5\u5177\uff1f\n\u8c22\u8c22\uff0c\u8bf7\u5404\u4f4d V \u53cb\u4e0d\u541d\u8d50\u6559\u3002</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/WangXXX", 
        "name": "WangXXX", 
        "avatar": "https://cdn.v2ex.com/gravatar/02e3c352745cae3d4782affb331b0ef3?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1170526", 
      "date_modified": "2025-11-04T13:58:18+00:00", 
      "content_html": "\u5728\u56fd\u5185\u7684\u670d\u52a1\u5668\u4e0a\u7528 python \u5199\u4e86\u4e00\u4e2a\u7535\u62a5\u673a\u5668\u4eba\u7684\u811a\u672c\uff0c\u4f7f\u7528 clash \u4ee3\u7406\u6765\u4e0e\u7535\u62a5\u901a\u8baf\uff0c\u4f46\u662f\u9694\u51e0\u4e2a\u5c0f\u65f6\u5c31\u4f1a\u51fa\u73b0\u4e00\u6b21\u7f51\u7edc\u95ee\u9898\uff0c\u800c\u4e14\u4e00\u51fa\u73b0\u673a\u5668\u4eba\u5c31\u4f1a\u6302\u6389\uff0c\u5373\u4f7f\u540e\u6765\u7f51\u7edc\u6062\u590d\uff0c\u673a\u5668\u4eba\u4e5f\u54cd\u5e94\u4e0d\u4e86\u7535\u62a5\u7684\u6d88\u606f\u4e86\u3002<br />\u6211\u76ee\u524d\u7684\u89e3\u51b3\u65b9\u6cd5\u662f\u6355\u83b7\u5230\u5f02\u5e38\u5c31\u9000\u51fa\u7a0b\u5e8f\uff0c\u7136\u540e docker \u518d\u628a\u5b83\u62c9\u8d77\u6765\u3002\u3002<br /><br />\u8bf7\u95ee\u6709\u6ca1\u6709\u66f4\u597d\u7684\u89e3\u51b3\u65b9\u6cd5\uff1f<br /><br />\u4f7f\u7528\u7684 python \u5e93\u662f python-telegram-bot<br />\u7528 run_polling \u7684\u65b9\u6cd5\u542f\u52a8\u7684\u7a0b\u5e8f\u3002", 
      "date_published": "2025-11-04T13:57:03+00:00", 
      "title": "\u8bf7\u6559\u4e00\u4e2a\u5173\u4e8e\u7535\u62a5\u673a\u5668\u4eba\u7684\u95ee\u9898", 
      "id": "https://www.v2ex.com/t/1170526"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/enrolls", 
        "name": "enrolls", 
        "avatar": "https://cdn.v2ex.com/avatar/5e2c/35af/114646_large.png?m=1763529309"
      }, 
      "url": "https://www.v2ex.com/t/1169670", 
      "title": "\u5e2e\u4f60\u9009\u62e9\u6700\u5feb\u7684 pip \u955c\u50cf\uff0c\u544a\u522b\u5b89\u88c5\u6162", 
      "id": "https://www.v2ex.com/t/1169670", 
      "date_published": "2025-10-31T04:48:39+00:00", 
      "content_html": "<p>pip \u9ed8\u8ba4\u8fde\u63a5\u7684\u662f\u4f4d\u4e8e\u6d77\u5916\u7684 PyPI \u5b98\u65b9\u6e90\uff0c\u7f51\u7edc\u5ef6\u8fdf\u7684\u65f6\u5019\u4e0b\u8f7d\u901f\u5ea6\u975e\u5e38\u6162\u3002</p>\n<p>\u6240\u4ee5\uff0c\u76f4\u63a5\u5199\u4e86\u4e00\u4e2a pip fast check <a href=\"https://pypi.org/project/pip-fc/\" rel=\"nofollow\">pypi</a></p>\n<h4>\u76f4\u63a5\u5b89\u88c5</h4>\n<blockquote>\n<p><code>pip install -U pip-fc </code></p>\n</blockquote>\n<h4>\u76f4\u63a5\u8fd0\u884c</h4>\n<blockquote>\n<p><code>pip-fc</code> \u6216\u8005 <code>python -m pip-fc</code></p>\n</blockquote>\n<h4>\u4e3b\u8981\u529f\u80fd</h4>\n<ul>\n<li><strong>\u81ea\u52a8\u9009\u62e9\u6700\u5feb\u7684\u955c\u50cf\u6e90</strong>\uff1a\u6d4b\u8bd5\u591a\u4e2a\u56fd\u5185\u955c\u50cf\u6e90\uff0c\u81ea\u52a8\u9009\u51fa\u54cd\u5e94\u6700\u5feb\u7684\u6e90\u3002</li>\n<li><strong>\u652f\u6301 Python 2.7 \u548c 3.x</strong>\uff1a\u65e0\u8bba\u4f60\u4f7f\u7528\u7684\u662f\u54ea\u4e2a\u7248\u672c\u7684 Python \uff0c\u90fd\u80fd\u517c\u5bb9\u3002</li>\n<li><strong>\u7b80\u5355\u7684\u547d\u4ee4\u884c\u64cd\u4f5c</strong>\uff1a\u65e0\u9700\u624b\u52a8\u914d\u7f6e\uff0c\u53ea\u9700\u6267\u884c\u7b80\u5355\u547d\u4ee4\uff0c\u5c31\u80fd\u5207\u6362\u5230\u6700\u5feb\u7684\u955c\u50cf\u6e90\u3002</li>\n<li><strong>\u652f\u6301\u5168\u5c40\u914d\u7f6e</strong>\uff1a\u955c\u50cf\u6e90\u8bbe\u7f6e\u652f\u6301\u5168\u5c40\u914d\u7f6e\uff0c\u751a\u81f3\u53ef\u4ee5\u5728 Docker \u5bb9\u5668\u4e2d\u4f7f\u7528\u3002</li>\n</ul>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/cxhello", 
        "name": "cxhello", 
        "avatar": "https://cdn.v2ex.com/avatar/a0f8/5a7f/651579_large.png?m=1769489720"
      }, 
      "url": "https://www.v2ex.com/t/1169136", 
      "title": "hishel \u5347\u7ea7 1.0.0 \u5bfc\u81f4\u4f7f\u7528 pdm \u9879\u76ee\u6784\u5efa\u5931\u8d25", 
      "id": "https://www.v2ex.com/t/1169136", 
      "date_published": "2025-10-29T05:19:40+00:00", 
      "content_html": "<p>\u4f7f\u7528 pdm \u7684\u9879\u76ee\u4eca\u5929\u7a81\u7136\u6784\u5efa\u5931\u8d25\u4e86\uff0c\u53bb\u5b98\u65b9 issue \u641c\u4e86\u4e0b\uff0c\u53d1\u73b0\u662f\u56e0\u4e3a hishel \u5347\u7ea7 1.0.0 \u5bfc\u81f4\u4e0d\u517c\u5bb9\u3002</p>\n<p>\u6211\u7684 Dockerfile \u89e3\u51b3\u65b9\u6848\u5982\u4e0b</p>\n<p>pip install --no-cache-dir -U \"pdm==2.25.9\" \"hishel&lt;1.0.0\"</p>\n<p><a href=\"https://github.com/pdm-project/pdm/issues/3657\" rel=\"nofollow\">https://github.com/pdm-project/pdm/issues/3657</a></p>\n<p><a href=\"https://github.com/karpetrosyan/hishel\" rel=\"nofollow\">https://github.com/karpetrosyan/hishel</a></p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/rev1si0n", 
        "name": "rev1si0n", 
        "avatar": "https://cdn.v2ex.com/gravatar/e1f3c758ad2fa84197b239ab19487e32?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1167751", 
      "title": "\u5f00\u6e90\u4e00\u4e2a Python JA3 \u8bf7\u6c42\u5e93\u3002\u53ef\u4ee5\u81ea\u5b9a\u4e49 JA3 \u6307\u7eb9\u5e76\u4e14\u5185\u7f6e 1200 \u591a\u4e2a JA3 \u6307\u7eb9\u3002", 
      "id": "https://www.v2ex.com/t/1167751", 
      "date_published": "2025-10-23T01:19:08+00:00", 
      "content_html": "<p>\u722c\u866b\u4e2d\u53ef\u80fd\u9047\u5230\u4f60\u4ec0\u4e48\u53c2\u6570\u90fd\u62fc\u5bf9\u4e86\u4f46\u662f\u8fd8\u662f\u6ca1\u6cd5\u8bf7\u6c42\u7684\u60c5\u51b5\uff0c\u6ca1\u9519\uff0c\u4f60\u53ef\u80fd\u662f\u9047\u5230\u4e86 JA3 \u6307\u7eb9\uff0c\u4ece TLS \u5c42\u9762\u5c31\u5df2\u7ecf\u628a\u4f60\u8bc6\u522b\u4e86\uff0c\u6240\u4ee5\u518d\u505a\u4ec0\u4e48\u4e5f\u662f\u65e0\u7528\uff0c\u653e\u5fc3\u4ed6\u80fd\u5e2e\u4f60\u3002\u867d\u7136\u53ea\u662f\u5957\u58f3\u4f46\u662f\u80fd\u8ba9 Python \u76f4\u63a5\u8c03\u7528\u4e5f\u662f\u5f88\u597d\u4e86\uff0c\u4f60\u4e5f\u53ef\u4ee5\u57fa\u4e8e\u6b64\u65b9\u6848\u5b9e\u73b0\u66f4\u9ad8\u7ea7\u7684\u8bf7\u6c42\u5e93\u3002</p>\n<p>Github: <a href=\"https://github.com/rev1si0n/ja3\" rel=\"nofollow\">https://github.com/rev1si0n/ja3</a></p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/ASLant", 
        "name": "ASLant", 
        "avatar": "https://cdn.v2ex.com/gravatar/0588b56ec2564818e84e17505a48a6d2?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1166979", 
      "title": "Arm64 \u5b89\u88c5 PyQt5/6\uff0c\u53ea\u80fd\u9009 conda \u5417\uff1f", 
      "id": "https://www.v2ex.com/t/1166979", 
      "date_published": "2025-10-20T05:32:02+00:00", 
      "content_html": "<p>\u516c\u53f8\u4e00\u76f4\u7528 jetson orin nano \u548c NX \u8bbe\u5907\u5f00\u53d1\u6848\u4f8b\uff0c\u4f46\u662f\u9700\u8981\u4f7f\u7528 Python + Qt \uff0c\u76ee\u524d\u4e00\u76f4\u91c7\u7528 miniconda3 \u6765\u5b89\u88c5 PyQt5 \u8fd9\u4e2a\u5e93\uff0c\u65e0\u6cd5\u4f7f\u7528 pip \u76f4\u63a5\u7f16\u8bd1\u5b89\u88c5\uff0c\u6211\u8ba4\u4e3a\u6027\u80fd\u662f\u5b8c\u5168\u591f\u7684\uff0c8G \u5185\u5b58\uff0c\u4f46\u662f\u7f16\u8bd1\u5b89\u88c5 Qt \u8fd8\u662f\u5931\u8d25\u3002\u4f46\u662f miniconda \u6709\u70b9\u81c3\u80bf\uff0c\u80fd\u7528\u662f\u80fd\u7528\uff0c\u4f46\u662f\u4e0d\u597d\u7528\uff0cX86 \u8bbe\u5907\u90fd\u6539\u6210\u4e86 uv \u7edf\u4e00\u7ba1\u7406\uff0c\u56e0\u4e3a\u5f00\u53d1\u7684\u6848\u4f8b\u6d89\u53ca\u5230\u73af\u5883\u91cd\u7f6e\u7b49\uff0cuv \u548c poetry \u8fd9\u79cd\u6bd4\u8f83\u597d\uff0c\u53ef\u4ee5 100%\u8fd8\u539f\u4f9d\u8d56\u3002conda \u5bfc\u51fa\u7684 yaml \u6709\u65f6\u5019\u4e5f\u662f\u4f1a\u62bd\u98ce\uff0c\u603b\u662f\u6f0f\u4e2a\u5305\u4e4b\u7c7b\u7684\u3002\u4f46\u662f\u53c8\u79bb\u4e0d\u5f00 PyQt \u3002\u6240\u4ee5 \u8fd8\u6709\u62db\u5417\uff1f apt \u5b89\u88c5\u7684\u5168\u5c40\uff0c\u597d\u50cf\u4e0d\u592a\u517c\u5bb9\u3002</p>\n<p>\u5728 arm64 \u4e0a\u4f7f\u7528 pip \u5b89\u88c5\u7f16\u8bd1 PyQt \u5e93\uff0c100%\u5931\u8d25\uff0cpip \u4e5f\u6ca1\u6709 arm64 \u8bbe\u5907\u9884\u7f16\u8bd1\u597d\u7684 wheel.\nconda \u5728 arm64 \u8bbe\u5907\u63d0\u4f9b\u9884\u7f16\u8bd1\u597d\u7684 pyqt \u5e93\uff0cconda install \u662f\u53ef\u4ee5\u76f4\u63a5\u57fa\u4e8e\u5f53\u524d Python \u7248\u672c\u5b89\u88c5\u7684\uff0c\u4e0d\u9700\u8981\u7f16\u8bd1.</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/happytaoer", 
        "name": "happytaoer", 
        "avatar": "https://cdn.v2ex.com/gravatar/1c9e069b9bcb24dfb8a9c6172021187c?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1166906", 
      "title": "Python \u722c\u866b\u5fae\u6846\u67b6 web-craft", 
      "id": "https://www.v2ex.com/t/1166906", 
      "date_published": "2025-10-20T02:29:55+00:00", 
      "content_html": "<h3>\u80cc\u666f</h3>\n<p>\u8fd9\u4e24\u5929\u6784\u601d\u4e86\u4e00\u4e2a\u722c\u866b\u6846\u67b6\uff0c\u5bf9\u5916\u63d0\u4f9b API \u521b\u5efa\u722c\u866b\u4efb\u52a1\uff0c\u7136\u540e\u5185\u90e8\u7684\u961f\u5217\u4f1a\u8fdb\u884c\u722c\u866b\u7684\u6d88\u8d39\u3002\u53ea\u9700\u8981\u5b9e\u73b0\u6570\u636e\u7684\u89e3\u6790\u63a5\u53e3\u5c31\u80fd\u5feb\u901f\u7f16\u5199\u722c\u866b\u3002\u975e\u5e38\u9002\u5408\u9700\u8981\u5229\u7528 AI \u5feb\u901f\u751f\u6210\u722c\u866b\u4ee3\u7801\u7684\u56e2\u961f\u3002\n<img alt=\"screenshot.png\" class=\"embedded_image\" loading=\"lazy\" referrerpolicy=\"no-referrer\" rel=\"noreferrer\" src=\"https://duckfiles.oss-cn-qingdao.aliyuncs.com/eleduck/image/9ee0dabf-7a7f-4ef9-abaf-0241a96cbb20.png\"/></p>\n<p>\u8fd9\u4e2a\u6846\u67b6\u5bf9\u5916\u63d0\u4f9b\u4e86 API \u63a5\u53e3\u6765\u521b\u5efa\uff0c\u975e\u5e38\u4fbf\u5229\u3002\u76ee\u524d\u7684\u8bbe\u8ba1\u601d\u8def\u5c31\u662f\u53ea\u9700\u8981\u5b9e\u73b0\u4e00\u4e2a parse \u63a5\u53e3\uff0c\u5c31\u884c\u4e86\uff0c\u65b9\u4fbf\u540e\u7eed AI \u7684\u4ecb\u5165\u3002</p>\n<h3>\u540e\u7eed\u5f00\u53d1\u8ba1\u5212</h3>\n<ol>\n<li>\u5f00\u653e AI \u63a5\u53e3\uff0c\u901a\u8fc7 AI \u81ea\u52a8\u751f\u6210\u722c\u866b\u4ee3\u7801</li>\n<li>\u96c6\u6210\u57fa\u4e8e redis \u7684\u4efb\u52a1\u961f\u5217</li>\n<li>\u5b9e\u73b0\u5bf9\u5916\u8f93\u51fa\u7684\u63a5\u53e3\u5c42\uff0c\u4f8b\u5982\u722c\u866b\u7ed3\u679c\u8f6c\u50a8\u5230 mysql \u7b49\u3002</li>\n</ol>\n<p>\u76ee\u524d\u8fd9\u662f\u4e00\u4e2a\u975e\u5e38\u7b80\u5355\u6e05\u6670\u7684\u9879\u76ee\uff0c\u5e0c\u671b\u548c\u611f\u5174\u8da3\u7684\u670b\u53cb\u5171\u5efa\u8fd9\u4e2a\u9879\u76ee\uff0c\u63d0\u5347\u5927\u5bb6\u7684\u6280\u672f\u5f71\u54cd\u529b\uff0c\u6216\u8bb8\u5bf9\u627e\u8fdc\u7a0b\u5de5\u4f5c\u4e5f\u662f\u6709\u5e2e\u52a9\u7684\u3002</p>\n<p>\u9879\u76ee\u5730\u5740\uff1a\n<a href=\"https://github.com/happytaoer/web-craft\" rel=\"nofollow\">happytaoer/web-craft: A Python-based modular web scraping framework focused on efficient single URL crawling, supporting asynchronous processing, API services, and highly customizable spider modules.</a></p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/EndlessSummer", 
        "name": "EndlessSummer", 
        "avatar": "https://cdn.v2ex.com/gravatar/cb94134a89f5f0731791f3f5af66e709?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1166457", 
      "title": "\u8bf7\u6559\u5404\u4f4d\u4f6c\u4eec\u4e00\u4e2a\u5947\u602a\u7684\u540e\u7aef\u76f8\u5173\u7684\u6280\u672f\u95ee\u9898", 
      "id": "https://www.v2ex.com/t/1166457", 
      "date_published": "2025-10-17T07:43:45+00:00", 
      "content_html": "<p>\u6700\u8fd1\u5f00\u53d1\u4e00\u4e2a\u65b0\u9879\u76ee \u6280\u672f\u6808\u662f python \u670d\u52a1\u6253\u5305 docker \u955c\u50cf\u653e\u5230\u4e86\u4e91\u4e0a\u90e8\u7f72 \u90e8\u7f72\u4e0a\u4e4b\u540e\u6ca1\u6709\u95ee\u9898 \u4f46\u662f\u9694\u4e86\u4e00\u6bb5\u65f6\u95f4\u5c31\u4f1a\u6709\u8d85\u65f6\u672a\u54cd\u5e94\u7684\u95ee\u9898 \u53ea\u8981\u91cd\u542f\u5bb9\u5668\u5c31\u7acb\u9a6c\u80fd\u6b63\u5e38\u8bbf\u95ee\u4e86 \u8fd9\u4e2a\u670d\u52a1\u672c\u8eab\u4e5f\u6ca1\u6709\u591a\u5927\u8bbf\u95ee\u91cf \u8bf7\u95ee\u5404\u4f4d\u4e00\u4e0b\u6392\u67e5\u601d\u8def \u4ee5\u53ca\u5927\u6982\u54ea\u91cc\u4f1a\u6709\u95ee\u9898</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/codists", 
        "name": "codists", 
        "avatar": "https://cdn.v2ex.com/avatar/8bba/467a/525158_large.png?m=1716029644"
      }, 
      "url": "https://www.v2ex.com/t/1165216", 
      "title": "\u8fed\u4ee3\u5668\u7684\u5b9e\u9645\u5e94\u7528\u573a\u666f\u662f\u4ec0\u4e48\uff1f", 
      "id": "https://www.v2ex.com/t/1165216", 
      "date_published": "2025-10-14T12:18:12+00:00", 
      "content_html": "<h1>\u6982\u8ff0</h1>\n<p>\u6700\u8fd1\u5728\u68b3\u7406 iterator \uff0c\u4e0d\u5f97\u4e0d\u8bf4\uff0c \u5373\u4f7f\u81ea\u5df1\u5199\u4e86\u5f88\u591a\u5e74\u7684\u4ee3\u7801\uff0c\u6211\u4ecd\u7136\u6ca1\u6709\u5728\u5b9e\u9645\u5e94\u7528\u4e2d\u770b\u5230\u81ea\u5b9a\u4e49\u7684\u8fed\u4ee3\u5668\u3002\u5373\u4f7f\u8bfb\u4e86\u5f88\u591a\u4e66\uff0c\u4f46\u662f\u8fd9\u4e9b\u4e66\u4e2d\u7684\u793a\u4f8b\u5927\u591a\u662f\u6ee5\u7afd\u5145\u6570\uff0c\u4e0d\u5177\u5907\u5b9e\u9645\u5e94\u7528\u610f\u4e49\u3002\u6240\u4ee5\u987a\u7740\u7f51\u7ebf\u722c\u4e0a V \u7ad9\u8bf7\u6559\u5404\u4f4d\u3002</p>\n<h1>\u53ef\u8fed\u4ee3\u5bf9\u8c61 &amp; \u8fed\u4ee3\u5668\u5b9a\u4e49</h1>\n<h2>\u53ef\u8fed\u4ee3\u5bf9\u8c61</h2>\n<blockquote>\n<p>\u5982\u679c\u4e00\u4e2a\u5bf9\u8c61\u5b9a\u4e49\u4e86  <code>__iter__() </code> \u65b9\u6cd5\u6216\u5b9a\u4e49\u4e86 <code>__getitem__()</code> \u65b9\u6cd5\uff0c\u90a3\u4e48\u8fd9\u6837\u7684\u5bf9\u8c61\u79f0\u4e3a\u53ef\u8fed\u4ee3\u5bf9\u8c61(iterable)\u3002</p>\n</blockquote>\n<h2>\u8fed\u4ee3\u5668</h2>\n<blockquote>\n<p>\u5982\u679c\u4e00\u4e2a\u5bf9\u8c61\u5b9a\u4e49\u4e86  <code>__iter__() </code> \u65b9\u6cd5\u548c  <code>__next__()</code> \u65b9\u6cd5\uff0c\u90a3\u4e48\u8fd9\u6837\u7684\u5bf9\u8c61\u79f0\u4e3a\u8fed\u4ee3\u5668(iterator)\u3002</p>\n</blockquote>\n<p><strong>\u6ce8</strong>\uff1a</p>\n<p>1.\u540e\u7eed\u7684\u8ba8\u8bba\u90fd\u662f\u57fa\u4e8e\u4ee5\u4e0a\u4e24\u4e2a\u5b9a\u4e49\u3002</p>\n<p>2.\u56e0\u8fed\u4ee3\u5668\u5e38\u548c\u53ef\u8fed\u4ee3\u5bf9\u8c61\u7ed3\u5408\u4f7f\u7528\uff0c\u6545\u5f15\u5982\u53ef\u8fed\u4ee3\u5bf9\u8c61\u8fd9\u4e00\u6982\u5ff5\uff0c\u4f46\u8fed\u4ee3\u5668\u7684\u6982\u5ff5\u5148\u4e8e\u751f\u6210\u5668(generator)\uff0c\u5728\u540e\u7eed\u7684\u8ba8\u8bba\u4e2d\u8bf7\u52ff\u6d89\u53ca\u751f\u6210\u5668\u3002</p>\n<h1>\u4e00\u4e9b\u793a\u4f8b</h1>\n<h2>\u793a\u4f8b 1</h2>\n<p>python 3 \u7684 range()  \u662f\u4e00\u4e2a\u53ef\u8fed\u4ee3\u5bf9\u8c61\uff0c\u5176\u5b9e\u73b0\u4f7f\u7528\u4e86\u8fed\u4ee3\u5668\u3002\u4f7f\u7528\u8fed\u4ee3\u5668\u540e\u4e0d\u662f\u76f4\u63a5\u751f\u6210\u5217\u8868\uff0c\u8282\u7701\u4e86\u5185\u5b58\uff0c\u4f53\u73b0\u4e86\u8fed\u4ee3\u5668\u7684\u5e94\u7528\u610f\u4e49\u3002</p>\n<h2>\u793a\u4f8b 2</h2>\n<p>\u300a Learn Python Programming(4th)\u300b \u7b2c 246 \u9875\uff1a</p>\n<pre><code>class OddEven:\n    def __init__(self, data):\n        self._data = data\n        self.indexes = list(range(0, len(data), 2)) + list(range(1, len(data), 2))\n\n    def __iter__(self):\n        return self\n\n    def __next__(self):\n        if self.indexes:\n            return self._data[self.indexes.pop(0)]\n        raise StopIteration\n\n\n# Testing the OddEven class\noddeven = OddEven(\"0123456789\")\nprint(\"\".join(c for c in oddeven))  # 0246813579\n\noddeven = OddEven(\"ABCD\")  # or manually...\nit = iter(oddeven)  # this calls oddeven.__iter__ internally\nprint(next(it))  # A\nprint(next(it))  # C\nprint(next(it))  # B\nprint(next(it))  # D\n\n</code></pre>\n<p>\u8be5\u793a\u4f8b\u867d\u7136\u521b\u5efa\u4e86\u4e00\u4e2a\u8fed\u4ee3\u5668\uff0c\u4f46\u5c31\u529f\u80fd\u800c\u8a00\u5176\u5b9e\u5c31\u662f\u201c\u5c06\u5947\u6570\u4f4d\u7f6e\u7684\u5b57\u7b26\u653e\u5728\u524d\u534a\u6bb5\uff0c\u5c06\u5076\u6570\u4f4d\u7f6e\u7684\u5b57\u7b26\u653e\u5728\u540e\u534a\u6bb5\u201d\uff0c\u5b8c\u5168\u6ca1\u6709\u5fc5\u8981\u4f7f\u7528\u8fed\u4ee3\u5668\u3002\u5173\u4e8e\u8fed\u4ee3\u5668\u7684\u5b9e\u529b\uff0c\u672c\u4eba\u770b\u5230\u7684\u5927\u591a\u662f\u8fd9\u6837\u7684\u2014\u2014\u6beb\u65e0\u5b9e\u9645\u5e94\u7528\u610f\u4e49\uff0c\u4ee4\u4eba\u6df1\u6076\u75db\u7edd\uff01</p>\n<h1>\u95ee\u9898</h1>\n<h2>\u95ee\u9898 1</h2>\n<p>PEP 234 \u4e2d\u5199\u5230 iterator \u7684 virtues \u6709: </p>\n<blockquote>\n<ol>\n<li>It provides an extensible iterator interface.</li>\n<li>It allows performance enhancements to list iteration.</li>\n<li>It allows big performance enhancements to dictionary iteration.</li>\n<li>It allows one to provide an interface for just iteration without pretending to provide random access to elements.</li>\n<li>It is backward-compatible with all existing user-defined classes and extension objects that emulate sequences and mappings, even mappings that only implement a subset of {<code>__getitem__</code>, <code>keys</code>, <code>values</code>, <code>items</code>}.</li>\n<li>It makes code iterating over non-sequence collections more concise and readable.</li>\n</ol>\n</blockquote>\n<p>\u4e2d\u8bd1\u7248\uff1a</p>\n<blockquote>\n<p>\u5982\u679c\u5305\u542b\u8be5\u63d0\u6848\u7684\u6240\u6709\u90e8\u5206\uff0c\u5219\u4f1a\u4ee5\u4e00\u81f4\u4e14\u7075\u6d3b\u7684\u65b9\u5f0f\u89e3\u51b3\u8bb8\u591a\u95ee\u9898\u3002\u5176\u4e3b\u8981\u4f18\u70b9\u5305\u62ec\u4ee5\u4e0b\u56db\u70b9\u2014\u2014\u4e0d\uff0c\u4e94\u70b9\u2014\u2014\u4e0d\uff0c\u516d\u70b9</p>\n<ol>\n<li>\u5b83\u63d0\u4f9b\u4e86\u4e00\u4e2a\u53ef\u6269\u5c55\u7684\u8fed\u4ee3\u5668\u63a5\u53e3\u3002</li>\n<li>\u5b83\u5141\u8bb8\u5bf9\u5217\u8868\u8fed\u4ee3\u8fdb\u884c\u6027\u80fd\u4f18\u5316\u3002</li>\n<li>\u5b83\u5141\u8bb8\u5bf9\u5b57\u5178\u8fed\u4ee3\u8fdb\u884c\u5927\u5e45\u5ea6\u6027\u80fd\u63d0\u5347\u3002</li>\n<li>\u5b83\u5141\u8bb8\u4e3a\u4ec5\u8fed\u4ee3\u63d0\u4f9b\u63a5\u53e3\uff0c\u800c\u65e0\u9700\u5047\u88c5\u63d0\u4f9b\u5bf9\u5143\u7d20\u7684\u968f\u673a\u8bbf\u95ee\u3002</li>\n<li>\u5b83\u4e0e\u6240\u6709\u73b0\u6709\u7684\u7528\u6237\u5b9a\u4e49\u7c7b\u548c\u6a21\u62df\u5e8f\u5217\u548c\u6620\u5c04\u7684\u6269\u5c55\u5bf9\u8c61\u5411\u540e\u517c\u5bb9\uff0c\u5373\u4f7f\u662f\u4ec5\u5b9e\u73b0\u4e86 {<code>__getitem__</code>, <code>keys</code>, <code>values</code>, <code>items</code>} \u5b50\u96c6\u7684\u6620\u5c04\u3002</li>\n<li>\u5b83\u4f7f\u904d\u5386\u975e\u5e8f\u5217\u96c6\u5408\u7684\u4ee3\u7801\u66f4\u52a0\u7b80\u6d01\u6613\u8bfb\u3002</li>\n</ol>\n</blockquote>\n<p>\u4e0a\u9762\u6240\u5217\u51fa\u7684\u4f18\u70b9\u8f83<strong>\u62bd\u8c61</strong>\uff0c\u5404\u4f4d\u80fd\u5426\u63d0\u4f9b\u4e00\u4e9b<strong>\u5177\u4f53</strong>\u7684\u4f8b\u5b50\uff1f</p>\n<h2>\u95ee\u9898 2</h2>\n<p>\u5404\u4f4d\u5728\u5b9e\u9645\u5e94\u7528\u4e2d\u662f\u5426\u81ea\u5df1\u5b9e\u73b0\u8fc7\u8fed\u4ee3\u5668\uff1f\u5982\u679c\u6709\u9ebb\u70e6\u63d0\u4f9b\u4e00\u4e9b\u4f8b\u5b50\u3002</p>\n<h1>\u53c2\u8003\u8d44\u6599</h1>\n<p>[1] Python Document Glossary \uff0citerator\uff1a <a href=\"https://docs.python.org/3/glossary.html#term-iterator\" rel=\"nofollow\">https://docs.python.org/3/glossary.html#term-iterator</a></p>\n<p>[2] PEP 234 \u2013 Iterators\uff1a <a href=\"https://peps.python.org/pep-0234\" rel=\"nofollow\">https://peps.python.org/pep-0234</a></p>\n<p>[3] PEP 234 \u2013 \u8fed\u4ee3\u5668: <a href=\"https://peps.pythonlang.cn/pep-0234/\" rel=\"nofollow\">https://peps.pythonlang.cn/pep-0234/</a></p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/ljfdeguge", 
        "name": "ljfdeguge", 
        "avatar": "https://cdn.v2ex.com/gravatar/80296a9c1147e67bfe913ccf1877c4af?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1164684", 
      "title": "\u5c0f\u767d\u6c42\u95ee\uff0c\u521a\u63a5\u89e6\u7f16\u7a0b\u9886\u57df\u6709\u4ec0\u4e48\u901f\u6210\u7684\u65b9\u5f0f\u5b66\u4e60\u5417\uff0c\u5b66\u57fa\u7840\u9636\u6bb5\u5e76\u4e0d\u592a\u60f3\u53bb\u7cfb\u7edf\u5b66\u4e60.", 
      "id": "https://www.v2ex.com/t/1164684", 
      "date_published": "2025-10-12T12:58:43+00:00", 
      "content_html": "<p>\u5982\u679c\u627e\u4eba\u4e00\u5bf9\u4e00\u6765\u5b66\u4e60\u662f\u4e0d\u662f\u66f4\u597d</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/cnbatch", 
        "name": "cnbatch", 
        "avatar": "https://cdn.v2ex.com/gravatar/7eb06cdf719fb364a1dfbbaefc4f9d36?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1164315", 
      "title": "\ud835\udf0bthon ( Python 3.14)", 
      "id": "https://www.v2ex.com/t/1164315", 
      "date_published": "2025-10-10T16:14:49+00:00", 
      "content_html": "<p>\u7248\u672c\u53f7\u5341\u5206\u7279\u522b\uff0c\u521a\u597d\u5c31\u662f \ud835\udf0b</p>\n<p>\u5df2\u7ecf\u6709\u4eba\u6539\u4e86\u4ee3\u7801\uff0c\u4e3a\u8fd9\u4e2a\u7248\u672c\u6dfb\u52a0\u4e13\u95e8\u7684\u540d\u79f0\uff1a\ud835\udf0bthon</p>\n<p><a href=\"https://github.com/python/cpython/pull/125035\" rel=\"nofollow\">gh-119535: Support \ud835\udf0bthon in Python 3.14 venvs</a></p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/w568w", 
        "name": "w568w", 
        "avatar": "https://cdn.v2ex.com/gravatar/a07da489e1f63eaa83cded683786df23?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1163727", 
      "date_modified": "2025-10-08T10:46:34+00:00", 
      "content_html": "<p>\u91cd\u5927\u66f4\u65b0\uff1a</p>\n<ol>\n<li><strong>\u81ea\u7531\u7ebf\u7a0b\uff08 PEP 779 \uff09\u7279\u6027\u5df2\u7a33\u5b9a\u652f\u6301</strong>\u3002\u4e4b\u524d\u5728 Python 3.13 \u4e2d\uff0c\u8fd9\u4e00\u7279\u6027\u9700\u8981\u663e\u5f0f\u6307\u5b9a\u7f16\u8bd1\u9009\u9879\u3002\u4e0d\u8fc7\uff0c\u5168\u5c40\u89e3\u91ca\u5668\u9501\uff08 GIL \uff09\u4f9d\u7136\u662f\u53ef\u9009\u7684\uff0c\u800c\u662f\u5426\u8981\u5f7b\u5e95\u79fb\u9664 GIL \u4ecd\u5728\u8ba8\u8bba\u4e2d\uff08 PEP 703 \uff09\uff1b</li>\n<li><strong>\u591a\u89e3\u91ca\u5668\uff08 PEP 734 \uff09\u7279\u6027\u5df2\u652f\u6301</strong>\u3002\u73b0\u5728\uff0c\u53ef\u4ee5\u5728\u540c\u4e00\u4e2a\u8fdb\u7a0b\u4e2d\u8fd0\u884c\u591a\u4e2a\u89e3\u91ca\u5668\uff0c\u6bcf\u4e2a\u89e3\u91ca\u5668\u90fd\u6709\u72ec\u7acb\u7684 GIL \u3002\u4f5c\u4e3a Python \u5e76\u884c\u7f16\u7a0b\u4e2d\u51cf\u5c11\u5bf9 <code>multiprocessing</code> \u4f9d\u8d56\u7684\u53c8\u4e00\u6b65\uff0c\u8fd8\u6dfb\u52a0\u4e86 <code>InterpreterPoolExecutor</code> \u6765\u5e2e\u52a9\u7ba1\u7406\u591a\u4e2a\u89e3\u91ca\u5668\u7684\u5e76\u884c\uff1b</li>\n<li>\u5b98\u65b9\u53d1\u5e03\u7684 macOS \u548c Windows \u7248\u4e8c\u8fdb\u5236\u73b0\u5df2\u5305\u542b\u00a0<em>\u5b9e\u9a8c\u6027</em>\u00a0\u7684<strong>\u5373\u65f6\u7f16\u8bd1\uff08 JIT \uff09\u5668\uff08 PEP 744 \uff09</strong>\u3002\u8be5 JIT \u7f16\u8bd1\u5668\u5c1a\u5904\u4e8e\u65e9\u671f\u5f00\u53d1\u9636\u6bb5\uff0c\u6027\u80fd\u8868\u73b0\u5b58\u5728\u6ce2\u52a8\uff1a\u542f\u7528\u540e\u6839\u636e\u5de5\u4f5c\u8d1f\u8f7d\u4e0d\u540c\uff0c\u53ef\u80fd\u4ea7\u751f 10% \u7684\u6027\u80fd\u4e0b\u964d\u81f3 20% \u7684\u6027\u80fd\u63d0\u5347\uff1b</li>\n<li><strong>\u6a21\u677f\u5b57\u7b26\u4e32\uff08 t-string \uff0cPEP 750 \uff09\u652f\u6301</strong>\u3002\u8fd9\u4e00\u529f\u80fd\u4e3b\u8981\u662f\u5bf9 f-string \u7684\u8865\u5145\uff0c\u5141\u8bb8\u4ece\u7c7b\u4f3c\u5b57\u7b26\u4e32\u5b57\u9762\u91cf\u7684\u5199\u6cd5\u76f4\u63a5\u521b\u5efa\u4e00\u4e2a\u5b57\u7b26\u4e32\u6a21\u677f\u5bf9\u8c61\uff1b</li>\n<li><strong>\u589e\u91cf\u5f0f\u5783\u573e\u56de\u6536</strong>\u3002\u5faa\u73af\u5783\u573e\u56de\u6536\u5668\u73b0\u5728\u91c7\u7528\u589e\u91cf\u5f0f\u5904\u7406\u3002\u8fd9\u610f\u5473\u7740\u5bf9\u4e8e\u8f83\u5927\u7684\u5806\u5185\u5b58\uff0c\u6700\u5927\u6682\u505c\uff08 Stop The World \uff09\u65f6\u95f4\u5c06\u51cf\u5c11\u4e00\u4e2a\u6570\u91cf\u7ea7\u6216\u66f4\u591a\uff1b</li>\n<li><strong>\u4ea4\u4e92\u5f0f Shell \u652f\u6301\u8bed\u6cd5\u9ad8\u4eae</strong>\u3002\u9664\u975e\u663e\u5f0f\u7981\u7528\uff0c\u5426\u5219\u4ece Python 3.14 \u8d77\uff0cPython \u4ea4\u4e92\u5f0f\u73af\u5883\uff08 RHEL \uff09\u5c06\u9ed8\u8ba4\u5728\u7ec8\u7aef\u4e2d\u6e32\u67d3\u4ee3\u7801\u9ad8\u4eae\u3002\u6b64\u5916\uff0cShell \u8fd8\u589e\u52a0\u4e86\u5bf9 <code>import ___</code> \u7684\u81ea\u52a8\u8865\u5168\uff1b</li>\n<li><strong>asyncio \u5185\u7701\u80fd\u529b</strong>\u3002\u53ef\u4ee5\u4f7f\u7528 <code>python\u00a0-m\u00a0asyncio\u00a0&lt;ps|pstree&gt;\u00a0&lt;PID&gt;</code> \u529f\u80fd\u6765\u68c0\u67e5\u6b63\u5728\u8fd0\u884c\u7684\u5f02\u6b65 Python \u7a0b\u5e8f\u7684 async task \u6811\u3002</li>\n</ol>\n<p>\u66f4\u591a\u53ef\u5728\u8fd9\u91cc\u770b\u5230\uff1a <a href=\"https://docs.python.org/zh-cn/3.14/whatsnew/3.14.html\" rel=\"nofollow\">https://docs.python.org/zh-cn/3.14/whatsnew/3.14.html</a></p>\n", 
      "date_published": "2025-10-08T10:44:24+00:00", 
      "title": "Python 3.14 \u5df2\u53d1\u5e03", 
      "id": "https://www.v2ex.com/t/1163727"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/tangkikodo", 
        "name": "tangkikodo", 
        "avatar": "https://cdn.v2ex.com/avatar/1771/1b39/291907_large.png?m=1720273866"
      }, 
      "url": "https://www.v2ex.com/t/1163179", 
      "title": "fastapi-router-viz, \u53ef\u89c6\u5316\u4f60\u7684 API \u5185\u4f9d\u8d56\u5173\u7cfb", 
      "id": "https://www.v2ex.com/t/1163179", 
      "date_published": "2025-10-02T12:17:35+00:00", 
      "content_html": "<p><a href=\"https://github.com/allmonday/fastapi-router-viz\" rel=\"nofollow\">https://github.com/allmonday/fastapi-router-viz</a></p>\n<p>\u5bf9\u4e8e\u9075\u5faa er \u6a21\u578b\u6765\u6784\u5efa\u89c6\u56fe\u6570\u636e\u7684\u9879\u76ee\uff0cfastapi-router-viz \u53ef\u4ee5\u4e3a\u4e86\u89e3 api \u8fd4\u56de\u7c7b\u578b\u95f4\u7684\u5173\u7cfb\u63d0\u4f9b\u5feb\u901f\uff0c\u76f4\u63a5\uff0c\u53ef\u4ea4\u4e92\u7684\u56fe\u6548\u679c\u3002</p>\n<p>\u53ef\u4ee5\u901a\u8fc7\u70b9\u51fb\u8282\u70b9\u9ad8\u4eae\u5168\u90e8\u4e0a\u4e0b\u6e38\u94fe\u8def\uff0c \u4e86\u89e3 pydantic class \u7684\u4e0a\u4e0b\u6e38\u4f9d\u8d56\u60c5\u51b5</p>\n<p>alt \u70b9\u51fb\u67e5\u770b\u8282\u70b9\u7684\u6e90\u4ee3\u7801\uff0c\u6216\u8005\u76f4\u63a5\u5728 vscode \u4e2d\u6253\u5f00</p>\n<p>\u6839\u636e class + field name \u6765\u7cbe\u51c6\u5b9a\u4f4d\u6570\u636e\u88ab\u54ea\u4e9b\u9875\u9762/ \u63a5\u53e3\u4f7f\u7528</p>\n"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/emisora", 
        "name": "emisora", 
        "avatar": "https://cdn.v2ex.com/gravatar/ca281e0365d35422bad0c541497b0783?s=73&d=retro"
      }, 
      "url": "https://www.v2ex.com/t/1162621", 
      "date_modified": "2025-09-29T05:16:35+00:00", 
      "content_html": "<p>\u8fd9\u95ee\u9898\u90fd\u51fa\u73b0\u534a\u5e74\u4e86\u3002\u5c1d\u8bd5\u7528 AI \u89e3\u51b3\uff0c\u7ed9\u6211\u8bd5\u4e86\u534a\u5929\u8fd8\u90fd\u662f too many request. </p>\n<p>\u6211\u7684\u7f51\u7edc\u662f\u53ef\u4ee5\u7528 gemini cli \u4e4b\u7c7b\u7684\uff0c\u6309\u7406\u8bf4\u5e94\u8be5\u4e0d\u662f\u7f51\u7edc\u95ee\u9898\u554a\u3002</p>\n<p>\u7b80\u5355\u6d4b\u8bd5\u4ee3\u7801\u5982\u4e0b\uff1a</p>\n<pre><code class=\"language-python\">import yfinance as yf\nimport pandas as pd\n\n# \u8bbe\u7f6e\u6c38\u8f89\u8d85\u5e02\u7684\u80a1\u7968\u4ee3\u7801\nticker = \"601933.SS\"  # .SS \u8868\u793a\u4e0a\u6d77\u8bc1\u5238\u4ea4\u6613\u6240\n\n# \u521b\u5efa yfinance \u5bf9\u8c61\nstock = yf.Ticker(ticker)\n\n# \u83b7\u53d6\u5b9e\u65f6\u4ef7\u683c\uff08\u6700\u65b0\u6536\u76d8\u4ef7\u6216\u5f53\u524d\u4ef7\u683c\uff0c\u89c6\u5e02\u573a\u60c5\u51b5\u800c\u5b9a\uff09\ncurrent_price = stock.history(period=\"1d\")[\"Close\"].iloc[-1]\n\n# \u83b7\u53d6\u80a1\u7968\u7684\u8be6\u7ec6\u4fe1\u606f\uff08\u5305\u62ec\u516c\u53f8\u540d\u79f0\u7b49\uff09\ninfo = stock.info\n\n# \u8f93\u51fa\u7ed3\u679c\nprint(f\"\u6c38\u8f89\u8d85\u5e02 ({ticker}) \u7684\u5b9e\u65f6\u4ef7\u683c: {current_price:.2f} CNY\")\nprint(f\"\u516c\u53f8\u540d\u79f0: {info.get('shortName', '\u672a\u77e5')}\")\n</code></pre>\n<p>\u8fd0\u884c\u5c31\u76f4\u63a5\u62a5\u9519\uff1a</p>\n<pre><code class=\"language-shell\"> line 11, in &lt;module&gt;\n    current_price = stock.history(period=\"1d\")[\"Close\"].iloc[-1]\n                    ^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/anaconda3/lib/python3.12/site-packages/yfinance/utils.py\", line 103, in wrapper\n    result = func(*args, **kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/anaconda3/lib/python3.12/site-packages/yfinance/base.py\", line 91, in history\n    return self._lazy_load_price_history().history(*args, **kwargs)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/anaconda3/lib/python3.12/site-packages/yfinance/utils.py\", line 103, in wrapper\n    result = func(*args, **kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/anaconda3/lib/python3.12/site-packages/yfinance/scrapers/history.py\", line 178, in history\n    data = get_fn(\n           ^^^^^^^\n  File \"/opt/anaconda3/lib/python3.12/site-packages/yfinance/utils.py\", line 103, in wrapper\n    result = func(*args, **kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/anaconda3/lib/python3.12/site-packages/yfinance/data.py\", line 364, in get\n    return self._make_request(url, request_method = self._session.get, user_agent_headers=user_agent_headers, params=\nparams, timeout=timeout)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/anaconda3/lib/python3.12/site-packages/yfinance/utils.py\", line 103, in wrapper\n    result = func(*args, **kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/anaconda3/lib/python3.12/site-packages/yfinance/data.py\", line 424, in _make_request\n    raise YFRateLimitError()\nyfinance.exceptions.YFRateLimitError: Too Many Requests. Rate limited. Try after a while.\n</code></pre>\n<p>\u8fd9\u8981\u600e\u4e48\u89e3\u51b3\u5462\uff0c\u96be\u9053 yahoo \u7684 API \u5c01\u8fd9\u4e48\u4e25\u91cd\u554a\u3002\u6709\u6ca1\u6709\u77e5\u9053\u600e\u4e48\u89e3\u51b3\u7684\u8001\u5144\u554a\uff0c\u611f\u8c22\uff01</p>\n", 
      "date_published": "2025-09-29T05:15:57+00:00", 
      "title": "yfinance \u83b7\u53d6\u6570\u636e\u603b\u662f too many request \u65e0\u6cd5\u83b7\u53d6\u4ef7\u683c\u554a", 
      "id": "https://www.v2ex.com/t/1162621"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/deng1xin2020", 
        "name": "deng1xin2020", 
        "avatar": "https://cdn.v2ex.com/avatar/f8b7/d5c0/475324_large.png?m=1733819907"
      }, 
      "url": "https://www.v2ex.com/t/1159945", 
      "title": "\u5c0f\u767d\u6c42\u95ee\uff0c\u5e94\u8058 AI \u7b97\u6cd5\u5de5\u7a0b\u5e08\u8fd9\u7c7b\u5c97\u4f4d\u5e94\u8be5\u6709\u4ec0\u4e48\u57fa\u7840\u77e5\u8bc6", 
      "id": "https://www.v2ex.com/t/1159945", 
      "date_published": "2025-09-17T06:54:59+00:00", 
      "content_html": "\u79cb\u62db\u5728\u5373\uff0c\u672b\u6d41 211 \u7855\u63a7\u5236\u7c7b\u4e13\u4e1a\u505a\u7684\u53c8\u504f\u751f\u533b\u65b9\u5411\u7684\uff0c\u5e73\u5e38\u5c31\u662f\u6d89\u53ca\u7684\u6280\u672f\u9762\u4e0d\u591a\uff0c\u60f3\u4e86\u89e3\u4e00\u4e0b\u5e94\u8058\u7b97\u6cd5\u7c7b\u7684\u90fd\u9700\u8981\u638c\u63e1\u54ea\u4e9b\u57fa\u7840\u77e5\u8bc6\uff0c\u5927\u4f6c\u4eec\u522b\u9a82\uff0c\u60f3\u5b66\u4e60\u4e00\u4e0b\u4e0d\u884c\u6625\u62db\u7528\u5f97\u4e0a\u4e5f ok \uff0c\u5b9e\u5728\u662f\u4e0d\u60f3\u5728\u7701\u5185\u7684\u51e0\u4e2a\u79c1\u4f01\u7b49\u7740\u5de5\u8d44\u5012\u6302"
    }, 
    {
      "author": {
        "url": "https://www.v2ex.com/member/VforVendetta", 
        "name": "VforVendetta", 
        "avatar": "https://cdn.v2ex.com/avatar/5e1d/d362/451056_large.png?m=1718169471"
      }, 
      "url": "https://www.v2ex.com/t/1159424", 
      "date_modified": "2025-09-15T11:09:02+00:00", 
      "content_html": "<p>\u53d1\u73b0 Celery 4.2.3 \u4e0d\u652f\u6301\u7c7b\u65b9\u6cd5</p>\n<p>\u4f8b\u5b50\uff1a</p>\n<pre><code>class Demo:\n \u00a0@app.task\n \u00a0@classmethod\n \u00a0def test(cls):\n \u00a0 \u00a0 \u00a0pass\n      \n\nschedule \u6ce8\u518c\u4efb\u52a1\uff1a\nDemo.test</code></pre>\n", 
      "date_published": "2025-09-15T11:08:13+00:00", 
      "title": "Celery \u4e0d\u652f\u6301\u7c7b\u65b9\u6cd5\u5417\uff1f\u9759\u6001\u65b9\u6cd5\u5462", 
      "id": "https://www.v2ex.com/t/1159424"
    }
  ]
}