[{"data":1,"prerenderedAt":19866},["ShallowReactive",2],{"site-settings":3,"writing-\u002Fwriting\u002F2026-09-30-week3-exceptions-testing-debugging":108,"writing-navigation":845},{"site":4,"home":19,"writing":40,"services":48,"about":82,"series":98},{"id":5,"title":6,"description":7,"extension":8,"footer":9,"location":13,"meta":14,"name":6,"stem":15,"theme":16,"__hash__":18},"site\u002Fsettings\u002Fsite.yml","QQQuilibra","这是我自己部署的小主页，用于记录想法并提供一些服务的入口。","yml",{"icp":10,"policeRecord":11,"credit":12},"皖ICP备2026023522号-1","皖公网安备34012102000966号","DESIGNED & BUILT BY GPT-5.6 IN CODEX","China · UTC+8",{},"settings\u002Fsite",{"defaultAccent":17},"#000000","Q4nF96VvpN8OTNvR0hCTdam97jYsBDDLpMHyQ8Z2r2g",{"id":20,"dock":21,"extension":8,"headline":25,"introduction":27,"kicker":28,"latestWriting":29,"meta":35,"modules":36,"stem":37,"systemsLabel":38,"__hash__":39},"homeSettings\u002Fsettings\u002Fhome.yml",{"writing":22,"services":23,"about":24},"文章","服务","关于",{"prefix":26,"emphasis":6},"你好啊，这里是","这是我的个人主页，用于记录我的想法并提供服务的访问入口~","个人主页 \u002F 持续施工中",{"enabled":30,"title":31,"linkLabel":32,"emptyLabel":33,"limit":34},true,"最近写下","全部文章","暂无文章",3,{},{"services":30,"rgbAssembly":30},"settings\u002Fhome","SYSTEMS","7q25wScDZRyDzU0wFsUzx96oYs2izNTkn2XnbmORcGw",{"id":41,"emptyLabel":42,"extension":8,"intro":43,"meta":44,"seoDescription":45,"stem":46,"__hash__":47},"writingSettings\u002Fsettings\u002Fwriting.yml","没有找到匹配的文章。","随笔、学习、交易，以及一些观察。",{},"随笔、学习、交易与观察。","settings\u002Fwriting","YEMHVxKRvAlcMP_kI5F46TdJR19p9ErI69cG5BbP_j8",{"id":49,"accessNotes":50,"accessTitle":52,"extension":8,"intro":53,"items":54,"meta":78,"seoDescription":79,"stem":80,"__hash__":81},"servicesSettings\u002Fsettings\u002Fservices.yml",[51],"安全第一 大哥不要攻击我的小网站啊","访问说明","统一入口，分别运行。尚未开放的服务会在域名和登录保护完成后上线。",[55,61,67,73],{"name":56,"description":57,"href":58,"state":59,"icon":60,"showOnHome":30},"Code","浏览器里的开发环境","https:\u002F\u002Fcode.quilibra.cn","online","code",{"name":62,"description":63,"href":64,"state":65,"icon":66,"showOnHome":30},"Photos","照片备份和浏览","https:\u002F\u002Fphotos.example.com","soon","image",{"name":68,"description":69,"href":70,"state":71,"icon":72,"showOnHome":30},"NAS","文件与存储管理","https:\u002F\u002Fnas.quilibra.cn","private","hard-drive",{"name":74,"description":75,"href":76,"state":59,"icon":77,"showOnHome":30},"Navidrome","音乐流媒体服务","https:\u002F\u002Fmusic.quilibra.cn","navidrome",{},"开发环境、相册、NAS 和音乐服务。","settings\u002Fservices","E8I3lTn5Ok014_P9ojINkRV5pkHrGn2k6-k0fhvcJdE",{"id":83,"contactCopy":84,"contactLabel":85,"currentFocus":86,"extension":8,"focusLabel":87,"introduction":88,"meta":89,"seoDescription":90,"stem":91,"topicsLabel":92,"writingTopics":93,"__hash__":97},"aboutSettings\u002Fsettings\u002Fabout.yml","不是，你联系我干嘛？","联系","正在上班当牛马。","正在做的事","这是我的个人主页，用于记录我的想法并提供服务的访问入口",{},"一些基本信息，以及这个网站目前用来做什么。","settings\u002Fabout","关注的主题",[94,95,96],"炒股亏钱 跌跌不休","锻炼身体（计划中）","刷抖音 刷b站","9HBhRc3Z7UaHxUAq1eKfdguDj_BnZPNMy0SmhS9PgQM",{"id":99,"extension":8,"items":100,"meta":105,"stem":106,"__hash__":107},"seriesSettings\u002Fsettings\u002Fseries.yml",[101],{"slug":102,"title":103,"description":104},"data-ai-one-year","从 Python 到 Data\u002FAI 一年计划","12 个阶段、52 周，每周的计划与成果分开成文。",{},"settings\u002Fseries","RBIhmORVxBPd7A55Ysw93qbqR37KaDYoEhB1wWOTGl8",{"id":109,"title":110,"body":111,"category":824,"date":825,"description":826,"extension":827,"hidden":828,"kind":829,"meta":830,"navigation":30,"path":831,"publishedAt":832,"seo":833,"series":102,"slug":834,"stem":835,"tags":836,"unit":843,"updated":834,"__hash__":844},"writing\u002Fwriting\u002F2026-09-30-week3-exceptions-testing-debugging.md","Week 3 学习计划｜异常、测试与 Debugging",{"type":112,"value":113,"toc":793},"minimark",[114,129,134,139,146,150,155,159,164,168,171,174,228,232,235,260,263,319,322,326,329,367,371,374,420,423,427,432,435,440,444,449,453,457,461,472,476,479,482,491,494,509,512,517,521,527,531,536,540,542,546,614,618,628,632,669,672,709,712,715,736,739,742,789],[115,116,117,118,123,124,128],"p",{},"本周计划对应",[119,120,122],"a",{"href":121},"\u002Fwriting\u002F2026-08-31-one-year-data-ai-plan","一年学习计划总纲"," Phase I 的 ",[125,126,127],"strong",{},"Week 3 · 异常、测试与 Debugging","，按总纲 Part II「每周固定执行模板」编排。本篇只是规划，不含执行结果；实际产出完成后另写 Week 3 学习成果。",[130,131,133],"h2",{"id":132},"本周目标","🎯 本周目标",[135,136,138],"h3",{"id":137},"l1","L1",[140,141,142],"ul",{},[143,144,145],"li",{},"能读懂 traceback，讲清 Exception \u002F traceback \u002F raise \u002F try-except-finally 的语义。",[135,147,149],{"id":148},"l2","L2",[140,151,152],{},[143,153,154],{},"不看参考独立编写 boundary test、invalid input test、exception test，并接上 fixture 与 mock。",[135,156,158],{"id":157},"l3","L3",[140,160,161],{},[143,162,163],{},"（总纲本周等级为 L2，不设 L3）",[130,165,167],{"id":166},"core-knowledge","📚 Core Knowledge",[115,169,170],{},"总纲 Week 3 学习内容：",[115,172,173],{},"Exception \u002F traceback \u002F raise \u002F try-except-finally \u002F unit test \u002F fixture \u002F mock \u002F regression。",[140,175,176,200,210,216,222],{},[143,177,178,181,182,185,186,185,189,185,192,195,196,199],{},[125,179,180],{},"异常","：",[60,183,184],{},"try"," \u002F ",[60,187,188],{},"except",[60,190,191],{},"else",[60,193,194],{},"finally","、",[60,197,198],{},"raise","、异常层级、traceback 的阅读方式",[143,201,202,205,206,209],{},[125,203,204],{},"单元测试","：pytest 基本用法、",[60,207,208],{},"assert","、测试函数命名与发现规则",[143,211,212,215],{},[125,213,214],{},"fixture","：准备测试环境（临时文件、临时数据库、构造好的对象）",[143,217,218,221],{},[125,219,220],{},"mock","：替换不想真跑的东西（网络请求、时间、随机数、文件 IO）",[143,223,224,227],{},[125,225,226],{},"regression","：每修好一个 bug 就补一条测试，再跑全量确认没有破坏别处",[130,229,231],{"id":230},"️-human-core","✍️ Human Core",[115,233,234],{},"总纲 Week 3 要求独立写（不看参考）：",[140,236,239,248,254],{"className":237},[238],"contains-task-list",[143,240,243,247],{"className":241},[242],"task-list-item",[244,245],"input",{"disabled":30,"type":246},"checkbox"," boundary test（边界值）",[143,249,251,253],{"className":250},[242],[244,252],{"disabled":30,"type":246}," invalid input test（非法输入）",[143,255,257,259],{"className":256},[242],[244,258],{"disabled":30,"type":246}," exception test（异常路径）",[115,261,262],{},"三类测试的要求：",[264,265,266,282],"table",{},[267,268,269],"thead",{},[270,271,272,276,279],"tr",{},[273,274,275],"th",{},"类型",[273,277,278],{},"说明",[273,280,281],{},"例子",[283,284,285,297,308],"tbody",{},[270,286,287,291,294],{},[288,289,290],"td",{},"boundary test",[288,292,293],{},"边界值",[288,295,296],{},"0、-1、len、len+1、空列表、单元素",[270,298,299,302,305],{},[288,300,301],{},"invalid input test",[288,303,304],{},"非法输入",[288,306,307],{},"None、错误类型、空字符串、超长输入",[270,309,310,313,316],{},[288,311,312],{},"exception test",[288,314,315],{},"异常路径",[288,317,318],{},"该抛的异常抛了吗？抛的类型对吗？消息对吗？",[115,320,321],{},"要求：不看参考，自己写。",[130,323,325],{"id":324},"ai-build","🤖 AI Build",[115,327,328],{},"总纲 Week 3 分工：AI 负责扩充测试、review 测试、人为制造 bug；你负责定位、解释、修复。",[264,330,331,341],{},[267,332,333],{},[270,334,335,338],{},[273,336,337],{},"AI 做",[273,339,340],{},"你做",[283,342,343,351,359],{},[270,344,345,348],{},[288,346,347],{},"扩充测试用例",[288,349,350],{},"判断 AI 的测试有没有漏、有没有错",[270,352,353,356],{},[288,354,355],{},"review 你的测试",[288,357,358],{},"接受或反驳 AI 的意见",[270,360,361,364],{},[288,362,363],{},"人为制造 bug",[288,365,366],{},"定位、解释、修复",[130,368,370],{"id":369},"experiment","🧪 Experiment",[115,372,373],{},"总纲 Week 3 要求：主动制造四类 bug。",[264,375,376,386],{},[267,377,378],{},[270,379,380,383],{},[273,381,382],{},"Bug 类型",[273,384,385],{},"典型场景",[283,387,388,396,404,412],{},[270,389,390,393],{},[288,391,392],{},"off-by-one",[288,394,395],{},"循环多跑\u002F少跑一次，索引差一",[270,397,398,401],{},[288,399,400],{},"None bug",[288,402,403],{},"None 没判空就调用方法",[270,405,406,409],{},[288,407,408],{},"type bug",[288,410,411],{},"int 当 str 用，list 当 dict 用",[270,413,414,417],{},[288,415,416],{},"state bug",[288,418,419],{},"共享可变状态、修改后没同步",[115,421,422],{},"每个修好后，补一个回归测试。",[135,424,426],{"id":425},"hypothesis","Hypothesis",[140,428,429],{},[143,430,431],{},"主动制造并修复典型 bug，能训练从 traceback 到根因的定位能力，也能暴露测试覆盖的盲区。",[135,433,434],{"id":369},"Experiment",[140,436,437],{},[143,438,439],{},"先写测试，再让 AI 或自己引入四类 bug，观察测试是否捕获；对未捕获的补测试，对捕获的走 Debug 四步法。",[135,441,443],{"id":442},"result","Result",[140,445,446],{},[143,447,448],{},"待执行后填入。",[135,450,452],{"id":451},"conclusion","Conclusion",[140,454,455],{},[143,456,448],{},[130,458,460],{"id":459},"数智工程师考试映射","📝 数智工程师考试映射",[140,462,463,466,469],{},[143,464,465],{},"异常处理与错误传播",[143,467,468],{},"单元测试、测试隔离与回归测试",[143,470,471],{},"调试方法与根因定位",[130,473,475],{"id":474},"acceptance","✅ Acceptance",[115,477,478],{},"总纲 Week 3 验收：看到 traceback 后能够完成「定位 → 解释 → 提出原因 → 修复」。",[135,480,138],{"id":481},"l1-1",[140,483,485],{"className":484},[238],[143,486,488,490],{"className":487},[242],[244,489],{"disabled":30,"type":246}," 能读懂 traceback，定位到出错文件与行号",[135,492,149],{"id":493},"l2-1",[140,495,497,503],{"className":496},[238],[143,498,500,502],{"className":499},[242],[244,501],{"disabled":30,"type":246}," 能独立编写 boundary \u002F invalid input \u002F exception test",[143,504,506,508],{"className":505},[242],[244,507],{"disabled":30,"type":246}," 能按要求完成 Debug 四步法",[135,510,158],{"id":511},"l3-1",[140,513,514],{},[143,515,516],{},"总纲本周不设 L3",[130,518,520],{"id":519},"本周最重要的问题","🧠 本周最重要的问题",[522,523,524],"blockquote",{},[115,525,526],{},"看到 traceback 后，能不能不靠猜，走完「定位 → 解释 → 原因 → 修复」四步？",[130,528,530],{"id":529},"尚未解决","❓ 尚未解决",[140,532,533],{},[143,534,535],{},"待执行后填入",[130,537,539],{"id":538},"final-level","📊 Final Level",[115,541,149],{},[130,543,545],{"id":544},"周内节奏34-天","周内节奏（3–4 天）",[264,547,548,561],{},[267,549,550],{},[270,551,552,555,558],{},[273,553,554],{},"天",[273,556,557],{},"内容",[273,559,560],{},"状态",[283,562,563,574,584,594,604],{},[270,564,565,568,571],{},[288,566,567],{},"Day 1",[288,569,570],{},"异常 + traceback 复习 + 读 traceback 训练",[288,572,573],{},"待开始",[270,575,576,579,582],{},[288,577,578],{},"Day 2",[288,580,581],{},"Human Core：自己写 boundary \u002F invalid \u002F exception test",[288,583,573],{},[270,585,586,589,592],{},[288,587,588],{},"Day 3",[288,590,591],{},"fixture + mock，绑定真实场景",[288,593,573],{},[270,595,596,599,602],{},[288,597,598],{},"Day 4",[288,600,601],{},"主动制造 4 类 bug，定位 → 解释 → 原因 → 修复 → 补回归测试",[288,603,573],{},[270,605,606,609,612],{},[288,607,608],{},"Day 5（可选）",[288,610,611],{},"AI 扩充测试 + review，你判断 AI 的测试质量",[288,613,573],{},[130,615,617],{"id":616},"debug-四步法","Debug 四步法",[619,620,626],"pre",{"className":621,"code":623,"language":624,"meta":625},[622],"language-text","1. 定位：哪一行？哪个文件？\n2. 解释：这行在干什么？为什么出错？\n3. 原因：根本原因是什么？\n4. 修复：改什么？怎么验证改对了？\n","text","",[60,627,623],{"__ignoreMap":625},[130,629,631],{"id":630},"fixture-与-mock","Fixture 与 Mock",[264,633,634,647],{},[267,635,636],{},[270,637,638,641,644],{},[273,639,640],{},"概念",[273,642,643],{},"目的",[273,645,646],{},"场景",[283,648,649,659],{},[270,650,651,653,656],{},[288,652,214],{},[288,654,655],{},"准备测试环境",[288,657,658],{},"临时文件、临时数据库、构造好的对象",[270,660,661,663,666],{},[288,662,220],{},[288,664,665],{},"替换不想真跑的东西",[288,667,668],{},"网络请求、时间、随机数、文件 IO",[130,670,671],{"id":671},"测试覆盖标准",[264,673,674,684],{},[267,675,676],{},[270,677,678,681],{},[273,679,680],{},"维度",[273,682,683],{},"做法",[283,685,686,693,701],{},[270,687,688,690],{},[288,689,293],{},[288,691,692],{},"0、-1、len、len+1、空、单元素",[270,694,695,698],{},[288,696,697],{},"等价类",[288,699,700],{},"合法输入取代表，非法输入按类型分组",[270,702,703,706],{},[288,704,705],{},"分支覆盖",[288,707,708],{},"每个 if\u002Felse 至少各走一次",[130,710,711],{"id":711},"回归测试",[115,713,714],{},"每修好一个 bug，补一个测试：",[619,716,720],{"className":717,"code":718,"language":719,"meta":625,"style":625},"language-python shiki shiki-themes github-light github-dark","def test_parse_int_rejects_none():\n    assert parse_int(None) is None\n","python",[60,721,722,730],{"__ignoreMap":625},[723,724,727],"span",{"class":725,"line":726},"line",1,[723,728,729],{},"def test_parse_int_rejects_none():\n",[723,731,733],{"class":725,"line":732},2,[723,734,735],{},"    assert parse_int(None) is None\n",[115,737,738],{},"跑完整测试，确认没破坏别的。",[130,740,741],{"id":741},"来源与延伸阅读",[140,743,744,752,759,768,775,782],{},[143,745,746],{},[119,747,751],{"href":748,"rel":749},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Ferrors.html",[750],"nofollow","Python Tutorial：Errors and Exceptions",[143,753,754],{},[119,755,758],{"href":756,"rel":757},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fexceptions.html#exception-hierarchy",[750],"内置异常层级",[143,760,761],{},[119,762,765,767],{"href":763,"rel":764},"https:\u002F\u002Fdocs.python.org\u002F3\u002Freference\u002Fsimple_stmts.html#the-raise-statement",[750],[60,766,198],{}," 语句",[143,769,770],{},[119,771,774],{"href":772,"rel":773},"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002F",[750],"pytest 官方文档",[143,776,777],{},[119,778,781],{"href":779,"rel":780},"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Fhow-to\u002Ffixtures.html",[750],"pytest fixtures",[143,783,784],{},[119,785,788],{"href":786,"rel":787},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Funittest.mock.html",[750],"unittest.mock",[790,791,792],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":625,"searchDepth":732,"depth":732,"links":794},[795,800,801,802,803,809,810,815,816,817,818,819,820,821,822,823],{"id":132,"depth":732,"text":133,"children":796},[797,798,799],{"id":137,"depth":34,"text":138},{"id":148,"depth":34,"text":149},{"id":157,"depth":34,"text":158},{"id":166,"depth":732,"text":167},{"id":230,"depth":732,"text":231},{"id":324,"depth":732,"text":325},{"id":369,"depth":732,"text":370,"children":804},[805,806,807,808],{"id":425,"depth":34,"text":426},{"id":369,"depth":34,"text":434},{"id":442,"depth":34,"text":443},{"id":451,"depth":34,"text":452},{"id":459,"depth":732,"text":460},{"id":474,"depth":732,"text":475,"children":811},[812,813,814],{"id":481,"depth":34,"text":138},{"id":493,"depth":34,"text":149},{"id":511,"depth":34,"text":158},{"id":519,"depth":732,"text":520},{"id":529,"depth":732,"text":530},{"id":538,"depth":732,"text":539},{"id":544,"depth":732,"text":545},{"id":616,"depth":732,"text":617},{"id":630,"depth":732,"text":631},{"id":671,"depth":732,"text":671},{"id":711,"depth":732,"text":711},{"id":741,"depth":732,"text":741},"学习","2026-09-30","Week 3 学习计划：从 Exception \u002F traceback \u002F raise \u002F try-except-finally 出发，独立编写 boundary \u002F invalid input \u002F exception test，配合 fixture 与 mock，并主动制造 off-by-one \u002F None \u002F type \u002F state 四类 bug 完成定位、解释、修复与回归测试。","md",false,"计划",{},"\u002Fwriting\u002F2026-09-30-week3-exceptions-testing-debugging","2026-09-30T10:00:00+08:00",{"title":110,"description":826},null,"writing\u002F2026-09-30-week3-exceptions-testing-debugging",[837,838,839,840,204,841,214,220,842,711],"Python","Week3","异常处理","traceback","pytest","debugging","week-3","MCpBQHtVHJImsupWUgNV8whC5_kkeDpphTcxLf_fLLI",[846,1530,1903,2431,3018,3529,6213,6814,7823,8359,10628,13011,14165,14621,14839,15100,15446,15892,19314,19488,19687],{"id":847,"title":848,"body":849,"category":824,"date":1513,"description":1514,"extension":827,"hidden":828,"kind":1515,"meta":1516,"navigation":30,"path":1517,"publishedAt":1518,"seo":1519,"series":102,"slug":1520,"stem":1521,"tags":1522,"unit":1528,"updated":834,"__hash__":1529},"writing\u002Fwriting\u002F2026-10-24-week6-graph-algorithms-and-toolkit.md","Week 6 学习成果｜图算法与 Python Engineering Toolkit",{"type":112,"value":850,"toc":1493},[851,864,866,870,881,883,900,902,923,925,969,971,982,984,1005,1007,1012,1019,1021,1029,1031,1033,1037,1046,1179,1185,1188,1211,1214,1220,1266,1276,1279,1293,1351,1354,1357,1406,1416,1420,1440,1442,1491],[115,852,853,854,123,856,859,860,863],{},"本篇与",[119,855,122],{"href":121},[125,857,858],{},"Week 6 · 图算法与数据处理工具"," 一致，按总纲「每周固定执行模板」编排。Week 6 暂无单独的计划篇，本篇记录实际成果，也是 Phase I 的收尾——阶段产物 ",[125,861,862],{},"Asset 1 · Python Engineering Toolkit","。",[130,865,133],{"id":132},[135,867,869],{"id":868},"l2-l3","L2 \u002F L3",[140,871,872,875,878],{},[143,873,874],{},"✅ 手写 BFS、DFS、连通分量",[143,876,877],{},"✅ AI-Off 能实现 BFS \u002F DFS",[143,879,880],{},"✅ 能独立修改项目模块",[130,882,167],{"id":166},[140,884,885,888,891,894,897],{},[143,886,887],{},"图的表示：邻接表",[143,889,890],{},"BFS、DFS、连通分量",[143,892,893],{},"数据处理工具：csv、json、numpy、pandas",[143,895,896],{},"工程分层：Reader → Processor → Storage → CLI",[143,898,899],{},"配置分离与日志",[130,901,231],{"id":230},[140,903,905,911,917],{"className":904},[238],[143,906,908,910],{"className":907},[242],[244,909],{"checked":30,"disabled":30,"type":246}," 手写 BFS",[143,912,914,916],{"className":913},[242],[244,915],{"checked":30,"disabled":30,"type":246}," 手写 DFS",[143,918,920,922],{"className":919},[242],[244,921],{"checked":30,"disabled":30,"type":246}," 手写连通分量",[130,924,325],{"id":324},[140,926,928,934,943,956],{"className":927},[238],[143,929,931,933],{"className":930},[242],[244,932],{"checked":30,"disabled":30,"type":246}," 实现 Reader → Processor → Storage → CLI 分层",[143,935,937,939,940],{"className":936},[242],[244,938],{"checked":30,"disabled":30,"type":246}," 中间格式统一为 ",[60,941,942],{},"list[dict]",[143,944,946,948,949,952,953],{"className":945},[242],[244,947],{"checked":30,"disabled":30,"type":246}," ",[60,950,951],{},"config.json"," + ",[60,954,955],{},"@dataclass AppConfig",[143,957,959,961,962,965,966],{"className":958},[242],[244,960],{"checked":30,"disabled":30,"type":246}," 各模块 ",[60,963,964],{},"logging.getLogger(__name__)","，入口统一 ",[60,967,968],{},"basicConfig",[130,970,460],{"id":459},[140,972,973,976,979],{},[143,974,975],{},"图的遍历（BFS \u002F DFS）与连通分量",[143,977,978],{},"数据处理工具与文件读写",[143,980,981],{},"项目分层与工程化",[130,983,475],{"id":474},[140,985,987,993,999],{"className":986},[238],[143,988,990,992],{"className":989},[242],[244,991],{"checked":30,"disabled":30,"type":246}," AI-Off 能实现 BFS \u002F DFS",[143,994,996,998],{"className":995},[242],[244,997],{"checked":30,"disabled":30,"type":246}," 能独立修改项目模块",[143,1000,1002,1004],{"className":1001},[242],[244,1003],{"checked":30,"disabled":30,"type":246}," 能说清各层职责与依赖方向",[130,1006,520],{"id":519},[522,1008,1009],{},[115,1010,1011],{},"BFS 和 DFS 只差一个容器的选择，为什么结果差这么多？",[115,1013,1014,1015,1018],{},"两者都靠邻接表 + ",[60,1016,1017],{},"visited"," 保证 O(V+E)。BFS 用队列 → 按层扩散，天然得到无权图最短路径；DFS 用栈 \u002F 递归 → 一路走到底再回溯，适合连通性和拓扑类问题。",[130,1020,530],{"id":529},[140,1022,1023,1026],{},[143,1024,1025],{},"只做了入门级 csv \u002F json \u002F numpy \u002F pandas，还没处理过大文件的流式读写。",[143,1027,1028],{},"分层是否要加 Service \u002F 校验层，还没定。",[130,1030,539],{"id":538},[115,1032,869],{},[130,1034,1036],{"id":1035},"bfs-dfs-连通分量","BFS \u002F DFS \u002F 连通分量",[115,1038,1039,1040,1042,1043,863],{},"图用邻接表存，遍历时用一个 ",[60,1041,1017],{}," 集合防止走回头路，复杂度都是 ",[125,1044,1045],{},"O(V+E)",[619,1047,1049],{"className":717,"code":1048,"language":719,"meta":625,"style":625},"from collections import deque\n\ndef bfs(graph, start):\n    visited, order = {start}, []\n    q = deque([start])\n    while q:\n        u = q.popleft()\n        order.append(u)\n        for v in graph[u]:\n            if v not in visited:\n                visited.add(v)\n                q.append(v)\n    return order\n\ndef dfs(graph, start, visited=None):\n    if visited is None:\n        visited = set()\n    visited.add(start)\n    for v in graph[start]:\n        if v not in visited:\n            dfs(graph, v, visited)\n    return visited\n",[60,1050,1051,1056,1061,1066,1072,1078,1084,1090,1096,1102,1108,1114,1120,1126,1131,1137,1143,1149,1155,1161,1167,1173],{"__ignoreMap":625},[723,1052,1053],{"class":725,"line":726},[723,1054,1055],{},"from collections import deque\n",[723,1057,1058],{"class":725,"line":732},[723,1059,1060],{"emptyLinePlaceholder":30},"\n",[723,1062,1063],{"class":725,"line":34},[723,1064,1065],{},"def bfs(graph, start):\n",[723,1067,1069],{"class":725,"line":1068},4,[723,1070,1071],{},"    visited, order = {start}, []\n",[723,1073,1075],{"class":725,"line":1074},5,[723,1076,1077],{},"    q = deque([start])\n",[723,1079,1081],{"class":725,"line":1080},6,[723,1082,1083],{},"    while q:\n",[723,1085,1087],{"class":725,"line":1086},7,[723,1088,1089],{},"        u = q.popleft()\n",[723,1091,1093],{"class":725,"line":1092},8,[723,1094,1095],{},"        order.append(u)\n",[723,1097,1099],{"class":725,"line":1098},9,[723,1100,1101],{},"        for v in graph[u]:\n",[723,1103,1105],{"class":725,"line":1104},10,[723,1106,1107],{},"            if v not in visited:\n",[723,1109,1111],{"class":725,"line":1110},11,[723,1112,1113],{},"                visited.add(v)\n",[723,1115,1117],{"class":725,"line":1116},12,[723,1118,1119],{},"                q.append(v)\n",[723,1121,1123],{"class":725,"line":1122},13,[723,1124,1125],{},"    return order\n",[723,1127,1129],{"class":725,"line":1128},14,[723,1130,1060],{"emptyLinePlaceholder":30},[723,1132,1134],{"class":725,"line":1133},15,[723,1135,1136],{},"def dfs(graph, start, visited=None):\n",[723,1138,1140],{"class":725,"line":1139},16,[723,1141,1142],{},"    if visited is None:\n",[723,1144,1146],{"class":725,"line":1145},17,[723,1147,1148],{},"        visited = set()\n",[723,1150,1152],{"class":725,"line":1151},18,[723,1153,1154],{},"    visited.add(start)\n",[723,1156,1158],{"class":725,"line":1157},19,[723,1159,1160],{},"    for v in graph[start]:\n",[723,1162,1164],{"class":725,"line":1163},20,[723,1165,1166],{},"        if v not in visited:\n",[723,1168,1170],{"class":725,"line":1169},21,[723,1171,1172],{},"            dfs(graph, v, visited)\n",[723,1174,1176],{"class":725,"line":1175},22,[723,1177,1178],{},"    return visited\n",[115,1180,1181,1184],{},[125,1182,1183],{},"连通分量","：对每个未访问点各跑一次遍历，遍历次数就是分量个数。",[130,1186,1187],{"id":1187},"数据处理工具",[140,1189,1190,1199,1205],{},[143,1191,1192,185,1195,1198],{},[60,1193,1194],{},"csv",[60,1196,1197],{},"json","：读写最常见的两种文本数据格式",[143,1200,1201,1204],{},[60,1202,1203],{},"numpy","：数组与向量化运算",[143,1206,1207,1210],{},[60,1208,1209],{},"pandas","：表结构数据（DataFrame）的读写、筛选、分组",[130,1212,1213],{"id":1213},"工程分层",[619,1215,1218],{"className":1216,"code":1217,"language":624},[622],"Reader → Processor → Storage → CLI\n",[60,1219,1217],{"__ignoreMap":625},[264,1221,1222,1232],{},[267,1223,1224],{},[270,1225,1226,1229],{},[273,1227,1228],{},"层",[273,1230,1231],{},"职责",[283,1233,1234,1242,1250,1258],{},[270,1235,1236,1239],{},[288,1237,1238],{},"Reader",[288,1240,1241],{},"只负责把原始文件读进来",[270,1243,1244,1247],{},[288,1245,1246],{},"Processor",[288,1248,1249],{},"只负责数据处理",[270,1251,1252,1255],{},[288,1253,1254],{},"Storage",[288,1256,1257],{},"只负责写出去",[270,1259,1260,1263],{},[288,1261,1262],{},"CLI",[288,1264,1265],{},"解析参数、把上面几层串起来",[115,1267,1268,1269,1275],{},"各层之间",[125,1270,1271,1272,1274],{},"统一用 ",[60,1273,942],{}," 作为中间格式","，让「从哪读、怎么处理、写到哪」互相解耦。",[130,1277,1278],{"id":1278},"配置分离",[115,1280,1281,1282,1284,1285,1288,1289,1292],{},"值和“怎么读”分开：",[60,1283,951],{}," 只放值，",[60,1286,1287],{},"config.py"," 用 ",[60,1290,1291],{},"@dataclass"," 承载：",[619,1294,1296],{"className":717,"code":1295,"language":719,"meta":625,"style":625},"import json\nfrom dataclasses import dataclass\n\n@dataclass\nclass AppConfig:\n    source: str = \"input.csv\"\n    log_level: str = \"INFO\"\n\ndef load_config(path: str = \"config.json\") -> AppConfig:\n    with open(path, encoding=\"utf-8\") as f:\n        return AppConfig(**json.load(f))\n",[60,1297,1298,1303,1308,1312,1317,1322,1327,1332,1336,1341,1346],{"__ignoreMap":625},[723,1299,1300],{"class":725,"line":726},[723,1301,1302],{},"import json\n",[723,1304,1305],{"class":725,"line":732},[723,1306,1307],{},"from dataclasses import dataclass\n",[723,1309,1310],{"class":725,"line":34},[723,1311,1060],{"emptyLinePlaceholder":30},[723,1313,1314],{"class":725,"line":1068},[723,1315,1316],{},"@dataclass\n",[723,1318,1319],{"class":725,"line":1074},[723,1320,1321],{},"class AppConfig:\n",[723,1323,1324],{"class":725,"line":1080},[723,1325,1326],{},"    source: str = \"input.csv\"\n",[723,1328,1329],{"class":725,"line":1086},[723,1330,1331],{},"    log_level: str = \"INFO\"\n",[723,1333,1334],{"class":725,"line":1092},[723,1335,1060],{"emptyLinePlaceholder":30},[723,1337,1338],{"class":725,"line":1098},[723,1339,1340],{},"def load_config(path: str = \"config.json\") -> AppConfig:\n",[723,1342,1343],{"class":725,"line":1104},[723,1344,1345],{},"    with open(path, encoding=\"utf-8\") as f:\n",[723,1347,1348],{"class":725,"line":1110},[723,1349,1350],{},"        return AppConfig(**json.load(f))\n",[130,1352,1353],{"id":1353},"日志",[115,1355,1356],{},"每个模块只声明自己的 logger，入口统一配置一次：",[619,1358,1360],{"className":717,"code":1359,"language":719,"meta":625,"style":625},"# 各模块顶部\nimport logging\nlogger = logging.getLogger(__name__)\n\n# 入口只调一次\nlogging.basicConfig(\n    level=logging.INFO,\n    format=\"%(asctime)s %(name)s %(levelname)s %(message)s\",\n)\n",[60,1361,1362,1367,1372,1377,1381,1386,1391,1396,1401],{"__ignoreMap":625},[723,1363,1364],{"class":725,"line":726},[723,1365,1366],{},"# 各模块顶部\n",[723,1368,1369],{"class":725,"line":732},[723,1370,1371],{},"import logging\n",[723,1373,1374],{"class":725,"line":34},[723,1375,1376],{},"logger = logging.getLogger(__name__)\n",[723,1378,1379],{"class":725,"line":1068},[723,1380,1060],{"emptyLinePlaceholder":30},[723,1382,1383],{"class":725,"line":1074},[723,1384,1385],{},"# 入口只调一次\n",[723,1387,1388],{"class":725,"line":1080},[723,1389,1390],{},"logging.basicConfig(\n",[723,1392,1393],{"class":725,"line":1086},[723,1394,1395],{},"    level=logging.INFO,\n",[723,1397,1398],{"class":725,"line":1092},[723,1399,1400],{},"    format=\"%(asctime)s %(name)s %(levelname)s %(message)s\",\n",[723,1402,1403],{"class":725,"line":1098},[723,1404,1405],{},")\n",[115,1407,1408,1409,1412,1413,1415],{},"铁律：底层模块只 ",[60,1410,1411],{},"getLogger","，绝不 ",[60,1414,968],{},"——谁先被 import，谁就决定全局日志格式（Week 2 重构实验 A\u002FB 已确认过）。",[130,1417,1419],{"id":1418},"阶段产物asset-1-python-engineering-toolkit-第一版","阶段产物｜Asset 1 · Python Engineering Toolkit 第一版",[115,1421,1422,1423,1426,1427,1430,1431,1433,1434,1437,1438,863],{},"具备",[125,1424,1425],{},"读文件、数据处理、写文件、CLI 参数控制","的基础工具框架：分层沿用 ",[60,1428,1429],{},"Reader → Processor → Storage → CLI","，中间格式统一 ",[60,1432,942],{},"，配置走 ",[60,1435,1436],{},"config.json + @dataclass","，日志按模块 ",[60,1439,1411],{},[130,1441,741],{"id":741},[140,1443,1444,1455,1464,1477],{},[143,1445,1446],{},[119,1447,1450,1451,1454],{"href":1448,"rel":1449},"https:\u002F\u002Fdocs.python.org\u002F3\u002Fhowto\u002Flogging.html",[750],"Python ",[60,1452,1453],{},"logging"," HOWTO",[143,1456,1457],{},[119,1458,1461],{"href":1459,"rel":1460},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fdataclasses.html",[750],[60,1462,1463],{},"dataclasses",[143,1465,1466,1471,1472],{},[119,1467,1470],{"href":1468,"rel":1469},"https:\u002F\u002Fpandas.pydata.org\u002Fdocs\u002Fuser_guide\u002F10min.html",[750],"pandas 入门"," 与 ",[119,1473,1476],{"href":1474,"rel":1475},"https:\u002F\u002Fnumpy.org\u002Fdoc\u002Fstable\u002Fuser\u002Fquickstart.html",[750],"NumPy 快速入门",[143,1478,1479,1471,1485],{},[119,1480,1483],{"href":1481,"rel":1482},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fcsv.html",[750],[60,1484,1194],{},[119,1486,1489],{"href":1487,"rel":1488},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fjson.html",[750],[60,1490,1197],{},[790,1492,792],{},{"title":625,"searchDepth":732,"depth":732,"links":1494},[1495,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512],{"id":132,"depth":732,"text":133,"children":1496},[1497],{"id":868,"depth":34,"text":869},{"id":166,"depth":732,"text":167},{"id":230,"depth":732,"text":231},{"id":324,"depth":732,"text":325},{"id":459,"depth":732,"text":460},{"id":474,"depth":732,"text":475},{"id":519,"depth":732,"text":520},{"id":529,"depth":732,"text":530},{"id":538,"depth":732,"text":539},{"id":1035,"depth":732,"text":1036},{"id":1187,"depth":732,"text":1187},{"id":1213,"depth":732,"text":1213},{"id":1278,"depth":732,"text":1278},{"id":1353,"depth":732,"text":1353},{"id":1418,"depth":732,"text":1419},{"id":741,"depth":732,"text":741},"2026-10-24","Week 6 的实际产出：用邻接表 + visited 手写 BFS \u002F DFS \u002F 连通分量（O(V+E)），接触 csv \u002F json \u002F numpy \u002F pandas，并把工具整理成 Reader → Processor → Storage → CLI 的分层，中间格式统一 list[dict]，配置走 config.json + @dataclass，日志按模块 getLogger；阶段产物 Asset 1 · Python Engineering Toolkit 第一版。","成果",{},"\u002Fwriting\u002F2026-10-24-week6-graph-algorithms-and-toolkit","2026-10-24T23:33:16+08:00",{"title":848,"description":1514},"week6-graph-algorithms-and-toolkit","writing\u002F2026-10-24-week6-graph-algorithms-and-toolkit",[837,1523,1524,1525,1526,1183,1203,1209,1213,1353,1527],"Week6","图算法","BFS","DFS","学习成果","week-6","mOSB_fnHuS5ayvm-xzYq7CSLUQEooN-9ONyOMGJgOAY",{"id":1531,"title":1532,"body":1533,"category":824,"date":1513,"description":1886,"extension":827,"hidden":828,"kind":1515,"meta":1887,"navigation":30,"path":1888,"publishedAt":1889,"seo":1890,"series":102,"slug":1891,"stem":1892,"tags":1893,"unit":1901,"updated":834,"__hash__":1902},"writing\u002Fwriting\u002F2026-10-24-week5-stack-queue-and-minheap.md","Week 5 学习成果｜Stack、Queue 与 MinHeap",{"type":112,"value":1534,"toc":1862},[1535,1543,1545,1547,1555,1557,1568,1570,1595,1597,1618,1620,1622,1627,1629,1634,1636,1641,1643,1648,1650,1661,1663,1678,1680,1685,1688,1690,1701,1703,1705,1709,1712,1732,1736,1742,1766,1770,1780,1810,1813,1837,1839,1860],[115,1536,853,1537,123,1539,1542],{},[119,1538,122],{"href":121},[125,1540,1541],{},"Week 5 · 常用数据结构"," 一致，按总纲「每周固定执行模板」编排。Week 5 暂无单独的计划篇，本篇先记录实际成果。",[130,1544,133],{"id":132},[135,1546,149],{"id":148},[140,1548,1549,1552],{},[143,1550,1551],{},"✅ 手写 Stack、Queue、简化 MinHeap",[143,1553,1554],{},"✅ 能解释为什么 Top-K 通常不必对全部数据排序",[130,1556,167],{"id":166},[140,1558,1559,1562,1565],{},[143,1560,1561],{},"Stack（LIFO）、Queue（FIFO）",[143,1563,1564],{},"Heap \u002F Priority Queue、MinHeap 的数组表示",[143,1566,1567],{},"用堆做 Top-K、频率排名、LRU Cache",[130,1569,231],{"id":230},[140,1571,1573,1579,1589],{"className":1572},[238],[143,1574,1576,1578],{"className":1575},[242],[244,1577],{"checked":30,"disabled":30,"type":246}," 手写 Stack（基于 list 尾部）",[143,1580,1582,1584,1585,1588],{"className":1581},[242],[244,1583],{"checked":30,"disabled":30,"type":246}," 手写 Queue（基于 ",[60,1586,1587],{},"collections.deque","）",[143,1590,1592,1594],{"className":1591},[242],[244,1593],{"checked":30,"disabled":30,"type":246}," 手写简化 MinHeap",[130,1596,325],{"id":324},[140,1598,1600,1606,1612],{"className":1599},[238],[143,1601,1603,1605],{"className":1602},[242],[244,1604],{"checked":30,"disabled":30,"type":246}," Top-K",[143,1607,1609,1611],{"className":1608},[242],[244,1610],{"checked":30,"disabled":30,"type":246}," 频率排名",[143,1613,1615,1617],{"className":1614},[242],[244,1616],{"checked":30,"disabled":30,"type":246}," LRU Cache",[130,1619,370],{"id":369},[135,1621,426],{"id":425},[140,1623,1624],{},[143,1625,1626],{},"只保留大小 k 的堆来选 Top-K，比先把全部数据排序再取前 k 更省。",[135,1628,434],{"id":369},[140,1630,1631],{},[143,1632,1633],{},"比较「全排序取前 k」与「维护大小 k 的堆」两种 Top-K 实现。",[135,1635,443],{"id":442},[140,1637,1638],{},[143,1639,1640],{},"全排序为 O(n log n)；堆做法只需在 n 个元素上各做一次 O(log k) 的调整，约 O(n log k)。",[135,1642,452],{"id":451},[140,1644,1645],{},[143,1646,1647],{},"k 远小于 n 时，堆做法更划算；这正是 Top-K 不必对全部数据排序的原因。",[130,1649,460],{"id":459},[140,1651,1652,1655,1658],{},[143,1653,1654],{},"栈、队列的语义与应用",[143,1656,1657],{},"堆与优先队列",[143,1659,1660],{},"Top-K 与复杂度取舍",[130,1662,475],{"id":474},[140,1664,1666,1672],{"className":1665},[238],[143,1667,1669,1671],{"className":1668},[242],[244,1670],{"checked":30,"disabled":30,"type":246}," 能解释为什么 Top-K 通常不必对全部数据排序",[143,1673,1675,1677],{"className":1674},[242],[244,1676],{"checked":30,"disabled":30,"type":246}," 能说清 MinHeap 的 push \u002F pop 为什么是 O(log n)",[130,1679,520],{"id":519},[522,1681,1682],{},[115,1683,1684],{},"为什么 Top-K 不用把全部数据排完？",[115,1686,1687],{},"因为只关心前 k 个。维护一个大小 k 的堆，每来一个元素最多做一次 O(log k) 的调整，整体约 O(n log k)；对全部数据排序则是 O(n log n)。k 越小，省得越多。",[130,1689,530],{"id":529},[140,1691,1692,1695],{},[143,1693,1694],{},"写一个完整、不依赖参考的 LRU（dict + 双向链表）还没做到。",[143,1696,1697,1700],{},[60,1698,1699],{},"heapq"," 只能做小顶堆，取 Top-K 时要取负或用元组技巧，还没整理成顺手的小抄。",[130,1702,539],{"id":538},[115,1704,149],{},[130,1706,1708],{"id":1707},"stacklifo","Stack（LIFO）",[115,1710,1711],{},"后进先出，用 list 尾部操作最自然：",[619,1713,1715],{"className":717,"code":1714,"language":719,"meta":625,"style":625},"stack = []\nstack.append(1)     # push，均摊 O(1)\ntop = stack.pop()   # pop，O(1)\n",[60,1716,1717,1722,1727],{"__ignoreMap":625},[723,1718,1719],{"class":725,"line":726},[723,1720,1721],{},"stack = []\n",[723,1723,1724],{"class":725,"line":732},[723,1725,1726],{},"stack.append(1)     # push，均摊 O(1)\n",[723,1728,1729],{"class":725,"line":34},[723,1730,1731],{},"top = stack.pop()   # pop，O(1)\n",[130,1733,1735],{"id":1734},"queuefifo","Queue（FIFO）",[115,1737,1738,1739,1741],{},"先进先出。用 list 头删是 O(n)（后面元素整体前移），改用 ",[60,1740,1587],{}," 两端都是 O(1)：",[619,1743,1745],{"className":717,"code":1744,"language":719,"meta":625,"style":625},"from collections import deque\nq = deque()\nq.append(1)         # 入队\nfirst = q.popleft() # 出队，O(1)\n",[60,1746,1747,1751,1756,1761],{"__ignoreMap":625},[723,1748,1749],{"class":725,"line":726},[723,1750,1055],{},[723,1752,1753],{"class":725,"line":732},[723,1754,1755],{},"q = deque()\n",[723,1757,1758],{"class":725,"line":34},[723,1759,1760],{},"q.append(1)         # 入队\n",[723,1762,1763],{"class":725,"line":1068},[723,1764,1765],{},"first = q.popleft() # 出队，O(1)\n",[130,1767,1769],{"id":1768},"minheap","MinHeap",[115,1771,1772,1773,1776,1777,1779],{},"用数组存完全二叉树，父子下标按 ",[60,1774,1775],{},"2i+1 \u002F 2i+2 \u002F (i-1)\u002F\u002F2"," 换算；push \u002F pop 都是 O(log n)，取最小值 O(1)。Python 用 ",[60,1778,1699],{},"（默认小顶堆）：",[619,1781,1783],{"className":717,"code":1782,"language":719,"meta":625,"style":625},"import heapq\nh = [3, 1, 5]\nheapq.heapify(h)          # O(n) 建堆\nheapq.heappush(h, 2)      # O(log n)\nsmallest = heapq.heappop(h)  # O(log n)\n",[60,1784,1785,1790,1795,1800,1805],{"__ignoreMap":625},[723,1786,1787],{"class":725,"line":726},[723,1788,1789],{},"import heapq\n",[723,1791,1792],{"class":725,"line":732},[723,1793,1794],{},"h = [3, 1, 5]\n",[723,1796,1797],{"class":725,"line":34},[723,1798,1799],{},"heapq.heapify(h)          # O(n) 建堆\n",[723,1801,1802],{"class":725,"line":1068},[723,1803,1804],{},"heapq.heappush(h, 2)      # O(log n)\n",[723,1806,1807],{"class":725,"line":1074},[723,1808,1809],{},"smallest = heapq.heappop(h)  # O(log n)\n",[130,1811,1812],{"id":1812},"应用",[140,1814,1815,1821,1827],{},[143,1816,1817,1820],{},[125,1818,1819],{},"Top-K","：维持大小 k 的堆，整体约 O(n log k)。",[143,1822,1823,1826],{},[125,1824,1825],{},"频率排名","：先统计频次，再用堆取前 N。",[143,1828,1829,1832,1833,1836],{},[125,1830,1831],{},"LRU Cache","：哈希表 + 双向链表，实现 O(1) 的访问与淘汰；",[60,1834,1835],{},"OrderedDict"," 可更省事地模拟。",[130,1838,741],{"id":741},[140,1840,1841,1851],{},[143,1842,1843],{},[119,1844,1450,1847,1850],{"href":1845,"rel":1846},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fcollections.html",[750],[60,1848,1849],{},"collections","（deque）",[143,1852,1853],{},[119,1854,1857,1859],{"href":1855,"rel":1856},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fheapq.html",[750],[60,1858,1699],{}," — 堆队列算法",[790,1861,792],{},{"title":625,"searchDepth":732,"depth":732,"links":1863},[1864,1867,1868,1869,1870,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885],{"id":132,"depth":732,"text":133,"children":1865},[1866],{"id":148,"depth":34,"text":149},{"id":166,"depth":732,"text":167},{"id":230,"depth":732,"text":231},{"id":324,"depth":732,"text":325},{"id":369,"depth":732,"text":370,"children":1871},[1872,1873,1874,1875],{"id":425,"depth":34,"text":426},{"id":369,"depth":34,"text":434},{"id":442,"depth":34,"text":443},{"id":451,"depth":34,"text":452},{"id":459,"depth":732,"text":460},{"id":474,"depth":732,"text":475},{"id":519,"depth":732,"text":520},{"id":529,"depth":732,"text":530},{"id":538,"depth":732,"text":539},{"id":1707,"depth":732,"text":1708},{"id":1734,"depth":732,"text":1735},{"id":1768,"depth":732,"text":1769},{"id":1812,"depth":732,"text":1812},{"id":741,"depth":732,"text":741},"Week 5 的实际产出：用 list 尾部实现 LIFO 的 Stack、用 collections.deque 实现 FIFO 的 Queue、用数组存完全二叉树实现 MinHeap（push \u002F pop 为 O(log n)），并把它用到 Top-K、频率排名与 LRU Cache。",{},"\u002Fwriting\u002F2026-10-24-week5-stack-queue-and-minheap","2026-10-24T23:33:14+08:00",{"title":1532,"description":1886},"week5-stack-queue-and-minheap","writing\u002F2026-10-24-week5-stack-queue-and-minheap",[837,1894,1895,1896,1897,1898,1899,1819,1900,1527],"Week5","数据结构","Stack","Queue","Heap","优先队列","LRU","week-5","xF7CWjirPS287hFCvaezgmPyHYSQVGjHx8DqQWzg9wg",{"id":1904,"title":1905,"body":1906,"category":824,"date":1513,"description":2415,"extension":827,"hidden":828,"kind":1515,"meta":2416,"navigation":30,"path":2417,"publishedAt":2418,"seo":2419,"series":102,"slug":2420,"stem":2421,"tags":2422,"unit":2429,"updated":834,"__hash__":2430},"writing\u002Fwriting\u002F2026-10-24-week4-bigo-and-basic-algorithms.md","Week 4 学习成果｜Big-O 与四类基础算法",{"type":112,"value":1907,"toc":2393},[1908,1916,1918,1920,1925,1927,1941,1943,1970,1972,1974,1979,1981,1986,1988,1993,1995,2002,2004,2012,2014,2029,2031,2036,2039,2041,2049,2051,2053,2056,2109,2116,2119,2355,2358,2363,2365,2391],[115,1909,853,1910,123,1912,1915],{},[119,1911,122],{"href":121},[125,1913,1914],{},"Week 4 · 算法与复杂度"," 一致，按总纲「每周固定执行模板」编排。Week 4 暂无单独的计划篇，本篇先记录实际成果。",[130,1917,133],{"id":132},[135,1919,158],{"id":157},[140,1921,1922],{},[143,1923,1924],{},"✅ 能判断时间复杂度、空间复杂度与最坏情况",[130,1926,167],{"id":166},[140,1928,1929,1932,1935,1938],{},[143,1930,1931],{},"Big-O 与复杂度增长阶梯，最坏 \u002F 平均 \u002F 最好情况的区别",[143,1933,1934],{},"递归与分治的直觉",[143,1936,1937],{},"线性搜索、二分查找",[143,1939,1940],{},"插入排序、归并排序",[130,1942,231],{"id":230},[140,1944,1946,1952,1958,1964],{"className":1945},[238],[143,1947,1949,1951],{"className":1948},[242],[244,1950],{"checked":30,"disabled":30,"type":246}," 手写线性搜索",[143,1953,1955,1957],{"className":1954},[242],[244,1956],{"checked":30,"disabled":30,"type":246}," 手写二分查找",[143,1959,1961,1963],{"className":1960},[242],[244,1962],{"checked":30,"disabled":30,"type":246}," 手写插入排序",[143,1965,1967,1969],{"className":1966},[242],[244,1968],{"checked":30,"disabled":30,"type":246}," 手写归并排序",[130,1971,370],{"id":369},[135,1973,426],{"id":425},[140,1975,1976],{},[143,1977,1978],{},"运行时间由「常数」和「增长阶」共同决定：小规模时常数可能反超，规模变大后 Big-O 主导。",[135,1980,434],{"id":369},[140,1982,1983],{},[143,1984,1985],{},"比较 O(n) \u002F O(n log n) \u002F O(n²) 三类算法在不同数据规模下的运行表现。",[135,1987,443],{"id":442},[140,1989,1990],{},[143,1991,1992],{},"小规模下常数小的实现更快；规模增大后，增长阶低的一方稳定胜出。",[135,1994,452],{"id":451},[140,1996,1997],{},[143,1998,1999],{},[125,2000,2001],{},"常数决定起跑线，Big-O 决定天花板。",[130,2003,460],{"id":459},[140,2005,2006,2009],{},[143,2007,2008],{},"时间 \u002F 空间复杂度分析与最坏情况",[143,2010,2011],{},"基础排序与查找算法",[130,2013,475],{"id":474},[140,2015,2017,2023],{"className":2016},[238],[143,2018,2020,2022],{"className":2019},[242],[244,2021],{"checked":30,"disabled":30,"type":246}," 能判断常见算法的时间、空间复杂度与最坏情况",[143,2024,2026,2028],{"className":2025},[242],[244,2027],{"checked":30,"disabled":30,"type":246}," 能说清二分查找为什么要求数据有序、复杂度为何是 O(log n)",[130,2030,520],{"id":519},[522,2032,2033],{},[115,2034,2035],{},"常数和 Big-O，各自在什么规模下决定胜负？",[115,2037,2038],{},"小数据量时循环常数和分支开销占主导，写法更朴素的 O(n²) 可能更快；一旦规模放大，增长阶更低的一方必然反超。所以选算法先看增长阶，再看常数。",[130,2040,530],{"id":529},[140,2042,2043,2046],{},[143,2044,2045],{},"归并排序的递归栈空间分析、快排的最坏情况还没展开。",[143,2047,2048],{},"复杂度只到「会判断」，没有系统推导过均摊分析。",[130,2050,539],{"id":538},[115,2052,158],{},[130,2054,2055],{"id":2055},"复杂度增长阶梯",[264,2057,2058,2067],{},[267,2059,2060],{},[270,2061,2062,2065],{},[273,2063,2064],{},"阶",[273,2066,385],{},[283,2068,2069,2077,2085,2093,2101],{},[270,2070,2071,2074],{},[288,2072,2073],{},"O(1)",[288,2075,2076],{},"哈希表命中、数组按下标取",[270,2078,2079,2082],{},[288,2080,2081],{},"O(log n)",[288,2083,2084],{},"二分查找、平衡树查找",[270,2086,2087,2090],{},[288,2088,2089],{},"O(n)",[288,2091,2092],{},"一次遍历",[270,2094,2095,2098],{},[288,2096,2097],{},"O(n log n)",[288,2099,2100],{},"归并 \u002F 快排、基于比较的排序下界",[270,2102,2103,2106],{},[288,2104,2105],{},"O(n²)",[288,2107,2108],{},"双层循环、朴素插入排序",[115,2110,2111,2112,2115],{},"默认用",[125,2113,2114],{},"最坏情况","分析：同一个算法，最好 \u002F 平均 \u002F 最坏常常不是同一阶。",[130,2117,2118],{"id":2118},"四种算法",[619,2120,2122],{"className":717,"code":2121,"language":719,"meta":625,"style":625},"# 线性搜索 O(n)\ndef linear_search(a, target):\n    for i, x in enumerate(a):\n        if x == target:\n            return i\n    return -1\n\n# 二分查找 O(log n)，要求已排序\ndef binary_search(a, target):\n    lo, hi = 0, len(a) - 1\n    while lo \u003C= hi:\n        mid = (lo + hi) \u002F\u002F 2\n        if a[mid] == target:\n            return mid\n        if a[mid] \u003C target:\n            lo = mid + 1\n        else:\n            hi = mid - 1\n    return -1\n\n# 插入排序 O(n²)，近乎有序时接近 O(n)\ndef insertion_sort(a):\n    for i in range(1, len(a)):\n        key, j = a[i], i - 1\n        while j >= 0 and a[j] > key:\n            a[j + 1] = a[j]\n            j -= 1\n        a[j + 1] = key\n    return a\n\n# 归并排序 O(n log n)，稳定\ndef merge_sort(a):\n    if len(a) \u003C= 1:\n        return a\n    mid = len(a) \u002F\u002F 2\n    left, right = merge_sort(a[:mid]), merge_sort(a[mid:])\n    out, i, j = [], 0, 0\n    while i \u003C len(left) and j \u003C len(right):\n        if left[i] \u003C= right[j]:\n            out.append(left[i]); i += 1\n        else:\n            out.append(right[j]); j += 1\n    return out + left[i:] + right[j:]\n",[60,2123,2124,2129,2134,2139,2144,2149,2154,2158,2163,2168,2173,2178,2183,2188,2193,2198,2203,2208,2213,2217,2221,2226,2231,2237,2243,2249,2255,2261,2267,2273,2278,2284,2290,2296,2302,2308,2314,2320,2326,2332,2338,2343,2349],{"__ignoreMap":625},[723,2125,2126],{"class":725,"line":726},[723,2127,2128],{},"# 线性搜索 O(n)\n",[723,2130,2131],{"class":725,"line":732},[723,2132,2133],{},"def linear_search(a, target):\n",[723,2135,2136],{"class":725,"line":34},[723,2137,2138],{},"    for i, x in enumerate(a):\n",[723,2140,2141],{"class":725,"line":1068},[723,2142,2143],{},"        if x == target:\n",[723,2145,2146],{"class":725,"line":1074},[723,2147,2148],{},"            return i\n",[723,2150,2151],{"class":725,"line":1080},[723,2152,2153],{},"    return -1\n",[723,2155,2156],{"class":725,"line":1086},[723,2157,1060],{"emptyLinePlaceholder":30},[723,2159,2160],{"class":725,"line":1092},[723,2161,2162],{},"# 二分查找 O(log n)，要求已排序\n",[723,2164,2165],{"class":725,"line":1098},[723,2166,2167],{},"def binary_search(a, target):\n",[723,2169,2170],{"class":725,"line":1104},[723,2171,2172],{},"    lo, hi = 0, len(a) - 1\n",[723,2174,2175],{"class":725,"line":1110},[723,2176,2177],{},"    while lo \u003C= hi:\n",[723,2179,2180],{"class":725,"line":1116},[723,2181,2182],{},"        mid = (lo + hi) \u002F\u002F 2\n",[723,2184,2185],{"class":725,"line":1122},[723,2186,2187],{},"        if a[mid] == target:\n",[723,2189,2190],{"class":725,"line":1128},[723,2191,2192],{},"            return mid\n",[723,2194,2195],{"class":725,"line":1133},[723,2196,2197],{},"        if a[mid] \u003C target:\n",[723,2199,2200],{"class":725,"line":1139},[723,2201,2202],{},"            lo = mid + 1\n",[723,2204,2205],{"class":725,"line":1145},[723,2206,2207],{},"        else:\n",[723,2209,2210],{"class":725,"line":1151},[723,2211,2212],{},"            hi = mid - 1\n",[723,2214,2215],{"class":725,"line":1157},[723,2216,2153],{},[723,2218,2219],{"class":725,"line":1163},[723,2220,1060],{"emptyLinePlaceholder":30},[723,2222,2223],{"class":725,"line":1169},[723,2224,2225],{},"# 插入排序 O(n²)，近乎有序时接近 O(n)\n",[723,2227,2228],{"class":725,"line":1175},[723,2229,2230],{},"def insertion_sort(a):\n",[723,2232,2234],{"class":725,"line":2233},23,[723,2235,2236],{},"    for i in range(1, len(a)):\n",[723,2238,2240],{"class":725,"line":2239},24,[723,2241,2242],{},"        key, j = a[i], i - 1\n",[723,2244,2246],{"class":725,"line":2245},25,[723,2247,2248],{},"        while j >= 0 and a[j] > key:\n",[723,2250,2252],{"class":725,"line":2251},26,[723,2253,2254],{},"            a[j + 1] = a[j]\n",[723,2256,2258],{"class":725,"line":2257},27,[723,2259,2260],{},"            j -= 1\n",[723,2262,2264],{"class":725,"line":2263},28,[723,2265,2266],{},"        a[j + 1] = key\n",[723,2268,2270],{"class":725,"line":2269},29,[723,2271,2272],{},"    return a\n",[723,2274,2276],{"class":725,"line":2275},30,[723,2277,1060],{"emptyLinePlaceholder":30},[723,2279,2281],{"class":725,"line":2280},31,[723,2282,2283],{},"# 归并排序 O(n log n)，稳定\n",[723,2285,2287],{"class":725,"line":2286},32,[723,2288,2289],{},"def merge_sort(a):\n",[723,2291,2293],{"class":725,"line":2292},33,[723,2294,2295],{},"    if len(a) \u003C= 1:\n",[723,2297,2299],{"class":725,"line":2298},34,[723,2300,2301],{},"        return a\n",[723,2303,2305],{"class":725,"line":2304},35,[723,2306,2307],{},"    mid = len(a) \u002F\u002F 2\n",[723,2309,2311],{"class":725,"line":2310},36,[723,2312,2313],{},"    left, right = merge_sort(a[:mid]), merge_sort(a[mid:])\n",[723,2315,2317],{"class":725,"line":2316},37,[723,2318,2319],{},"    out, i, j = [], 0, 0\n",[723,2321,2323],{"class":725,"line":2322},38,[723,2324,2325],{},"    while i \u003C len(left) and j \u003C len(right):\n",[723,2327,2329],{"class":725,"line":2328},39,[723,2330,2331],{},"        if left[i] \u003C= right[j]:\n",[723,2333,2335],{"class":725,"line":2334},40,[723,2336,2337],{},"            out.append(left[i]); i += 1\n",[723,2339,2341],{"class":725,"line":2340},41,[723,2342,2207],{},[723,2344,2346],{"class":725,"line":2345},42,[723,2347,2348],{},"            out.append(right[j]); j += 1\n",[723,2350,2352],{"class":725,"line":2351},43,[723,2353,2354],{},"    return out + left[i:] + right[j:]\n",[130,2356,2357],{"id":2357},"实验结论",[115,2359,2360,2361],{},"小数据量时，常数小、循环更简单的插入排序可能反超归并排序；数据量一放大，O(n log n) 就稳定压过 O(n²)。",[125,2362,2001],{},[130,2364,741],{"id":741},[140,2366,2367,2374,2381],{},[143,2368,2369],{},[119,2370,2373],{"href":2371,"rel":2372},"https:\u002F\u002Fwww.bigocheatsheet.com\u002F",[750],"Big-O Cheat Sheet",[143,2375,2376],{},[119,2377,2380],{"href":2378,"rel":2379},"https:\u002F\u002Fdocs.python.org\u002F3\u002Fhowto\u002Fsorting.html",[750],"Python Sorting HOWTO",[143,2382,2383],{},[119,2384,2387,2390],{"href":2385,"rel":2386},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fbisect.html",[750],[60,2388,2389],{},"bisect"," — 数组二分算法",[790,2392,792],{},{"title":625,"searchDepth":732,"depth":732,"links":2394},[2395,2398,2399,2400,2406,2407,2408,2409,2410,2411,2412,2413,2414],{"id":132,"depth":732,"text":133,"children":2396},[2397],{"id":157,"depth":34,"text":158},{"id":166,"depth":732,"text":167},{"id":230,"depth":732,"text":231},{"id":369,"depth":732,"text":370,"children":2401},[2402,2403,2404,2405],{"id":425,"depth":34,"text":426},{"id":369,"depth":34,"text":434},{"id":442,"depth":34,"text":443},{"id":451,"depth":34,"text":452},{"id":459,"depth":732,"text":460},{"id":474,"depth":732,"text":475},{"id":519,"depth":732,"text":520},{"id":529,"depth":732,"text":530},{"id":538,"depth":732,"text":539},{"id":2055,"depth":732,"text":2055},{"id":2118,"depth":732,"text":2118},{"id":2357,"depth":732,"text":2357},{"id":741,"depth":732,"text":741},"Week 4 的实际产出：掌握 O(1) \u003C O(log n) \u003C O(n) \u003C O(n log n) \u003C O(n²) 的增长阶梯，手写线性搜索、二分查找、插入排序、归并排序，并得出「常数决定起跑线，Big-O 决定天花板」的实验结论。",{},"\u002Fwriting\u002F2026-10-24-week4-bigo-and-basic-algorithms","2026-10-24T23:33:12+08:00",{"title":1905,"description":2415},"week4-bigo-and-basic-algorithms","writing\u002F2026-10-24-week4-bigo-and-basic-algorithms",[837,2423,2424,2425,2426,2427,2428,1527],"Week4","算法","Big-O","复杂度","二分查找","排序","week-4","hUVKWa4VpV-Z-At8MaIQ0y0wvs7irIYLgySPvtf8bO4",{"id":2432,"title":2433,"body":2434,"category":824,"date":825,"description":3009,"extension":827,"hidden":828,"kind":1515,"meta":3010,"navigation":30,"path":3011,"publishedAt":3012,"seo":3013,"series":102,"slug":834,"stem":3014,"tags":3015,"unit":843,"updated":834,"__hash__":3017},"writing\u002Fwriting\u002F2026-09-30-week3-learning-outcomes.md","Week 3 学习成果｜异常、traceback 与 Debug 起步",{"type":112,"value":2435,"toc":2972},[2436,2448,2450,2452,2457,2459,2464,2466,2470,2472,2501,2503,2539,2541,2550,2552,2554,2559,2561,2566,2568,2576,2578,2583,2585,2592,2594,2596,2610,2612,2627,2629,2633,2635,2639,2641,2646,2648,2651,2655,2664,2667,2670,2674,2680,2685,2704,2710,2715,2726,2729,2733,2742,2745,2751,2776,2793,2797,2835,2846,2850,2853,2878,2883,2897,2903,2914,2916,2921,2924,2940,2942,2970],[115,2437,2438,2439,2442,2443,123,2445,2447],{},"本篇对应 ",[119,2440,2441],{"href":831},"Week 3 学习计划","，与",[119,2444,122],{"href":121},[125,2446,127],{},"（等级 L2）一致，按总纲 Part II「每周固定执行模板」编排。计划篇保留任务与要求，这里放实际做出来的东西和训练记录。目前有书面成果的是 Day 1，Day 2–5 完成后再补进来。",[130,2449,133],{"id":132},[135,2451,138],{"id":137},[140,2453,2454],{},[143,2455,2456],{},"✅ 能读懂 traceback，讲清 Exception \u002F traceback \u002F raise \u002F try-except-finally 的语义",[135,2458,149],{"id":148},[140,2460,2461],{},[143,2462,2463],{},"🟡 三类测试（boundary \u002F invalid input \u002F exception）与 fixture \u002F mock 尚未产出书面成果",[135,2465,158],{"id":157},[140,2467,2468],{},[143,2469,516],{},[130,2471,167],{"id":166},[140,2473,2474,2477,2480,2488,2491,2498],{},[143,2475,2476],{},"异常是什么、异常对象与 traceback 的结构",[143,2478,2479],{},"traceback 的读法：从下往上读，区分调用帧与抛出点，过滤库帧",[143,2481,2482,2484,2485],{},[60,2483,198],{}," 主动抛异常与异常链 ",[60,2486,2487],{},"raise ... from ...",[143,2489,2490],{},"try \u002F except \u002F else \u002F finally 的执行顺序",[143,2492,2493,2494,2497],{},"LBYL 与 ",[60,2495,2496],{},".get()"," 两种修 bug 的字典写法",[143,2499,2500],{},"四类常见异常的成因：KeyError \u002F AttributeError \u002F TypeError \u002F ZeroDivisionError",[130,2502,231],{"id":230},[140,2504,2506,2512,2518,2524,2533],{"className":2505},[238],[143,2507,2509,2511],{"className":2508},[242],[244,2510],{"checked":30,"disabled":30,"type":246}," 读 4 个 traceback，能说出异常类型、消息、抛出位置",[143,2513,2515,2517],{"className":2514},[242],[244,2516],{"checked":30,"disabled":30,"type":246}," 区分“调用帧”和“抛出点”",[143,2519,2521,2523],{"className":2520},[242],[244,2522],{"checked":30,"disabled":30,"type":246}," 能解释 KeyError \u002F AttributeError \u002F TypeError \u002F ZeroDivisionError 的成因",[143,2525,2527,2529,2530,2532],{"className":2526},[242],[244,2528],{"checked":30,"disabled":30,"type":246}," 能修复 KeyError，用 ",[60,2531,2496],{}," 替代直接索引",[143,2534,2536,2538],{"className":2535},[242],[244,2537],{"disabled":30,"type":246}," boundary test \u002F invalid input test \u002F exception test（Day 2）",[130,2540,325],{"id":324},[140,2542,2544],{"className":2543},[238],[143,2545,2547,2549],{"className":2546},[242],[244,2548],{"disabled":30,"type":246}," AI 扩充测试、review 测试、人为制造 bug（Day 4–5）",[130,2551,370],{"id":369},[135,2553,426],{"id":425},[140,2555,2556],{},[143,2557,2558],{},"反复读 traceback 并归纳异常成因，能建立“从异常类型反推根因”的直觉。",[135,2560,434],{"id":369},[140,2562,2563],{},[143,2564,2565],{},"Day 1 读 4 个 traceback，逐条拆出异常类型、消息、抛出位置，并对比调用帧与抛出点。",[135,2567,443],{"id":442},[140,2569,2570],{},[143,2571,2572,2573,2575],{},"4 个 traceback 均能定位异常类型与抛出点；能解释 KeyError \u002F AttributeError \u002F TypeError \u002F ZeroDivisionError 的成因；KeyError 用 ",[60,2574,2496],{}," 修复。",[135,2577,452],{"id":451},[140,2579,2580],{},[143,2581,2582],{},"读 traceback 的关键是“从下往上 + 只看自己写的帧”；traceback 只给事实（类型 \u002F 消息 \u002F 位置），不给原因和修法，这两步得自己判断。",[130,2584,460],{"id":459},[140,2586,2587,2589],{},[143,2588,465],{},[143,2590,2591],{},"traceback 阅读与根因定位",[130,2593,475],{"id":474},[135,2595,138],{"id":481},[140,2597,2599,2604],{"className":2598},[238],[143,2600,2602,490],{"className":2601},[242],[244,2603],{"checked":30,"disabled":30,"type":246},[143,2605,2607,2609],{"className":2606},[242],[244,2608],{"checked":30,"disabled":30,"type":246}," 能区分“调用帧”和“抛出点”",[135,2611,149],{"id":493},[140,2613,2615,2621],{"className":2614},[238],[143,2616,2618,2620],{"className":2617},[242],[244,2619],{"disabled":30,"type":246}," 能独立编写 boundary \u002F invalid input \u002F exception test（Day 2）",[143,2622,2624,2626],{"className":2623},[242],[244,2625],{"disabled":30,"type":246}," 能按要求完成 Debug 四步法（Day 4）",[135,2628,158],{"id":511},[140,2630,2631],{},[143,2632,516],{},[130,2634,520],{"id":519},[522,2636,2637],{},[115,2638,526],{},[130,2640,530],{"id":529},[140,2642,2643],{},[143,2644,2645],{},"Day 2–5 的三类测试、fixture \u002F mock、四类 bug 实验尚未产出书面成果",[130,2647,539],{"id":538},[115,2649,2650],{},"L2（进行中）",[130,2652,2654],{"id":2653},"day-1-成果异常traceback-与-debug-起步","Day 1 成果｜异常、traceback 与 Debug 起步",[115,2656,2657,181,2660],{},[125,2658,2659],{},"对应计划",[119,2661,2663],{"href":2662},"\u002Fwriting\u002F2026-09-30-week3-exceptions-testing-debugging#%E5%91%A8%E5%86%85%E8%8A%82%E5%A5%8F34-%E5%A4%A9","Day 1｜异常 + traceback 复习 + 读 traceback 训练",[135,2665,2666],{"id":2666},"异常是什么",[115,2668,2669],{},"程序做不到某件事时，抛出异常对象。没人接住，程序中断并打印 traceback。",[135,2671,2673],{"id":2672},"读-traceback","读 traceback",[619,2675,2678],{"className":2676,"code":2677,"language":624},[622],"Traceback (most recent call last):\n  File \"demo.py\", line 10, in \u003Cmodule>      ← 最早发生\n    result = parse(\"abc\")\n  File \"demo.py\", line 5, in parse          ← 最近发生（抛出点）\n    return int(s)\nValueError: invalid literal for int() ...   ← 异常类型 + 消息\n",[60,2679,2677],{"__ignoreMap":625},[115,2681,2682],{},[125,2683,2684],{},"读法：",[140,2686,2687,2692,2695,2698,2701],{},[143,2688,2689],{},[125,2690,2691],{},"从下往上读",[143,2693,2694],{},"最下面：异常类型和消息",[143,2696,2697],{},"往上找：哪一行抛的（最靠近异常类型的那一帧）",[143,2699,2700],{},"再往上：谁调用了谁",[143,2702,2703],{},"找“自己写的代码”",[115,2705,2706,2709],{},[125,2707,2708],{},"traceback 里混着标准库 \u002F 第三方库的帧","：找自己项目的文件名，跳过库的帧。",[115,2711,2712],{},[125,2713,2714],{},"traceback 不告诉你的事：",[140,2716,2717,2720,2723],{},[143,2718,2719],{},"为什么这行会出错",[143,2721,2722],{},"是数据问题还是代码问题",[143,2724,2725],{},"怎么修",[115,2727,2728],{},"这三件事得自己判断。",[135,2730,2732],{"id":2731},"raise-主动抛异常","raise 主动抛异常",[619,2734,2736],{"className":717,"code":2735,"language":719,"meta":625,"style":625},"raise ValueError(\"年龄不能为负数\")\n",[60,2737,2738],{"__ignoreMap":625},[723,2739,2740],{"class":725,"line":726},[723,2741,2735],{},[115,2743,2744],{},"常见用途：参数校验、不支持输入、记录后重抛。",[135,2746,2748,2749],{"id":2747},"异常链-raise-from","异常链 ",[60,2750,2487],{},[619,2752,2754],{"className":717,"code":2753,"language":719,"meta":625,"style":625},"try:\n    int(\"abc\")\nexcept ValueError as e:\n    raise RuntimeError(\"配置解析失败\") from e\n",[60,2755,2756,2761,2766,2771],{"__ignoreMap":625},[723,2757,2758],{"class":725,"line":726},[723,2759,2760],{},"try:\n",[723,2762,2763],{"class":725,"line":732},[723,2764,2765],{},"    int(\"abc\")\n",[723,2767,2768],{"class":725,"line":34},[723,2769,2770],{},"except ValueError as e:\n",[723,2772,2773],{"class":725,"line":1068},[723,2774,2775],{},"    raise RuntimeError(\"配置解析失败\") from e\n",[140,2777,2778,2784,2790],{},[143,2779,2780,2781],{},"对外暴露 ",[60,2782,2783],{},"RuntimeError",[143,2785,2786,2789],{},[60,2787,2788],{},"ValueError"," 保留在 traceback 里当根因",[143,2791,2792],{},"用于分层转换：底层技术错误 → 上层业务错误",[135,2794,2796],{"id":2795},"try-except-finally-执行顺序","try \u002F except \u002F finally 执行顺序",[264,2798,2799,2809],{},[267,2800,2801],{},[270,2802,2803,2806],{},[273,2804,2805],{},"情况",[273,2807,2808],{},"顺序",[283,2810,2811,2819,2827],{},[270,2812,2813,2816],{},[288,2814,2815],{},"成功",[288,2817,2818],{},"try → else → finally",[270,2820,2821,2824],{},[288,2822,2823],{},"失败被捕获",[288,2825,2826],{},"try → except → finally",[270,2828,2829,2832],{},[288,2830,2831],{},"失败没捕获",[288,2833,2834],{},"try → finally，异常继续抛",[115,2836,2837,2839,2840,2843,2844,863],{},[60,2838,194],{}," 里的 ",[60,2841,2842],{},"return"," 会覆盖 try 里的 ",[60,2845,2842],{},[135,2847,2849],{"id":2848},"修-bug-的两种字典写法","修 bug 的两种字典写法",[115,2851,2852],{},"LBYL：先检查",[619,2854,2856],{"className":717,"code":2855,"language":719,"meta":625,"style":625},"def get_user(users, user_id):\n    if user_id not in users:\n        return None\n    return users[user_id]\n",[60,2857,2858,2863,2868,2873],{"__ignoreMap":625},[723,2859,2860],{"class":725,"line":726},[723,2861,2862],{},"def get_user(users, user_id):\n",[723,2864,2865],{"class":725,"line":732},[723,2866,2867],{},"    if user_id not in users:\n",[723,2869,2870],{"class":725,"line":34},[723,2871,2872],{},"        return None\n",[723,2874,2875],{"class":725,"line":1068},[723,2876,2877],{},"    return users[user_id]\n",[115,2879,2880,2882],{},[60,2881,2496],{},"：字典内置，最简洁",[619,2884,2886],{"className":717,"code":2885,"language":719,"meta":625,"style":625},"def get_user(users, user_id):\n    return users.get(user_id)\n",[60,2887,2888,2892],{"__ignoreMap":625},[723,2889,2890],{"class":725,"line":726},[723,2891,2862],{},[723,2893,2894],{"class":725,"line":732},[723,2895,2896],{},"    return users.get(user_id)\n",[115,2898,2899,2902],{},[60,2900,2901],{},".get(key, default)"," 规则：",[140,2904,2905,2908,2911],{},[143,2906,2907],{},"键存在 → 返回真实值",[143,2909,2910],{},"键不存在 → 返回 default（不写则 None）",[143,2912,2913],{},"永不抛 KeyError",[135,2915,617],{"id":616},[619,2917,2919],{"className":2918,"code":623,"language":624,"meta":625},[622],[60,2920,623],{"__ignoreMap":625},[135,2922,2923],{"id":2923},"今日训练完成",[140,2925,2926,2929,2932,2935],{},[143,2927,2928],{},"读了 4 个 traceback，能说出异常类型、消息、抛出位置",[143,2930,2931],{},"区分了“调用帧”和“抛出点”",[143,2933,2934],{},"能解释 KeyError \u002F AttributeError \u002F TypeError \u002F ZeroDivisionError 的成因",[143,2936,2937,2938,2532],{},"能修复 KeyError，用 ",[60,2939,2496],{},[130,2941,741],{"id":741},[140,2943,2944,2949,2954,2961],{},[143,2945,2946],{},[119,2947,751],{"href":748,"rel":2948},[750],[143,2950,2951],{},[119,2952,758],{"href":756,"rel":2953},[750],[143,2955,2956],{},[119,2957,2959,767],{"href":763,"rel":2958},[750],[60,2960,198],{},[143,2962,2963],{},[119,2964,2967],{"href":2965,"rel":2966},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fstdtypes.html#dict.get",[750],[60,2968,2969],{},"dict.get()",[790,2971,792],{},{"title":625,"searchDepth":732,"depth":732,"links":2973},[2974,2979,2980,2981,2982,2988,2989,2994,2995,2996,2997,3008],{"id":132,"depth":732,"text":133,"children":2975},[2976,2977,2978],{"id":137,"depth":34,"text":138},{"id":148,"depth":34,"text":149},{"id":157,"depth":34,"text":158},{"id":166,"depth":732,"text":167},{"id":230,"depth":732,"text":231},{"id":324,"depth":732,"text":325},{"id":369,"depth":732,"text":370,"children":2983},[2984,2985,2986,2987],{"id":425,"depth":34,"text":426},{"id":369,"depth":34,"text":434},{"id":442,"depth":34,"text":443},{"id":451,"depth":34,"text":452},{"id":459,"depth":732,"text":460},{"id":474,"depth":732,"text":475,"children":2990},[2991,2992,2993],{"id":481,"depth":34,"text":138},{"id":493,"depth":34,"text":149},{"id":511,"depth":34,"text":158},{"id":519,"depth":732,"text":520},{"id":529,"depth":732,"text":530},{"id":538,"depth":732,"text":539},{"id":2653,"depth":732,"text":2654,"children":2998},[2999,3000,3001,3002,3004,3005,3006,3007],{"id":2666,"depth":34,"text":2666},{"id":2672,"depth":34,"text":2673},{"id":2731,"depth":34,"text":2732},{"id":2747,"depth":34,"text":3003},"异常链 raise ... from ...",{"id":2795,"depth":34,"text":2796},{"id":2848,"depth":34,"text":2849},{"id":616,"depth":34,"text":617},{"id":2923,"depth":34,"text":2923},{"id":741,"depth":732,"text":741},"Week 3 的实际产出：Day 1 异常与 traceback 训练，能读 traceback、区分调用帧与抛出点、解释 KeyError \u002F AttributeError \u002F TypeError \u002F ZeroDivisionError 的成因，并用 .get() 修复 KeyError。",{},"\u002Fwriting\u002F2026-09-30-week3-learning-outcomes","2026-09-30T10:05:00+08:00",{"title":2433,"description":3009},"writing\u002F2026-09-30-week3-learning-outcomes",[837,838,839,840,198,3016,842,1527],"异常链","34tzJ-poiySoot8l6Hi4rUHJ-nOz81fUMGlqycvIsh4",{"id":109,"title":110,"body":3019,"category":824,"date":825,"description":826,"extension":827,"hidden":828,"kind":829,"meta":3526,"navigation":30,"path":831,"publishedAt":832,"seo":3527,"series":102,"slug":834,"stem":835,"tags":3528,"unit":843,"updated":834,"__hash__":844},{"type":112,"value":3020,"toc":3495},[3021,3027,3029,3031,3035,3037,3041,3043,3047,3049,3051,3053,3087,3089,3091,3109,3111,3149,3151,3153,3155,3185,3187,3189,3225,3227,3229,3233,3235,3239,3241,3245,3247,3251,3253,3261,3263,3265,3267,3275,3277,3290,3292,3296,3298,3302,3304,3308,3310,3312,3314,3368,3370,3375,3377,3407,3409,3439,3441,3443,3455,3457,3459,3493],[115,3022,117,3023,123,3025,128],{},[119,3024,122],{"href":121},[125,3026,127],{},[130,3028,133],{"id":132},[135,3030,138],{"id":137},[140,3032,3033],{},[143,3034,145],{},[135,3036,149],{"id":148},[140,3038,3039],{},[143,3040,154],{},[135,3042,158],{"id":157},[140,3044,3045],{},[143,3046,163],{},[130,3048,167],{"id":166},[115,3050,170],{},[115,3052,173],{},[140,3054,3055,3069,3075,3079,3083],{},[143,3056,3057,181,3059,185,3061,185,3063,185,3065,195,3067,199],{},[125,3058,180],{},[60,3060,184],{},[60,3062,188],{},[60,3064,191],{},[60,3066,194],{},[60,3068,198],{},[143,3070,3071,205,3073,209],{},[125,3072,204],{},[60,3074,208],{},[143,3076,3077,215],{},[125,3078,214],{},[143,3080,3081,221],{},[125,3082,220],{},[143,3084,3085,227],{},[125,3086,226],{},[130,3088,231],{"id":230},[115,3090,234],{},[140,3092,3094,3099,3104],{"className":3093},[238],[143,3095,3097,247],{"className":3096},[242],[244,3098],{"disabled":30,"type":246},[143,3100,3102,253],{"className":3101},[242],[244,3103],{"disabled":30,"type":246},[143,3105,3107,259],{"className":3106},[242],[244,3108],{"disabled":30,"type":246},[115,3110,262],{},[264,3112,3113,3123],{},[267,3114,3115],{},[270,3116,3117,3119,3121],{},[273,3118,275],{},[273,3120,278],{},[273,3122,281],{},[283,3124,3125,3133,3141],{},[270,3126,3127,3129,3131],{},[288,3128,290],{},[288,3130,293],{},[288,3132,296],{},[270,3134,3135,3137,3139],{},[288,3136,301],{},[288,3138,304],{},[288,3140,307],{},[270,3142,3143,3145,3147],{},[288,3144,312],{},[288,3146,315],{},[288,3148,318],{},[115,3150,321],{},[130,3152,325],{"id":324},[115,3154,328],{},[264,3156,3157,3165],{},[267,3158,3159],{},[270,3160,3161,3163],{},[273,3162,337],{},[273,3164,340],{},[283,3166,3167,3173,3179],{},[270,3168,3169,3171],{},[288,3170,347],{},[288,3172,350],{},[270,3174,3175,3177],{},[288,3176,355],{},[288,3178,358],{},[270,3180,3181,3183],{},[288,3182,363],{},[288,3184,366],{},[130,3186,370],{"id":369},[115,3188,373],{},[264,3190,3191,3199],{},[267,3192,3193],{},[270,3194,3195,3197],{},[273,3196,382],{},[273,3198,385],{},[283,3200,3201,3207,3213,3219],{},[270,3202,3203,3205],{},[288,3204,392],{},[288,3206,395],{},[270,3208,3209,3211],{},[288,3210,400],{},[288,3212,403],{},[270,3214,3215,3217],{},[288,3216,408],{},[288,3218,411],{},[270,3220,3221,3223],{},[288,3222,416],{},[288,3224,419],{},[115,3226,422],{},[135,3228,426],{"id":425},[140,3230,3231],{},[143,3232,431],{},[135,3234,434],{"id":369},[140,3236,3237],{},[143,3238,439],{},[135,3240,443],{"id":442},[140,3242,3243],{},[143,3244,448],{},[135,3246,452],{"id":451},[140,3248,3249],{},[143,3250,448],{},[130,3252,460],{"id":459},[140,3254,3255,3257,3259],{},[143,3256,465],{},[143,3258,468],{},[143,3260,471],{},[130,3262,475],{"id":474},[115,3264,478],{},[135,3266,138],{"id":481},[140,3268,3270],{"className":3269},[238],[143,3271,3273,490],{"className":3272},[242],[244,3274],{"disabled":30,"type":246},[135,3276,149],{"id":493},[140,3278,3280,3285],{"className":3279},[238],[143,3281,3283,502],{"className":3282},[242],[244,3284],{"disabled":30,"type":246},[143,3286,3288,508],{"className":3287},[242],[244,3289],{"disabled":30,"type":246},[135,3291,158],{"id":511},[140,3293,3294],{},[143,3295,516],{},[130,3297,520],{"id":519},[522,3299,3300],{},[115,3301,526],{},[130,3303,530],{"id":529},[140,3305,3306],{},[143,3307,535],{},[130,3309,539],{"id":538},[115,3311,149],{},[130,3313,545],{"id":544},[264,3315,3316,3326],{},[267,3317,3318],{},[270,3319,3320,3322,3324],{},[273,3321,554],{},[273,3323,557],{},[273,3325,560],{},[283,3327,3328,3336,3344,3352,3360],{},[270,3329,3330,3332,3334],{},[288,3331,567],{},[288,3333,570],{},[288,3335,573],{},[270,3337,3338,3340,3342],{},[288,3339,578],{},[288,3341,581],{},[288,3343,573],{},[270,3345,3346,3348,3350],{},[288,3347,588],{},[288,3349,591],{},[288,3351,573],{},[270,3353,3354,3356,3358],{},[288,3355,598],{},[288,3357,601],{},[288,3359,573],{},[270,3361,3362,3364,3366],{},[288,3363,608],{},[288,3365,611],{},[288,3367,573],{},[130,3369,617],{"id":616},[619,3371,3373],{"className":3372,"code":623,"language":624,"meta":625},[622],[60,3374,623],{"__ignoreMap":625},[130,3376,631],{"id":630},[264,3378,3379,3389],{},[267,3380,3381],{},[270,3382,3383,3385,3387],{},[273,3384,640],{},[273,3386,643],{},[273,3388,646],{},[283,3390,3391,3399],{},[270,3392,3393,3395,3397],{},[288,3394,214],{},[288,3396,655],{},[288,3398,658],{},[270,3400,3401,3403,3405],{},[288,3402,220],{},[288,3404,665],{},[288,3406,668],{},[130,3408,671],{"id":671},[264,3410,3411,3419],{},[267,3412,3413],{},[270,3414,3415,3417],{},[273,3416,680],{},[273,3418,683],{},[283,3420,3421,3427,3433],{},[270,3422,3423,3425],{},[288,3424,293],{},[288,3426,692],{},[270,3428,3429,3431],{},[288,3430,697],{},[288,3432,700],{},[270,3434,3435,3437],{},[288,3436,705],{},[288,3438,708],{},[130,3440,711],{"id":711},[115,3442,714],{},[619,3444,3445],{"className":717,"code":718,"language":719,"meta":625,"style":625},[60,3446,3447,3451],{"__ignoreMap":625},[723,3448,3449],{"class":725,"line":726},[723,3450,729],{},[723,3452,3453],{"class":725,"line":732},[723,3454,735],{},[115,3456,738],{},[130,3458,741],{"id":741},[140,3460,3461,3466,3471,3478,3483,3488],{},[143,3462,3463],{},[119,3464,751],{"href":748,"rel":3465},[750],[143,3467,3468],{},[119,3469,758],{"href":756,"rel":3470},[750],[143,3472,3473],{},[119,3474,3476,767],{"href":763,"rel":3475},[750],[60,3477,198],{},[143,3479,3480],{},[119,3481,774],{"href":772,"rel":3482},[750],[143,3484,3485],{},[119,3486,781],{"href":779,"rel":3487},[750],[143,3489,3490],{},[119,3491,788],{"href":786,"rel":3492},[750],[790,3494,792],{},{"title":625,"searchDepth":732,"depth":732,"links":3496},[3497,3502,3503,3504,3505,3511,3512,3517,3518,3519,3520,3521,3522,3523,3524,3525],{"id":132,"depth":732,"text":133,"children":3498},[3499,3500,3501],{"id":137,"depth":34,"text":138},{"id":148,"depth":34,"text":149},{"id":157,"depth":34,"text":158},{"id":166,"depth":732,"text":167},{"id":230,"depth":732,"text":231},{"id":324,"depth":732,"text":325},{"id":369,"depth":732,"text":370,"children":3506},[3507,3508,3509,3510],{"id":425,"depth":34,"text":426},{"id":369,"depth":34,"text":434},{"id":442,"depth":34,"text":443},{"id":451,"depth":34,"text":452},{"id":459,"depth":732,"text":460},{"id":474,"depth":732,"text":475,"children":3513},[3514,3515,3516],{"id":481,"depth":34,"text":138},{"id":493,"depth":34,"text":149},{"id":511,"depth":34,"text":158},{"id":519,"depth":732,"text":520},{"id":529,"depth":732,"text":530},{"id":538,"depth":732,"text":539},{"id":544,"depth":732,"text":545},{"id":616,"depth":732,"text":617},{"id":630,"depth":732,"text":631},{"id":671,"depth":732,"text":671},{"id":711,"depth":732,"text":711},{"id":741,"depth":732,"text":741},{},{"title":110,"description":826},[837,838,839,840,204,841,214,220,842,711],{"id":3530,"title":3531,"body":3532,"category":824,"date":6196,"description":6197,"extension":827,"hidden":828,"kind":1515,"meta":6198,"navigation":30,"path":6199,"publishedAt":6200,"seo":6201,"series":102,"slug":6202,"stem":6203,"tags":6204,"unit":6211,"updated":825,"__hash__":6212},"writing\u002Fwriting\u002F2026-09-21-python-week-2-outcomes-modules-exceptions.md","Week 2 学习成果｜闭包、decorator 与工程化重构",{"type":112,"value":3533,"toc":6129},[3534,3546,3548,3550,3555,3557,3562,3564,3568,3570,3613,3615,3654,3656,3695,3697,3699,3704,3706,3714,3716,3724,3726,3734,3736,3751,3753,3756,3758,3767,3782,3789,3791,3814,3816,3820,3822,3834,3851,3853,3864,3866,3868,3872,3880,3883,3929,3932,3971,3989,3992,4080,4116,4119,4148,4152,4160,4164,4236,4239,4245,4253,4257,4329,4412,4419,4436,4451,4501,4511,4576,4592,4598,4613,4632,4635,4639,4660,4668,4671,4681,4689,4723,4757,4782,4785,4825,4829,4837,4840,4852,4860,4914,4957,4961,5018,5024,5028,5062,5069,5073,5081,5139,5142,5148,5153,5210,5214,5222,5225,5231,5234,5333,5336,5342,5364,5366,5371,5426,5431,5500,5519,5521,5593,5599,5612,5623,5663,5674,5677,5680,5702,5709,5713,5721,5727,5733,5776,5835,5848,5851,5883,5886,5894,5913,5929,5949,5952,5955,6037,6040,6065,6068,6075,6078,6080,6126],[115,3535,2438,3536,2442,3540,123,3542,3545],{},[119,3537,3539],{"href":3538},"\u002Fwriting\u002F2026-09-20-python-week-2-functions-abstraction-modularization","Week 2 学习计划",[119,3541,122],{"href":121},[125,3543,3544],{},"Week 2 · 函数、作用域、闭包与模块化","（等级 L2）一致，按总纲 Part II「每周固定执行模板」编排。计划篇保留任务与要求，这里放实际做出来的东西和实验记录。",[130,3547,133],{"id":132},[135,3549,138],{"id":137},[140,3551,3552],{},[143,3553,3554],{},"✅ 已掌握函数参数、作用域、闭包、lambda、import \u002F module \u002F package",[135,3556,149],{"id":148},[140,3558,3559],{},[143,3560,3561],{},"🟡 已完成参数组合、闭包、自定义 module；decorator 简化版整理中；项目重构（目录拆分、logging、dataclass、type hints）进行中",[135,3563,158],{"id":157},[140,3565,3566],{},[143,3567,516],{},[130,3569,167],{"id":166},[140,3571,3572,3582,3592,3595,3610],{},[143,3573,3574,3575,185,3578,3581],{},"函数进阶：positional \u002F keyword \u002F default 参数、",[60,3576,3577],{},"*args",[60,3579,3580],{},"**kwargs","、可变默认参数陷阱、lambda",[143,3583,3584,3585,185,3588,3591],{},"作用域与闭包：LEGB、",[60,3586,3587],{},"global",[60,3589,3590],{},"nonlocal","、闭包捕获定义时的环境",[143,3593,3594],{},"decorator：接受函数、返回新函数的高阶函数，本质依赖闭包",[143,3596,3597,3598,3601,3602,3605,3606,3609],{},"模块与包：module \u002F package \u002F subpackage、三种 import、",[60,3599,3600],{},"sys.modules"," 缓存、",[60,3603,3604],{},"sys.path"," 查找、",[60,3607,3608],{},"python -m","、循环导入",[143,3611,3612],{},"工程化：分层（storage → core → cli → config）、dataclass、type hints、logging",[130,3614,231],{"id":230},[140,3616,3618,3624,3639,3645],{"className":3617},[238],[143,3619,3621,3623],{"className":3620},[242],[244,3622],{"checked":30,"disabled":30,"type":246}," 手写：参数组合（positional \u002F keyword \u002F default）",[143,3625,3627,3629,3630,195,3633,185,3636,1588],{"className":3626},[242],[244,3628],{"checked":30,"disabled":30,"type":246}," 手写：闭包（",[60,3631,3632],{},"make_counter",[60,3634,3635],{},"outer",[60,3637,3638],{},"inner",[143,3640,3642,3644],{"className":3641},[242],[244,3643],{"checked":30,"disabled":30,"type":246}," 手写：decorator 简化版",[143,3646,3648,3650,3651,1588],{"className":3647},[242],[244,3649],{"checked":30,"disabled":30,"type":246}," 手写：自定义 module（",[60,3652,3653],{},"mypkg\u002F",[130,3655,325],{"id":324},[140,3657,3659,3677,3683,3689],{"className":3658},[238],[143,3660,3662,3664,3665,185,3668,185,3671,185,3674],{"className":3661},[242],[244,3663],{"checked":30,"disabled":30,"type":246}," 将 Week 1 项目重构为 ",[60,3666,3667],{},"core\u002F",[60,3669,3670],{},"storage\u002F",[60,3672,3673],{},"cli\u002F",[60,3675,3676],{},"config\u002F",[143,3678,3680,3682],{"className":3679},[242],[244,3681],{"checked":30,"disabled":30,"type":246}," 加入 dataclass",[143,3684,3686,3688],{"className":3685},[242],[244,3687],{"checked":30,"disabled":30,"type":246}," 加入 type hints",[143,3690,3692,3694],{"className":3691},[242],[244,3693],{"checked":30,"disabled":30,"type":246}," 加入 logging",[130,3696,370],{"id":369},[135,3698,426],{"id":425},[140,3700,3701],{},[143,3702,3703],{},"通过目录拆分、dataclass、type hints 与 logging，Week 1 项目能在不改变对外行为的前提下变得可维护、可测试。",[135,3705,434],{"id":369},[140,3707,3708],{},[143,3709,3710,3711,3713],{},"将 Week 1 的 KV 项目按职责拆为 storage \u002F core \u002F cli \u002F config 四层，接入 logging 与 ",[60,3712,951],{},"，补充 dataclass 与类型标注，并用 pytest 验证行为不变。",[135,3715,443],{"id":442},[140,3717,3718],{},[143,3719,3720,3721,3723],{},"重构后行为不变，测试通过；",[60,3722,968],{}," 实验确认「谁先被 import，谁就决定全局日志格式」——详见 Day 5 实验 A \u002F B。",[135,3725,452],{"id":451},[140,3727,3728],{},[143,3729,3730,3731,3733],{},"验证了假设：重构在不改变行为的前提下提升了可维护性；",[60,3732,968],{}," 必须只在入口调用一次。",[130,3735,460],{"id":459},[140,3737,3738,3741,3748],{},[143,3739,3740],{},"Python 函数、作用域、闭包与 decorator",[143,3742,3743,3744,195,3746,1588],{},"Python 模块与包机制（import、",[60,3745,3604],{},[60,3747,3600],{},[143,3749,3750],{},"代码组织、分层与工程化重构",[130,3752,475],{"id":474},[115,3754,3755],{},"总纲 Week 2 验收：",[135,3757,138],{"id":481},[140,3759,3761],{"className":3760},[238],[143,3762,3764,3766],{"className":3763},[242],[244,3765],{"checked":30,"disabled":30,"type":246}," 能独立解释：",[619,3768,3770],{"className":717,"code":3769,"language":719,"meta":625,"style":625},"x = outer()\nx()\n",[60,3771,3772,3777],{"__ignoreMap":625},[723,3773,3774],{"class":725,"line":726},[723,3775,3776],{},"x = outer()\n",[723,3778,3779],{"class":725,"line":732},[723,3780,3781],{},"x()\n",[115,3783,3784,3785,3788],{},"为什么还能访问 ",[60,3786,3787],{},"outer()"," 中定义的变量。",[135,3790,149],{"id":493},[140,3792,3794,3800],{"className":3793},[238],[143,3795,3797,3799],{"className":3796},[242],[244,3798],{"checked":30,"disabled":30,"type":246}," 手写参数组合、闭包、decorator 简化版、自定义 module",[143,3801,3803,3664,3805,185,3807,185,3809,185,3811,3813],{"className":3802},[242],[244,3804],{"checked":30,"disabled":30,"type":246},[60,3806,3667],{},[60,3808,3670],{},[60,3810,3673],{},[60,3812,3676],{},"，加入 dataclass、type hints、logging",[135,3815,158],{"id":511},[140,3817,3818],{},[143,3819,516],{},[130,3821,520],{"id":519},[522,3823,3824],{},[115,3825,3826,3827,3830,3831,3833],{},"为什么 ",[60,3828,3829],{},"x = outer(); x()"," 还能访问 ",[60,3832,3787],{}," 中定义的变量？",[115,3835,3836,3837,3839,3840,3842,3843,3846,3847,3850],{},"因为闭包会把“定义时的环境”和函数对象绑在一起：",[60,3838,3638],{}," 被返回后，它仍持有对 ",[60,3841,3635],{}," 局部作用域（enclosing）的引用，",[60,3844,3845],{},"x()"," 时按 LEGB 先查到 E 层的 ",[60,3848,3849],{},"n","。只要这个函数对象还活着，被它引用的外层变量就不会被回收。",[130,3852,530],{"id":529},[140,3854,3855],{},[143,3856,3857,3858,1471,3861],{},"业务代码手写熟练度不足，需要不看参考从零重写 ",[60,3859,3860],{},"KVService",[60,3862,3863],{},"cli.py",[130,3865,539],{"id":538},[115,3867,149],{},[130,3869,3871],{"id":3870},"day-1-成果参数组合与闭包","Day 1 成果｜参数组合与闭包",[115,3873,3874,181,3876],{},[125,3875,2659],{},[119,3877,3879],{"href":3878},"\u002Fwriting\u002F2026-09-20-python-week-2-functions-abstraction-modularization#day-1%E5%87%BD%E6%95%B0%E8%BF%9B%E9%98%B6%E5%8F%82%E6%95%B0%E4%BD%9C%E7%94%A8%E5%9F%9F%E9%97%AD%E5%8C%8515%E2%80%932h","Day 1｜函数进阶：参数、作用域、闭包",[135,3881,3882],{"id":3882},"参数组合",[264,3884,3885,3894],{},[267,3886,3887],{},[270,3888,3889,3892],{},[273,3890,3891],{},"写法",[273,3893,278],{},[283,3895,3896,3907,3918],{},[270,3897,3898,3901],{},[288,3899,3900],{},"positional",[288,3902,3903,3906],{},[60,3904,3905],{},"f(1, 2)"," 按位置传",[270,3908,3909,3912],{},[288,3910,3911],{},"keyword",[288,3913,3914,3917],{},[60,3915,3916],{},"f(a=1, b=2)"," 按名字传",[270,3919,3920,3923],{},[288,3921,3922],{},"default",[288,3924,3925,3928],{},[60,3926,3927],{},"def f(a, b=10)"," 可省略",[115,3930,3931],{},"可变默认参数陷阱：",[619,3933,3935],{"className":717,"code":3934,"language":719,"meta":625,"style":625},"def append_to(item, target=[]):\n    target.append(item)\n    return target\n\nprint(append_to(1))       # [1]\nprint(append_to(2))       # [1, 2] ← 不是 [2]，默认 list 被共享了\nprint(append_to(3, []))   # [3]    ← 显式传新 list 才隔离\n",[60,3936,3937,3942,3947,3952,3956,3961,3966],{"__ignoreMap":625},[723,3938,3939],{"class":725,"line":726},[723,3940,3941],{},"def append_to(item, target=[]):\n",[723,3943,3944],{"class":725,"line":732},[723,3945,3946],{},"    target.append(item)\n",[723,3948,3949],{"class":725,"line":34},[723,3950,3951],{},"    return target\n",[723,3953,3954],{"class":725,"line":1068},[723,3955,1060],{"emptyLinePlaceholder":30},[723,3957,3958],{"class":725,"line":1074},[723,3959,3960],{},"print(append_to(1))       # [1]\n",[723,3962,3963],{"class":725,"line":1080},[723,3964,3965],{},"print(append_to(2))       # [1, 2] ← 不是 [2]，默认 list 被共享了\n",[723,3967,3968],{"class":725,"line":1086},[723,3969,3970],{},"print(append_to(3, []))   # [3]    ← 显式传新 list 才隔离\n",[115,3972,3973,3974,3977,3978,3981,3982,3985,3986,863],{},"原因：默认参数只在函数",[125,3975,3976],{},"定义时","求值一次，",[60,3979,3980],{},"target=[]"," 这个 list 被所有调用共享。修复：",[60,3983,3984],{},"target=None","，函数内再 ",[60,3987,3988],{},"if target is None: target = []",[135,3990,3991],{"id":3991},"闭包计数器",[619,3993,3995],{"className":717,"code":3994,"language":719,"meta":625,"style":625},"def make_counter():\n    n = 0\n    def counter():\n        nonlocal n\n        n += 1\n        return n\n    return counter\n\ncounter = make_counter()\nprint(counter())   # 1\nprint(counter())   # 2\nprint(counter())   # 3\n\na = make_counter()\nb = make_counter()\nprint(a(), a())    # 1 2\nprint(b())         # 1 ← 两个计数器相互独立\n",[60,3996,3997,4002,4007,4012,4017,4022,4027,4032,4036,4041,4046,4051,4056,4060,4065,4070,4075],{"__ignoreMap":625},[723,3998,3999],{"class":725,"line":726},[723,4000,4001],{},"def make_counter():\n",[723,4003,4004],{"class":725,"line":732},[723,4005,4006],{},"    n = 0\n",[723,4008,4009],{"class":725,"line":34},[723,4010,4011],{},"    def counter():\n",[723,4013,4014],{"class":725,"line":1068},[723,4015,4016],{},"        nonlocal n\n",[723,4018,4019],{"class":725,"line":1074},[723,4020,4021],{},"        n += 1\n",[723,4023,4024],{"class":725,"line":1080},[723,4025,4026],{},"        return n\n",[723,4028,4029],{"class":725,"line":1086},[723,4030,4031],{},"    return counter\n",[723,4033,4034],{"class":725,"line":1092},[723,4035,1060],{"emptyLinePlaceholder":30},[723,4037,4038],{"class":725,"line":1098},[723,4039,4040],{},"counter = make_counter()\n",[723,4042,4043],{"class":725,"line":1104},[723,4044,4045],{},"print(counter())   # 1\n",[723,4047,4048],{"class":725,"line":1110},[723,4049,4050],{},"print(counter())   # 2\n",[723,4052,4053],{"class":725,"line":1116},[723,4054,4055],{},"print(counter())   # 3\n",[723,4057,4058],{"class":725,"line":1122},[723,4059,1060],{"emptyLinePlaceholder":30},[723,4061,4062],{"class":725,"line":1128},[723,4063,4064],{},"a = make_counter()\n",[723,4066,4067],{"class":725,"line":1133},[723,4068,4069],{},"b = make_counter()\n",[723,4071,4072],{"class":725,"line":1139},[723,4073,4074],{},"print(a(), a())    # 1 2\n",[723,4076,4077],{"class":725,"line":1145},[723,4078,4079],{},"print(b())         # 1 ← 两个计数器相互独立\n",[115,4081,4082,181,4085,4088,4089,4091,4092,4094,4095,4098,4099,4101,4102,195,4104,4107,4108,4110,4111,4113,4114,863],{},[125,4083,4084],{},"关键理解",[60,4086,4087],{},"counter"," 捕获的是 ",[60,4090,3632],{}," 的局部变量 ",[60,4093,3849],{},"（enclosing 层），而不是它当时的值。每次调用 ",[60,4096,4097],{},"make_counter()"," 都会新建一个独立的 ",[60,4100,3849],{},"，所以 ",[60,4103,119],{},[60,4105,4106],{},"b"," 互不影响。",[60,4109,3590],{}," 让 ",[60,4112,4087],{}," 内部可以修改外层的 ",[60,4115,3849],{},[135,4117,4118],{"id":4118},"验收",[140,4120,4121,4127,4135,4145],{},[143,4122,4123,4124,4126],{},"默认参数",[125,4125,3976],{},"求值一次 → 可变默认值被共享",[143,4128,4129,4131,4132,4134],{},[60,4130,3577],{}," 收集位置参数成 tuple；",[60,4133,3580],{}," 收集关键字参数成 dict",[143,4136,4137,4138,4141,4142,4144],{},"闭包捕获的是",[125,4139,4140],{},"变量（环境）","，不是值（所以要用 ",[60,4143,3590],{}," 修改）",[143,4146,4147],{},"“函数是对象”意味着：可赋值、可当参数传、可当返回值",[130,4149,4151],{"id":4150},"day-2-成果自定义-module-与-import-机制","Day 2 成果｜自定义 module 与 import 机制",[115,4153,4154,181,4156],{},[125,4155,2659],{},[119,4157,4159],{"href":4158},"\u002Fwriting\u002F2026-09-20-python-week-2-functions-abstraction-modularization#day-2%E8%87%AA%E5%AE%9A%E4%B9%89-module-%E4%B8%8E%E5%8C%8515%E2%80%932h","Day 2｜自定义 module 与包",[135,4161,4163],{"id":4162},"模块-包-子包","模块 \u002F 包 \u002F 子包",[264,4165,4166,4177],{},[267,4167,4168],{},[270,4169,4170,4172,4175],{},[273,4171,640],{},[273,4173,4174],{},"对应什么",[273,4176,281],{},[283,4178,4179,4200,4219],{},[270,4180,4181,4184,4191],{},[288,4182,4183],{},"模块 module",[288,4185,4186,4187,4190],{},"一个 ",[60,4188,4189],{},".py"," 文件",[288,4192,4193,4196,4197],{},[60,4194,4195],{},"core.py"," → 模块 ",[60,4198,4199],{},"mypkg.core",[270,4201,4202,4205,4211],{},[288,4203,4204],{},"包 package",[288,4206,4207,4208],{},"一个目录，含 ",[60,4209,4210],{},"__init__.py",[288,4212,4213,4215,4216],{},[60,4214,3653],{}," → 包 ",[60,4217,4218],{},"mypkg",[270,4220,4221,4224,4227],{},[288,4222,4223],{},"子包 subpackage",[288,4225,4226],{},"包里面的包",[288,4228,4229,4232,4233],{},[60,4230,4231],{},"mypkg\u002Futils\u002F"," → 子包 ",[60,4234,4235],{},"mypkg.utils",[115,4237,4238],{},"实验用的结构：",[619,4240,4243],{"className":4241,"code":4242,"language":624},[622],"mypkg\u002F\n├── __init__.py\n├── core.py\n└── utils\u002F\n    ├── __init__.py\n    └── text.py\n",[60,4244,4242],{"__ignoreMap":625},[115,4246,4247],{},[125,4248,4249,4250,4252],{},"一句话：目录 = 包 \u002F 子包；",[60,4251,4189],{}," 文件 = 模块；点号表示层级。",[135,4254,4256],{"id":4255},"三种-import-写法","三种 import 写法",[619,4258,4260],{"className":717,"code":4259,"language":719,"meta":625,"style":625},"# 1. import x —— 绑定包名\nimport mypkg.core\nmypkg.core.hello()      # 当前命名空间只绑定 mypkg，必须写全 mypkg.core\n\n# 2. from x import y —— 绑定里面的名字\nfrom mypkg import core\ncore.hello()\n\nfrom mypkg.core import hello\nhello()\n\n# 3. from x import y as z —— 重命名\nfrom mypkg.core import hello as hi\nhi()\n",[60,4261,4262,4267,4272,4277,4281,4286,4291,4296,4300,4305,4310,4314,4319,4324],{"__ignoreMap":625},[723,4263,4264],{"class":725,"line":726},[723,4265,4266],{},"# 1. import x —— 绑定包名\n",[723,4268,4269],{"class":725,"line":732},[723,4270,4271],{},"import mypkg.core\n",[723,4273,4274],{"class":725,"line":34},[723,4275,4276],{},"mypkg.core.hello()      # 当前命名空间只绑定 mypkg，必须写全 mypkg.core\n",[723,4278,4279],{"class":725,"line":1068},[723,4280,1060],{"emptyLinePlaceholder":30},[723,4282,4283],{"class":725,"line":1074},[723,4284,4285],{},"# 2. from x import y —— 绑定里面的名字\n",[723,4287,4288],{"class":725,"line":1080},[723,4289,4290],{},"from mypkg import core\n",[723,4292,4293],{"class":725,"line":1086},[723,4294,4295],{},"core.hello()\n",[723,4297,4298],{"class":725,"line":1092},[723,4299,1060],{"emptyLinePlaceholder":30},[723,4301,4302],{"class":725,"line":1098},[723,4303,4304],{},"from mypkg.core import hello\n",[723,4306,4307],{"class":725,"line":1104},[723,4308,4309],{},"hello()\n",[723,4311,4312],{"class":725,"line":1110},[723,4313,1060],{"emptyLinePlaceholder":30},[723,4315,4316],{"class":725,"line":1116},[723,4317,4318],{},"# 3. from x import y as z —— 重命名\n",[723,4320,4321],{"class":725,"line":1122},[723,4322,4323],{},"from mypkg.core import hello as hi\n",[723,4325,4326],{"class":725,"line":1128},[723,4327,4328],{},"hi()\n",[264,4330,4331,4343],{},[267,4332,4333],{},[270,4334,4335,4337,4340],{},[273,4336,3891],{},[273,4338,4339],{},"当前绑定谁",[273,4341,4342],{},"调用方式",[283,4344,4345,4361,4378,4395],{},[270,4346,4347,4352,4356],{},[288,4348,4349],{},[60,4350,4351],{},"import mypkg.core",[288,4353,4354],{},[60,4355,4218],{},[288,4357,4358],{},[60,4359,4360],{},"mypkg.core.hello()",[270,4362,4363,4368,4373],{},[288,4364,4365],{},[60,4366,4367],{},"from mypkg import core",[288,4369,4370],{},[60,4371,4372],{},"core",[288,4374,4375],{},[60,4376,4377],{},"core.hello()",[270,4379,4380,4385,4390],{},[288,4381,4382],{},[60,4383,4384],{},"from mypkg.core import hello",[288,4386,4387],{},[60,4388,4389],{},"hello",[288,4391,4392],{},[60,4393,4394],{},"hello()",[270,4396,4397,4402,4407],{},[288,4398,4399],{},[60,4400,4401],{},"from mypkg.core import hello as hi",[288,4403,4404],{},[60,4405,4406],{},"hi",[288,4408,4409],{},[60,4410,4411],{},"hi()",[135,4413,4415,4418],{"id":4414},"__name__-与-main-守卫",[60,4416,4417],{},"__name__"," 与 main 守卫",[140,4420,4421,4427],{},[143,4422,4423,4424],{},"直接运行文件：",[60,4425,4426],{},"__name__ == \"__main__\"",[143,4428,4429,4430,4432,4433],{},"被导入时：",[60,4431,4417],{}," 是模块导入路径，如 ",[60,4434,4435],{},"\"mypkg.core\"",[619,4437,4439],{"className":717,"code":4438,"language":719,"meta":625,"style":625},"if __name__ == \"__main__\":\n    hello()\n",[60,4440,4441,4446],{"__ignoreMap":625},[723,4442,4443],{"class":725,"line":726},[723,4444,4445],{},"if __name__ == \"__main__\":\n",[723,4447,4448],{"class":725,"line":732},[723,4449,4450],{},"    hello()\n",[264,4452,4453,4466],{},[267,4454,4455],{},[270,4456,4457,4460],{},[273,4458,4459],{},"运行方式",[273,4461,4462,2839,4464],{},[60,4463,4195],{},[60,4465,4417],{},[283,4467,4468,4480,4491],{},[270,4469,4470,4475],{},[288,4471,4472],{},[60,4473,4474],{},"python mypkg\u002Fcore.py",[288,4476,4477],{},[60,4478,4479],{},"\"__main__\"",[270,4481,4482,4487],{},[288,4483,4484],{},[60,4485,4486],{},"python -m mypkg.core",[288,4488,4489],{},[60,4490,4479],{},[270,4492,4493,4497],{},[288,4494,4495],{},[60,4496,4351],{},[288,4498,4499],{},[60,4500,4435],{},[135,4502,4504,4507,4508],{"id":4503},"python-filepy-vs-python-m-pkgmod",[60,4505,4506],{},"python file.py"," vs ",[60,4509,4510],{},"python -m pkg.mod",[264,4512,4513,4528],{},[267,4514,4515],{},[270,4516,4517,4520,4524],{},[273,4518,4519],{},"对比项",[273,4521,4522],{},[60,4523,4474],{},[273,4525,4526],{},[60,4527,4486],{},[283,4529,4530,4544,4555,4565],{},[270,4531,4532,4537,4541],{},[288,4533,4534],{},[60,4535,4536],{},"sys.path[0]",[288,4538,4539],{},[60,4540,3653],{},[288,4542,4543],{},"当前工作目录",[270,4545,4546,4549,4552],{},[288,4547,4548],{},"包上下文",[288,4550,4551],{},"无",[288,4553,4554],{},"有",[270,4556,4557,4560,4563],{},[288,4558,4559],{},"相对导入",[288,4561,4562],{},"失败",[288,4564,2815],{},[270,4566,4567,4570,4573],{},[288,4568,4569],{},"适合",[288,4571,4572],{},"独立脚本",[288,4574,4575],{},"包内模块",[115,4577,4578,4579,181,4582,4584,4585,4588,4589,4591],{},"相对导入 ",[60,4580,4581],{},"from .utils import text",[60,4583,4474],{}," 报 ",[60,4586,4587],{},"attempted relative import with no known parent package","；",[60,4590,4486],{}," 正常。",[135,4593,4595,4597],{"id":4594},"__init__py-与-re-export",[60,4596,4210],{}," 与 re-export",[619,4599,4601],{"className":717,"code":4600,"language":719,"meta":625,"style":625},"# mypkg\u002F__init__.py\nfrom .core import hello\n",[60,4602,4603,4608],{"__ignoreMap":625},[723,4604,4605],{"class":725,"line":726},[723,4606,4607],{},"# mypkg\u002F__init__.py\n",[723,4609,4610],{"class":725,"line":732},[723,4611,4612],{},"from .core import hello\n",[619,4614,4616],{"className":717,"code":4615,"language":719,"meta":625,"style":625},"# 外部\nfrom mypkg import hello\nhello()\n",[60,4617,4618,4623,4628],{"__ignoreMap":625},[723,4619,4620],{"class":725,"line":726},[723,4621,4622],{},"# 外部\n",[723,4624,4625],{"class":725,"line":732},[723,4626,4627],{},"from mypkg import hello\n",[723,4629,4630],{"class":725,"line":34},[723,4631,4309],{},[115,4633,4634],{},"好处：对外隐藏内部结构，重构内部文件时不影响外部调用。",[135,4636,4638],{"id":4637},"import-查找顺序与缓存","import 查找顺序与缓存",[4640,4641,4642,4650],"ol",{},[143,4643,4644,4649],{},[125,4645,4646,4648],{},[60,4647,3600],{}," 缓存"," —— 先看这个模块之前有没有被导入过，有就直接用，不再重新执行。",[143,4651,4652,4656,4657,4659],{},[125,4653,4654],{},[60,4655,3604],{}," —— 缓存里没有，就按 ",[60,4658,3604],{}," 里的目录顺序查找。",[115,4661,4662],{},[125,4663,4664,4665,4667],{},"结论：第一次导入会执行模块顶层代码并放入 ",[60,4666,3600],{},"；第二次导入直接复用缓存，不再执行。",[135,4669,4670],{"id":4670},"循环导入",[115,4672,4673,4674,4677,4678,4680],{},"成因：两个模块在顶层互相 ",[60,4675,4676],{},"import","，其中一个还没执行完，另一个就试图使用它还没定义好的名字。解法：把 ",[60,4679,4676],{}," 移到函数内部（延迟导入），或抽公共模块解耦。",[135,4682,4684,4685,4688],{"id":4683},"重点实验from-x-import-y-到底绑定了什么","重点实验：",[60,4686,4687],{},"from x import y"," 到底绑定了什么？",[619,4690,4692],{"className":717,"code":4691,"language":719,"meta":625,"style":625},"# counter.py\ncount = 0\n\ndef inc():\n    global count\n    count += 1\n",[60,4693,4694,4699,4704,4708,4713,4718],{"__ignoreMap":625},[723,4695,4696],{"class":725,"line":726},[723,4697,4698],{},"# counter.py\n",[723,4700,4701],{"class":725,"line":732},[723,4702,4703],{},"count = 0\n",[723,4705,4706],{"class":725,"line":34},[723,4707,1060],{"emptyLinePlaceholder":30},[723,4709,4710],{"class":725,"line":1068},[723,4711,4712],{},"def inc():\n",[723,4714,4715],{"class":725,"line":1074},[723,4716,4717],{},"    global count\n",[723,4719,4720],{"class":725,"line":1080},[723,4721,4722],{},"    count += 1\n",[619,4724,4726],{"className":717,"code":4725,"language":719,"meta":625,"style":625},"# main.py\nimport counter\nfrom counter import count\n\ncounter.inc()\nprint(counter.count, count)\n",[60,4727,4728,4733,4738,4743,4747,4752],{"__ignoreMap":625},[723,4729,4730],{"class":725,"line":726},[723,4731,4732],{},"# main.py\n",[723,4734,4735],{"class":725,"line":732},[723,4736,4737],{},"import counter\n",[723,4739,4740],{"class":725,"line":34},[723,4741,4742],{},"from counter import count\n",[723,4744,4745],{"class":725,"line":1068},[723,4746,1060],{"emptyLinePlaceholder":30},[723,4748,4749],{"class":725,"line":1074},[723,4750,4751],{},"counter.inc()\n",[723,4753,4754],{"class":725,"line":1080},[723,4755,4756],{},"print(counter.count, count)\n",[115,4758,4759,4760,863,4763,4766,4767,4770,4771,4774,4775,4778,4779,4781],{},"输出 ",[60,4761,4762],{},"1 0",[60,4764,4765],{},"from counter import count"," 绑定的是导入那一刻 ",[60,4768,4769],{},"count"," 指向的整数 ",[60,4772,4773],{},"0","，不是变量名本身；",[60,4776,4777],{},"counter.inc()"," 改的是 ",[60,4780,4087],{}," 模块自己的全局变量。",[135,4783,4784],{"id":4784},"验收四题",[4640,4786,4787,4796,4808,4819],{},[143,4788,4789,4792,4793,4795],{},[125,4790,4791],{},"import 同一模块两次，模块代码执行几次？"," 只执行一次，第二次复用 ",[60,4794,3600],{}," 缓存。",[143,4797,4798,4803,4804,4807],{},[125,4799,4800,4802],{},[60,4801,4210],{}," 能做什么？"," 执行包初始化、re-export、定义 ",[60,4805,4806],{},"__all__","、组织公开接口。",[143,4809,4810,4818],{},[125,4811,4812,4814,4815,4817],{},[60,4813,4506],{}," 和 ",[60,4816,4510],{}," 的区别？"," 前者当独立脚本，无包上下文；后者当包内模块，有包上下文，相对导入成功。",[143,4820,4821,4824],{},[125,4822,4823],{},"循环导入为什么发生？怎么解？"," 顶层互相导入，一方没执行完另一方就使用它。解法：延迟导入到函数内，或抽公共模块解耦。",[130,4826,4828],{"id":4827},"day-3-成果闭包与作用域深化","Day 3 成果｜闭包与作用域深化",[115,4830,4831,181,4833],{},[125,4832,2659],{},[119,4834,4836],{"href":4835},"\u002Fwriting\u002F2026-09-20-python-week-2-functions-abstraction-modularization#day-3%E9%97%AD%E5%8C%85%E4%B8%8E%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%B7%B1%E5%8C%9615%E2%80%932h","Day 3｜闭包与作用域深化",[115,4838,4839],{},"总纲 Week 2 的验收就落在这一条：",[619,4841,4842],{"className":717,"code":3769,"language":719,"meta":625,"style":625},[60,4843,4844,4848],{"__ignoreMap":625},[723,4845,4846],{"class":725,"line":726},[723,4847,3776],{},[723,4849,4850],{"class":725,"line":732},[723,4851,3781],{},[115,4853,4854],{},[125,4855,3826,4856,3830,4858,3833],{},[60,4857,3845],{},[60,4859,3787],{},[619,4861,4863],{"className":717,"code":4862,"language":719,"meta":625,"style":625},"def outer():\n    n = 0\n    def inner():\n        nonlocal n\n        n += 1\n        return n\n    return inner\n\nx = outer()\nprint(x())   # 1\nprint(x())   # 2\n",[60,4864,4865,4870,4874,4879,4883,4887,4891,4896,4900,4904,4909],{"__ignoreMap":625},[723,4866,4867],{"class":725,"line":726},[723,4868,4869],{},"def outer():\n",[723,4871,4872],{"class":725,"line":732},[723,4873,4006],{},[723,4875,4876],{"class":725,"line":34},[723,4877,4878],{},"    def inner():\n",[723,4880,4881],{"class":725,"line":1068},[723,4882,4016],{},[723,4884,4885],{"class":725,"line":1074},[723,4886,4021],{},[723,4888,4889],{"class":725,"line":1080},[723,4890,4026],{},[723,4892,4893],{"class":725,"line":1086},[723,4894,4895],{},"    return inner\n",[723,4897,4898],{"class":725,"line":1092},[723,4899,1060],{"emptyLinePlaceholder":30},[723,4901,4902],{"class":725,"line":1098},[723,4903,3776],{},[723,4905,4906],{"class":725,"line":1104},[723,4907,4908],{},"print(x())   # 1\n",[723,4910,4911],{"class":725,"line":1110},[723,4912,4913],{},"print(x())   # 2\n",[115,4915,4916,4918,4919,4921,4922,4928,4929,4931,4932,4934,4935,4937,4938,4940,4941,4944,4945,4947,4948,4950,4951,4110,4954,4956],{},[60,4917,3787],{}," 返回的是 ",[60,4920,3638],{}," 这个函数对象，它",[125,4923,4924,4925,4927],{},"带着 ",[60,4926,3635],{}," 的作用域环境一起被返回","（闭包）。",[60,4930,3845],{}," 执行时按 LEGB 找 ",[60,4933,3849],{},"：L 层没有 → E 层（",[60,4936,3635],{}," 的局部作用域）找到 ",[60,4939,3849],{},"。只要 ",[60,4942,4943],{},"x"," 还引用着 ",[60,4946,3638],{},"，",[60,4949,3849],{}," 就不会被回收。",[60,4952,4953],{},"nonlocal n",[60,4955,3638],{}," 能修改这个外层变量，而不只是读取。",[135,4958,4960],{"id":4959},"legb-查找顺序","LEGB 查找顺序",[264,4962,4963,4972],{},[267,4964,4965],{},[270,4966,4967,4969],{},[273,4968,1228],{},[273,4970,4971],{},"含义",[283,4973,4974,4985,4996,5007],{},[270,4975,4976,4982],{},[288,4977,4978,4981],{},[125,4979,4980],{},"L"," Local",[288,4983,4984],{},"当前函数内部",[270,4986,4987,4993],{},[288,4988,4989,4992],{},[125,4990,4991],{},"E"," Enclosing",[288,4994,4995],{},"外层嵌套函数",[270,4997,4998,5004],{},[288,4999,5000,5003],{},[125,5001,5002],{},"G"," Global",[288,5005,5006],{},"模块顶层",[270,5008,5009,5015],{},[288,5010,5011,5014],{},[125,5012,5013],{},"B"," Built-in",[288,5016,5017],{},"内置（print、len…）",[115,5019,5020,5021,863],{},"从 L 往外逐层找，",[125,5022,5023],{},"找到即停",[135,5025,5027],{"id":5026},"global-nonlocal","global \u002F nonlocal",[264,5029,5030,5040],{},[267,5031,5032],{},[270,5033,5034,5037],{},[273,5035,5036],{},"关键字",[273,5038,5039],{},"声明效果",[283,5041,5042,5052],{},[270,5043,5044,5049],{},[288,5045,5046],{},[60,5047,5048],{},"global x",[288,5050,5051],{},"x 属于全局 → 函数内赋值改的是全局变量",[270,5053,5054,5059],{},[288,5055,5056],{},[60,5057,5058],{},"nonlocal x",[288,5060,5061],{},"x 属于外层函数 → 函数内赋值改的是外层变量",[115,5063,5064,5065,5068],{},"不加声明却赋值，又想读写外层\u002F全局变量 → 报 ",[60,5066,5067],{},"UnboundLocalError","（赋值让名字变局部，局部还没赋值就被读取）。",[130,5070,5072],{"id":5071},"day-4-成果decorator-简化版","Day 4 成果｜decorator 简化版",[115,5074,5075,181,5077],{},[125,5076,2659],{},[119,5078,5080],{"href":5079},"\u002Fwriting\u002F2026-09-20-python-week-2-functions-abstraction-modularization#day-4decorator-%E7%AE%80%E5%8C%96%E7%89%8815%E2%80%932h","Day 4｜decorator 简化版",[619,5082,5084],{"className":717,"code":5083,"language":719,"meta":625,"style":625},"def log(func):\n    def wrapper(*args, **kwargs):\n        print(f\"call {func.__name__} args={args} kwargs={kwargs}\")\n        return func(*args, **kwargs)\n    return wrapper\n\n@log\ndef add(a, b):\n    return a + b\n\nprint(add(1, 2))\n",[60,5085,5086,5091,5096,5101,5106,5111,5115,5120,5125,5130,5134],{"__ignoreMap":625},[723,5087,5088],{"class":725,"line":726},[723,5089,5090],{},"def log(func):\n",[723,5092,5093],{"class":725,"line":732},[723,5094,5095],{},"    def wrapper(*args, **kwargs):\n",[723,5097,5098],{"class":725,"line":34},[723,5099,5100],{},"        print(f\"call {func.__name__} args={args} kwargs={kwargs}\")\n",[723,5102,5103],{"class":725,"line":1068},[723,5104,5105],{},"        return func(*args, **kwargs)\n",[723,5107,5108],{"class":725,"line":1074},[723,5109,5110],{},"    return wrapper\n",[723,5112,5113],{"class":725,"line":1080},[723,5114,1060],{"emptyLinePlaceholder":30},[723,5116,5117],{"class":725,"line":1086},[723,5118,5119],{},"@log\n",[723,5121,5122],{"class":725,"line":1092},[723,5123,5124],{},"def add(a, b):\n",[723,5126,5127],{"class":725,"line":1098},[723,5128,5129],{},"    return a + b\n",[723,5131,5132],{"class":725,"line":1104},[723,5133,1060],{"emptyLinePlaceholder":30},[723,5135,5136],{"class":725,"line":1110},[723,5137,5138],{},"print(add(1, 2))\n",[115,5140,5141],{},"输出：",[619,5143,5146],{"className":5144,"code":5145,"language":624},[622],"call add args=(1, 2) kwargs={}\n3\n",[60,5147,5145],{"__ignoreMap":625},[115,5149,5150],{},[125,5151,5152],{},"关键理解：",[140,5154,5155,5169,5179,5192],{},[143,5156,5157,5160,5161,5164,5165,5168],{},[60,5158,5159],{},"@log"," 等价于 ",[60,5162,5163],{},"add = log(add)","：把原函数传进 ",[60,5166,5167],{},"log","，拿回被包一层的新函数。",[143,5170,5171,1288,5174,185,5176,5178],{},[60,5172,5173],{},"wrapper",[60,5175,3577],{},[60,5177,3580],{}," 透传任意参数，这样装饰器对任何签名都通用。",[143,5180,5181,5182,181,5185,5187,5188,5191],{},"装饰器本质是",[125,5183,5184],{},"闭包",[60,5186,5173],{}," 记住了外层的 ",[60,5189,5190],{},"func","，调用时先执行附加逻辑再执行原函数。",[143,5193,5194,5195,4101,5198,5201,5202,5205,5206,5209],{},"简化版故意没用 ",[60,5196,5197],{},"functools.wraps",[60,5199,5200],{},"add.__name__"," 会变成 ",[60,5203,5204],{},"\"wrapper\"","——这正是“简化”的代价，正式版需要 ",[60,5207,5208],{},"wraps"," 保留元信息。",[130,5211,5213],{"id":5212},"day-5-成果l2-重构目录拆分与-logging","Day 5 成果｜L2 重构：目录拆分与 logging",[115,5215,5216,181,5218],{},[125,5217,2659],{},[119,5219,5221],{"href":5220},"\u002Fwriting\u002F2026-09-20-python-week-2-functions-abstraction-modularization#day-5l2-%E9%87%8D%E6%9E%84%E4%B8%80%E7%9B%AE%E5%BD%95%E6%8B%86%E5%88%86--logging15%E2%80%932h","Day 5｜L2 重构（一）：目录拆分 + logging",[135,5223,5224],{"id":5224},"目录结构",[619,5226,5229],{"className":5227,"code":5228,"language":624},[622],"kv_project\u002F\n├── app\u002F\n│   ├── __init__.py\n│   ├── storage\u002F\n│   │   ├── __init__.py\n│   │   ├── list_store.py     # ListKeyValueStore\n│   │   └── dict_store.py     # DictKeyValueStore\n│   ├── core\u002F\n│   │   ├── __init__.py\n│   │   └── kv_service.py     # KVService\n│   ├── cli.py                # main()，程序入口\n│   └── config.py             # AppConfig + load_config()\n├── tests\u002F\n│   ├── __init__.py\n│   ├── test_list_store.py\n│   └── test_dict_store.py\n├── config.json\n└── .venv\u002F\n",[60,5230,5228],{"__ignoreMap":625},[135,5232,5233],{"id":5233},"四层职责",[264,5235,5236,5249],{},[267,5237,5238],{},[270,5239,5240,5242,5245,5247],{},[273,5241,1228],{},[273,5243,5244],{},"文件",[273,5246,1231],{},[273,5248,281],{},[283,5250,5251,5284,5299,5316],{},[270,5252,5253,5256,5264,5267],{},[288,5254,5255],{},"storage",[288,5257,5258,185,5261],{},[60,5259,5260],{},"list_store.py",[60,5262,5263],{},"dict_store.py",[288,5265,5266],{},"只管数据怎么存、怎么取",[288,5268,5269,185,5272,185,5275,185,5278,185,5281],{},[60,5270,5271],{},"set",[60,5273,5274],{},"get",[60,5276,5277],{},"delete",[60,5279,5280],{},"exists",[60,5282,5283],{},"keys",[270,5285,5286,5288,5293,5296],{},[288,5287,4372],{},[288,5289,5290],{},[60,5291,5292],{},"kv_service.py",[288,5294,5295],{},"业务逻辑，调 storage",[288,5297,5298],{},"容量限制：满了拒绝",[270,5300,5301,5304,5308,5311],{},[288,5302,5303],{},"cli",[288,5305,5306],{},[60,5307,3863],{},[288,5309,5310],{},"用户入口，解析命令，组装一切",[288,5312,5313],{},[60,5314,5315],{},"set name alice",[270,5317,5318,5321,5325,5328],{},[288,5319,5320],{},"config",[288,5322,5323],{},[60,5324,1287],{},[288,5326,5327],{},"读 JSON → AppConfig",[288,5329,5330],{},[60,5331,5332],{},"capacity=100",[115,5334,5335],{},"依赖方向：",[619,5337,5340],{"className":5338,"code":5339,"language":624},[622],"cli → core → storage\nconfig ← 谁需要谁读（实际只有 cli 读）\n",[60,5341,5339],{"__ignoreMap":625},[115,5343,5344,5345,5347,5348,5350,5351,5353,5354,185,5357,5360,5361,863],{},"关键点：",[60,5346,4372],{}," 不读配置（配置值由 ",[60,5349,5303],{}," 读好后传进去）；",[60,5352,4372],{}," 不知道底层是 list 还是 dict，只调 ",[60,5355,5356],{},"store.set",[60,5358,5359],{},"store.get","——这叫",[125,5362,5363],{},"依赖注入",[135,5365,1278],{"id":1278},[115,5367,5368,5370],{},[60,5369,951],{}," 只放值：",[619,5372,5375],{"className":5373,"code":5374,"language":1197,"meta":625,"style":625},"language-json shiki shiki-themes github-light github-dark","{\n  \"storage_backend\": \"dict\",\n  \"capacity\": 100,\n  \"log_level\": \"INFO\"\n}\n",[60,5376,5377,5383,5399,5411,5421],{"__ignoreMap":625},[723,5378,5379],{"class":725,"line":726},[723,5380,5382],{"class":5381},"sVt8B","{\n",[723,5384,5385,5389,5392,5396],{"class":725,"line":732},[723,5386,5388],{"class":5387},"sj4cs","  \"storage_backend\"",[723,5390,5391],{"class":5381},": ",[723,5393,5395],{"class":5394},"sZZnC","\"dict\"",[723,5397,5398],{"class":5381},",\n",[723,5400,5401,5404,5406,5409],{"class":725,"line":34},[723,5402,5403],{"class":5387},"  \"capacity\"",[723,5405,5391],{"class":5381},[723,5407,5408],{"class":5387},"100",[723,5410,5398],{"class":5381},[723,5412,5413,5416,5418],{"class":725,"line":1068},[723,5414,5415],{"class":5387},"  \"log_level\"",[723,5417,5391],{"class":5381},[723,5419,5420],{"class":5394},"\"INFO\"\n",[723,5422,5423],{"class":725,"line":1074},[723,5424,5425],{"class":5381},"}\n",[115,5427,5428,5430],{},[60,5429,1287],{}," 放“怎么读”：",[619,5432,5434],{"className":717,"code":5433,"language":719,"meta":625,"style":625},"import json\nfrom dataclasses import dataclass\n\n\n@dataclass\nclass AppConfig:\n    storage_backend: str = \"dict\"\n    capacity: int = 100\n    log_level: str = \"INFO\"\n\n\ndef load_config(path: str = \"config.json\") -> AppConfig:\n    with open(path, encoding=\"utf-8\") as f:\n        data = json.load(f)\n    return AppConfig(**data)\n",[60,5435,5436,5440,5444,5448,5452,5456,5460,5465,5470,5474,5478,5482,5486,5490,5495],{"__ignoreMap":625},[723,5437,5438],{"class":725,"line":726},[723,5439,1302],{},[723,5441,5442],{"class":725,"line":732},[723,5443,1307],{},[723,5445,5446],{"class":725,"line":34},[723,5447,1060],{"emptyLinePlaceholder":30},[723,5449,5450],{"class":725,"line":1068},[723,5451,1060],{"emptyLinePlaceholder":30},[723,5453,5454],{"class":725,"line":1074},[723,5455,1316],{},[723,5457,5458],{"class":725,"line":1080},[723,5459,1321],{},[723,5461,5462],{"class":725,"line":1086},[723,5463,5464],{},"    storage_backend: str = \"dict\"\n",[723,5466,5467],{"class":725,"line":1092},[723,5468,5469],{},"    capacity: int = 100\n",[723,5471,5472],{"class":725,"line":1098},[723,5473,1331],{},[723,5475,5476],{"class":725,"line":1104},[723,5477,1060],{"emptyLinePlaceholder":30},[723,5479,5480],{"class":725,"line":1110},[723,5481,1060],{"emptyLinePlaceholder":30},[723,5483,5484],{"class":725,"line":1116},[723,5485,1340],{},[723,5487,5488],{"class":725,"line":1122},[723,5489,1345],{},[723,5491,5492],{"class":725,"line":1128},[723,5493,5494],{},"        data = json.load(f)\n",[723,5496,5497],{"class":725,"line":1133},[723,5498,5499],{},"    return AppConfig(**data)\n",[115,5501,5502,5503,5505,5506,5505,5509,5505,5512,5505,5515,5518],{},"转换链路：",[60,5504,951],{}," → ",[60,5507,5508],{},"json.load()",[60,5510,5511],{},"dict",[60,5513,5514],{},"AppConfig(**data)",[60,5516,5517],{},"AppConfig"," 实例。",[135,5520,1453],{"id":1453},[619,5522,5524],{"className":717,"code":5523,"language":719,"meta":625,"style":625},"# 1. 每个模块顶部\nimport logging\nlogger = logging.getLogger(__name__)\n\n# 2. 打日志\nlogger.debug(\"细节\")\nlogger.info(\"正常流程\")\nlogger.warning(\"警告\")\nlogger.error(\"出错\")\n\n# 3. 入口只配一次\nlogging.basicConfig(\n    level=logging.INFO,\n    format=\"%(asctime)s %(name)s %(levelname)s %(message)s\",\n)\n",[60,5525,5526,5531,5535,5539,5543,5548,5553,5558,5563,5568,5572,5577,5581,5585,5589],{"__ignoreMap":625},[723,5527,5528],{"class":725,"line":726},[723,5529,5530],{},"# 1. 每个模块顶部\n",[723,5532,5533],{"class":725,"line":732},[723,5534,1371],{},[723,5536,5537],{"class":725,"line":34},[723,5538,1376],{},[723,5540,5541],{"class":725,"line":1068},[723,5542,1060],{"emptyLinePlaceholder":30},[723,5544,5545],{"class":725,"line":1074},[723,5546,5547],{},"# 2. 打日志\n",[723,5549,5550],{"class":725,"line":1080},[723,5551,5552],{},"logger.debug(\"细节\")\n",[723,5554,5555],{"class":725,"line":1086},[723,5556,5557],{},"logger.info(\"正常流程\")\n",[723,5559,5560],{"class":725,"line":1092},[723,5561,5562],{},"logger.warning(\"警告\")\n",[723,5564,5565],{"class":725,"line":1098},[723,5566,5567],{},"logger.error(\"出错\")\n",[723,5569,5570],{"class":725,"line":1104},[723,5571,1060],{"emptyLinePlaceholder":30},[723,5573,5574],{"class":725,"line":1110},[723,5575,5576],{},"# 3. 入口只配一次\n",[723,5578,5579],{"class":725,"line":1116},[723,5580,1390],{},[723,5582,5583],{"class":725,"line":1122},[723,5584,1395],{},[723,5586,5587],{"class":725,"line":1128},[723,5588,1400],{},[723,5590,5591],{"class":725,"line":1133},[723,5592,1405],{},[115,5594,5595,5596,863],{},"日志级别：",[60,5597,5598],{},"DEBUG \u003C INFO \u003C WARNING \u003C ERROR \u003C CRITICAL",[115,5600,5601],{},[125,5602,5603,5604,5607,5608,1412,5610,863],{},"basicConfig 铁律：",[60,5605,5606],{},"basicConfig()"," 只能由应用入口调一次。底层模块只 ",[60,5609,1411],{},[60,5611,968],{},[115,5613,5614],{},[125,5615,5616,5617,5619,5620],{},"实验 A：在 ",[60,5618,5263],{}," 里也加 ",[60,5621,5622],{},"basicConfig(STORAGE: ...)",[115,5624,5625,5626,5629,5630,5633,5634,5636,5637,5640,5641,5644,5645,5647,5648,4947,5651,5653,5654,5656,5657,2839,5660,5662],{},"结果格式变成 ",[60,5627,5628],{},"STORAGE: ...","、级别变成 ",[60,5631,5632],{},"DEBUG","，连 ",[60,5635,3863],{}," 的日志也被改成 ",[60,5638,5639],{},"STORAGE:"," 格式。原因：",[60,5642,5643],{},"python -m app.cli"," 先从 ",[60,5646,3863],{}," 顶部 ",[60,5649,5650],{},"import app.storage.dict_store",[60,5652,5263],{}," 的 ",[60,5655,968],{}," 抢先生效，",[60,5658,5659],{},"main()",[60,5661,968],{}," 被静默忽略。",[115,5664,5665,5670,5671,5673],{},[125,5666,5667,5668],{},"实验 B：删掉 storage 里的 ",[60,5669,968],{}," —— 恢复成 ",[60,5672,3863],{}," 配的格式。",[115,5675,5676],{},"结论：谁先被 import，谁就决定了全局日志格式和级别；入口想统一配置，就会被架空。",[135,5678,5679],{"id":5679},"测试",[619,5681,5685],{"className":5682,"code":5683,"language":5684,"meta":625,"style":625},"language-bash shiki shiki-themes github-light github-dark","uv run pytest -v\n","bash",[60,5686,5687],{"__ignoreMap":625},[723,5688,5689,5693,5696,5699],{"class":725,"line":726},[723,5690,5692],{"class":5691},"sScJk","uv",[723,5694,5695],{"class":5394}," run",[723,5697,5698],{"class":5394}," pytest",[723,5700,5701],{"class":5387}," -v\n",[115,5703,5704,5705,5708],{},"结果：",[60,5706,5707],{},"10 passed in 0.02s","。重构没有改变原有行为。",[130,5710,5712],{"id":5711},"day-6-成果l2-重构dataclass-与-type-hints","Day 6 成果｜L2 重构：dataclass 与 type hints",[115,5714,5715,181,5717],{},[125,5716,2659],{},[119,5718,5720],{"href":5719},"\u002Fwriting\u002F2026-09-20-python-week-2-functions-abstraction-modularization#day-6l2-%E9%87%8D%E6%9E%84%E4%BA%8Cdataclass-%E4%B8%8E-type-hints15%E2%80%932h","Day 6｜L2 重构（二）：dataclass 与 type hints",[135,5722,5724,5726],{"id":5723},"dataclass-是什么",[60,5725,1291],{}," 是什么",[115,5728,5729,5730,5732],{},"标准库 ",[60,5731,1463],{}," 里的装饰器，自动为“数据类”生成样板方法。",[619,5734,5736],{"className":717,"code":5735,"language":719,"meta":625,"style":625},"from dataclasses import dataclass, field\n\n@dataclass\nclass Record:\n    id: int\n    name: str\n    tags: list[str] = field(default_factory=list)\n    created_at: str = \"\"\n",[60,5737,5738,5743,5747,5751,5756,5761,5766,5771],{"__ignoreMap":625},[723,5739,5740],{"class":725,"line":726},[723,5741,5742],{},"from dataclasses import dataclass, field\n",[723,5744,5745],{"class":725,"line":732},[723,5746,1060],{"emptyLinePlaceholder":30},[723,5748,5749],{"class":725,"line":34},[723,5750,1316],{},[723,5752,5753],{"class":725,"line":1068},[723,5754,5755],{},"class Record:\n",[723,5757,5758],{"class":725,"line":1074},[723,5759,5760],{},"    id: int\n",[723,5762,5763],{"class":725,"line":1080},[723,5764,5765],{},"    name: str\n",[723,5767,5768],{"class":725,"line":1086},[723,5769,5770],{},"    tags: list[str] = field(default_factory=list)\n",[723,5772,5773],{"class":725,"line":1092},[723,5774,5775],{},"    created_at: str = \"\"\n",[264,5777,5778,5788],{},[267,5779,5780],{},[270,5781,5782,5785],{},[273,5783,5784],{},"方法",[273,5786,5787],{},"何时生成",[283,5789,5790,5800,5809,5818],{},[270,5791,5792,5797],{},[288,5793,5794],{},[60,5795,5796],{},"__init__",[288,5798,5799],{},"默认",[270,5801,5802,5807],{},[288,5803,5804],{},[60,5805,5806],{},"__repr__",[288,5808,5799],{},[270,5810,5811,5816],{},[288,5812,5813],{},[60,5814,5815],{},"__eq__",[288,5817,5799],{},[270,5819,5820,5828],{},[288,5821,5822,185,5825],{},[60,5823,5824],{},"__setattr__",[60,5826,5827],{},"__delattr__",[288,5829,5830,5831,5834],{},"仅 ",[60,5832,5833],{},"frozen=True"," 时",[115,5836,5837,5838,5840,5841,5844,5845,5847],{},"原理：",[60,5839,1291],{}," 读类里的类型标注（存在 ",[60,5842,5843],{},"__annotations__","），据此生成 ",[60,5846,5796],{}," 等方法。",[135,5849,5850],{"id":5850},"默认值规则",[264,5852,5853,5861],{},[267,5854,5855],{},[270,5856,5857,5859],{},[273,5858,275],{},[273,5860,3891],{},[283,5862,5863,5873],{},[270,5864,5865,5868],{},[288,5866,5867],{},"不可变（int \u002F str \u002F bool）",[288,5869,5870],{},[60,5871,5872],{},"count: int = 0",[270,5874,5875,5878],{},[288,5876,5877],{},"可变（list \u002F dict \u002F set）",[288,5879,5880],{},[60,5881,5882],{},"tags: list[str] = field(default_factory=list)",[115,5884,5885],{},"原因：可变默认值会被所有实例共享，和 Day 1 的函数默认参数是同一个坑。",[135,5887,5889,1471,5891],{"id":5888},"frozentrue-与-__post_init__",[60,5890,5833],{},[60,5892,5893],{},"__post_init__",[619,5895,5897],{"className":717,"code":5896,"language":719,"meta":625,"style":625},"@dataclass(frozen=True)\nclass Item:\n    name: str\n",[60,5898,5899,5904,5909],{"__ignoreMap":625},[723,5900,5901],{"class":725,"line":726},[723,5902,5903],{},"@dataclass(frozen=True)\n",[723,5905,5906],{"class":725,"line":732},[723,5907,5908],{},"class Item:\n",[723,5910,5911],{"class":725,"line":34},[723,5912,5765],{},[140,5914,5915,5921],{},[143,5916,5917,5918],{},"实例创建后字段不可改，赋值抛 ",[60,5919,5920],{},"FrozenInstanceError",[143,5922,5923,5925,5926,5928],{},[60,5924,5893],{}," 在 ",[60,5927,5796],{}," 执行完后自动调用，用来做数据校验：",[619,5930,5932],{"className":717,"code":5931,"language":719,"meta":625,"style":625},"def __post_init__(self):\n    if self.price \u003C 0:\n        raise ValueError(\"price 不能为负数\")\n",[60,5933,5934,5939,5944],{"__ignoreMap":625},[723,5935,5936],{"class":725,"line":726},[723,5937,5938],{},"def __post_init__(self):\n",[723,5940,5941],{"class":725,"line":732},[723,5942,5943],{},"    if self.price \u003C 0:\n",[723,5945,5946],{"class":725,"line":34},[723,5947,5948],{},"        raise ValueError(\"price 不能为负数\")\n",[135,5950,5951],{"id":5951},"类型标注",[115,5953,5954],{},"运行时不做检查，只是“说明”，给编辑器、mypy、AI、未来的你看。",[264,5956,5957,5966],{},[267,5958,5959],{},[270,5960,5961,5963],{},[273,5962,3891],{},[273,5964,5965],{},"意思",[283,5967,5968,5984,5994,6004,6014,6027],{},[270,5969,5970,5981],{},[288,5971,5972,185,5975,185,5978],{},[60,5973,5974],{},"int",[60,5976,5977],{},"str",[60,5979,5980],{},"bool",[288,5982,5983],{},"基本类型",[270,5985,5986,5991],{},[288,5987,5988],{},[60,5989,5990],{},"list[int]",[288,5992,5993],{},"整数列表",[270,5995,5996,6001],{},[288,5997,5998],{},[60,5999,6000],{},"dict[str, int]",[288,6002,6003],{},"键 str、值 int",[270,6005,6006,6011],{},[288,6007,6008],{},[60,6009,6010],{},"Optional[int]",[288,6012,6013],{},"int 或 None",[270,6015,6016,6021],{},[288,6017,6018],{},[60,6019,6020],{},"Union[int, str]",[288,6022,6023,6024,1588],{},"int 或 str（3.10+ 可写 ",[60,6025,6026],{},"int | str",[270,6028,6029,6034],{},[288,6030,6031],{},[60,6032,6033],{},"Callable[[int], str]",[288,6035,6036],{},"接收 int、返回 str 的函数",[115,6038,6039],{},"用在哪：",[619,6041,6043],{"className":717,"code":6042,"language":719,"meta":625,"style":625},"x: Optional[int] = None            # 变量\ndef f(a: int, b: str) -> bool: ... # 参数 + 返回值\ndef g() -> Optional[Record]: ...   # 返回值可能是 None\nname: Optional[str] = None         # dataclass 字段\n",[60,6044,6045,6050,6055,6060],{"__ignoreMap":625},[723,6046,6047],{"class":725,"line":726},[723,6048,6049],{},"x: Optional[int] = None            # 变量\n",[723,6051,6052],{"class":725,"line":732},[723,6053,6054],{},"def f(a: int, b: str) -> bool: ... # 参数 + 返回值\n",[723,6056,6057],{"class":725,"line":34},[723,6058,6059],{},"def g() -> Optional[Record]: ...   # 返回值可能是 None\n",[723,6061,6062],{"class":725,"line":1068},[723,6063,6064],{},"name: Optional[str] = None         # dataclass 字段\n",[135,6066,6067],{"id":6067},"mypy",[115,6069,6070,6071,6074],{},"静态类型检查工具，运行前检查，不执行代码。能查参数 \u002F 返回值类型不对、",[60,6072,6073],{},"Optional"," 没判空就用、访问不存在的属性等；不能查具体值是否合理、业务逻辑错误。",[115,6076,6077],{},"本周为 storage、core 的公开函数补全类型标注后运行 mypy 作为重构的验证手段。",[130,6079,741],{"id":741},[140,6081,6082,6090,6097,6107,6113,6120],{},[143,6083,6084,6089],{},[119,6085,6088],{"href":6086,"rel":6087},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Findex.html",[750],"Python Tutorial","（函数、模块、包）",[143,6091,6092],{},[119,6093,6096],{"href":6094,"rel":6095},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fclasses.html#scopes-and-namespaces-example",[750],"Scopes and Namespaces",[143,6098,6099,1471,6102],{},[119,6100,1463],{"href":1459,"rel":6101},[750],[119,6103,6106],{"href":6104,"rel":6105},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Ftyping.html",[750],"typing",[143,6108,6109],{},[119,6110,6112],{"href":1448,"rel":6111},[750],"logging HOWTO",[143,6114,6115],{},[119,6116,6119],{"href":6117,"rel":6118},"https:\u002F\u002Fmypy.readthedocs.io\u002Fen\u002Fstable\u002Findex.html",[750],"mypy 文档",[143,6121,6122],{},[119,6123,6125],{"href":772,"rel":6124},[750],"pytest 文档",[790,6127,6128],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":625,"searchDepth":732,"depth":732,"links":6130},[6131,6136,6137,6138,6139,6145,6146,6151,6152,6153,6154,6159,6174,6178,6179,6186,6195],{"id":132,"depth":732,"text":133,"children":6132},[6133,6134,6135],{"id":137,"depth":34,"text":138},{"id":148,"depth":34,"text":149},{"id":157,"depth":34,"text":158},{"id":166,"depth":732,"text":167},{"id":230,"depth":732,"text":231},{"id":324,"depth":732,"text":325},{"id":369,"depth":732,"text":370,"children":6140},[6141,6142,6143,6144],{"id":425,"depth":34,"text":426},{"id":369,"depth":34,"text":434},{"id":442,"depth":34,"text":443},{"id":451,"depth":34,"text":452},{"id":459,"depth":732,"text":460},{"id":474,"depth":732,"text":475,"children":6147},[6148,6149,6150],{"id":481,"depth":34,"text":138},{"id":493,"depth":34,"text":149},{"id":511,"depth":34,"text":158},{"id":519,"depth":732,"text":520},{"id":529,"depth":732,"text":530},{"id":538,"depth":732,"text":539},{"id":3870,"depth":732,"text":3871,"children":6155},[6156,6157,6158],{"id":3882,"depth":34,"text":3882},{"id":3991,"depth":34,"text":3991},{"id":4118,"depth":34,"text":4118},{"id":4150,"depth":732,"text":4151,"children":6160},[6161,6162,6163,6165,6167,6169,6170,6171,6173],{"id":4162,"depth":34,"text":4163},{"id":4255,"depth":34,"text":4256},{"id":4414,"depth":34,"text":6164},"__name__ 与 main 守卫",{"id":4503,"depth":34,"text":6166},"python file.py vs python -m pkg.mod",{"id":4594,"depth":34,"text":6168},"__init__.py 与 re-export",{"id":4637,"depth":34,"text":4638},{"id":4670,"depth":34,"text":4670},{"id":4683,"depth":34,"text":6172},"重点实验：from x import y 到底绑定了什么？",{"id":4784,"depth":34,"text":4784},{"id":4827,"depth":732,"text":4828,"children":6175},[6176,6177],{"id":4959,"depth":34,"text":4960},{"id":5026,"depth":34,"text":5027},{"id":5071,"depth":732,"text":5072},{"id":5212,"depth":732,"text":5213,"children":6180},[6181,6182,6183,6184,6185],{"id":5224,"depth":34,"text":5224},{"id":5233,"depth":34,"text":5233},{"id":1278,"depth":34,"text":1278},{"id":1453,"depth":34,"text":1453},{"id":5679,"depth":34,"text":5679},{"id":5711,"depth":732,"text":5712,"children":6187},[6188,6190,6191,6193,6194],{"id":5723,"depth":34,"text":6189},"@dataclass 是什么",{"id":5850,"depth":34,"text":5850},{"id":5888,"depth":34,"text":6192},"frozen=True 与 __post_init__",{"id":5951,"depth":34,"text":5951},{"id":6067,"depth":34,"text":6067},{"id":741,"depth":732,"text":741},"2026-09-21","Week 2 的实际产出：Day 1 参数组合与闭包、Day 2 自定义 module 与 import 机制、Day 3 闭包与作用域深化、Day 4 decorator 简化版、Day 5–6 项目重构（目录拆分、logging、dataclass、type hints）的完整总结与实验记录。",{},"\u002Fwriting\u002F2026-09-21-python-week-2-outcomes-modules-exceptions","2026-09-21T14:09:50+08:00",{"title":3531,"description":6197},"python-week-2-outcomes-modules-exceptions","writing\u002F2026-09-21-python-week-2-outcomes-modules-exceptions",[837,6205,6206,5184,6207,6208,4676,6209,6210,5951,1453,1527],"Week2","函数","decorator","模块","重构","dataclass","week-2","jqyGA7DubAhi6Ww5B09U-HgIh5hReO1XaGbLLu68BDo",{"id":6214,"title":6215,"body":6216,"category":6797,"date":6798,"description":6799,"extension":827,"hidden":828,"kind":834,"meta":6800,"navigation":30,"path":6801,"publishedAt":6802,"seo":6803,"series":834,"slug":6804,"stem":6805,"tags":6806,"unit":834,"updated":834,"__hash__":6813},"writing\u002Fwriting\u002F2026-09-20-i-never-learned-how-to-build-relationships.md","我好像一直没学会怎么和人建立关系",{"type":112,"value":6217,"toc":6778},[6218,6221,6224,6227,6230,6233,6236,6239,6242,6245,6248,6251,6254,6257,6260,6263,6266,6271,6274,6278,6281,6284,6287,6290,6293,6296,6299,6305,6308,6314,6318,6321,6324,6327,6330,6335,6338,6344,6347,6350,6353,6356,6362,6365,6368,6371,6374,6377,6380,6383,6389,6393,6396,6399,6402,6405,6408,6411,6414,6417,6423,6426,6429,6432,6435,6438,6441,6461,6464,6467,6471,6474,6477,6480,6483,6486,6489,6493,6496,6499,6502,6508,6511,6514,6517,6520,6523,6526,6529,6532,6535,6538,6541,6544,6547,6553,6557,6560,6563,6566,6569,6572,6575,6579,6582,6585,6588,6591,6594,6597,6601,6604,6610,6613,6616,6620,6623,6626,6629,6632,6635,6640,6643,6646,6649,6654,6657,6660,6663,6669,6672,6675,6678,6681,6684,6687,6690,6693,6696,6700,6703,6706,6709,6712,6715,6718,6721,6724,6727,6730,6736,6739,6742,6745,6751,6754,6757,6760,6763,6766,6769,6772,6775],[115,6219,6220],{},"最近和 ChatGPT 聊了很久关于\"人为什么会被一些事情伤害\"这件事。",[115,6222,6223],{},"一开始其实只是一个非常小的问题：为什么游戏里一个完全不认识的人骂我一句，我明明知道这件事情没有任何现实意义，事后却还是会想很久？",[115,6225,6226],{},"甚至会反复想：\"我当时为什么没骂回去？\"",[115,6228,6229],{},"事情明明已经结束了，大脑却还在继续。",[115,6231,6232],{},"然后我又想到，现实里好像也一样。",[115,6234,6235],{},"一句话、一次拒绝、一次失败、一次争吵，甚至一段关系的结束，都可能让一个人很长时间走不出来。",[115,6237,6238],{},"于是我开始不断追问。",[130,6240,6241],{"id":6241},"从一句话为什么会伤人开始",[115,6243,6244],{},"最开始讨论的时候，我慢慢意识到，人并不是一个简单的\"发生事情 → 理性分析 → 得出结论 → 翻篇\"的机器。",[115,6246,6247],{},"事情本身有多大，并不完全决定它会带来多大的情绪。真正重要的，往往是这件事情在自己心里意味着什么。",[115,6249,6250],{},"别人骂我一句，表面上只是一句话。但它可能让我觉得：他是不是看不起我？我是不是显得很弱？我为什么没有反击？是不是我真的不够好？",[115,6252,6253],{},"于是，真正让我难受的可能已经不是那句话，而是它触碰到了我的自我价值和尊严。",[115,6255,6256],{},"这也让我理解了为什么有些人经历一次失败以后还能继续，而连续失败却可能让一个人彻底崩掉。",[115,6258,6259],{},"一次失败可以被理解成\"这次没做好\"，但反复失败以后，人可能开始把事情解释成\"是不是我这个人就不行？\"",[115,6261,6262],{},"这两句话之间，差别非常大。",[115,6264,6265],{},"所以后来我觉得，所谓韧性，并不是一个人永远不会被伤害，而是：",[115,6267,6268],{},[125,6269,6270],{},"允许事情刺痛自己，但不允许事情定义自己。",[115,6272,6273],{},"真正重要的可能不是\"我有没有碎掉\"，而是碎了以后，能不能一点一点把自己捡回来。承认自己的价值，认识自己的能力，也接受自己的不足。",[130,6275,6277],{"id":6276},"然后我开始问自尊到底是什么","然后我开始问：自尊到底是什么？",[115,6279,6280],{},"这就自然走到了\"自尊\"。",[115,6282,6283],{},"以前我总觉得自尊就是面子，但继续聊下去以后，我发现它好像比面子复杂得多。",[115,6285,6286],{},"自尊更像是一个人对于自己的基本判断：我是谁？我应该被怎样对待？哪些事情别人不能随便对我做？",[115,6288,6289],{},"于是我开始意识到，人与人交往的时候，其实到处都是边界：身体有边界、隐私有边界、说话有边界、情绪有边界、人格也有边界。",[115,6291,6292],{},"比如别人说\"你这件事情做得不好\"，这可能只是对能力和行为的评价。",[115,6294,6295],{},"但如果变成\"你这个人就是废物\"，性质就变了。",[115,6297,6298],{},"前者是在讨论一件事情，后者是在否定一个人。",[115,6300,6301,6302],{},"这让我开始理解一个很重要的区别：",[125,6303,6304],{},"不同意一个人，不等于否定这个人。",[115,6306,6307],{},"别人可以反驳我，可以不喜欢我，可以觉得我的观点很差，但这和羞辱、侵犯、贬低不是一回事。",[115,6309,6310,6311],{},"于是一个很简单的原则慢慢出现了：",[125,6312,6313],{},"尊重别人的边界，也守住自己的边界。",[130,6315,6317],{"id":6316},"可是奇怪的是人为什么一边需要边界一边又渴望亲密","可是奇怪的是，人为什么一边需要边界，一边又渴望亲密？",[115,6319,6320],{},"这是后来让我觉得特别有意思的问题。",[115,6322,6323],{},"面对陌生人，我们希望保护自己。可是面对真正亲近的人，我们又希望对方能够进入自己的内心。",[115,6325,6326],{},"我们希望被理解，但又害怕真的被看见。希望有人知道真实的自己，又担心自己的脆弱有一天会被拿来伤害自己。",[115,6328,6329],{},"这看起来像一个矛盾，但讨论到后来，我觉得它其实不是矛盾。因为：",[115,6331,6332],{},[125,6333,6334],{},"亲密并不意味着边界消失，更像是：边界变薄，而不是边界消失。",[115,6336,6337],{},"如果把自己想象成一栋房子：陌生人只能站在门外，普通朋友可以进入客厅，关系更近的人可以看到更多房间，而真正亲密的人，可能会看到自己最脆弱、最不愿意展示的地方。",[115,6339,6340,6341],{},"但关键是：",[125,6342,6343],{},"我愿意为你开门。",[115,6345,6346],{},"而不是：\"因为我们关系好，所以你可以随便闯进来。\"",[115,6348,6349],{},"所以真正健康的亲密关系，应该是两个完整的人，在保留自己的情况下，主动向彼此开放。",[130,6351,6352],{"id":6352},"然后我发现了自己的一个习惯",[115,6354,6355],{},"我突然想到自己平时和别人相处的方式。",[115,6357,6358,6359],{},"我其实不太知道应该怎么主动建立关系。我的一个很自然的原则一直是：",[125,6360,6361],{},"没人问，我就不说。",[115,6363,6364],{},"别人问我什么，我回答。别人不问，我就不主动讲自己的事情。",[115,6366,6367],{},"这其实挺安全的，因为别人不知道我的事情，也就很难伤害我。",[115,6369,6370],{},"但是聊到这里以后，我发现了一个问题：如果两个人都这样——你不问，我不说——那么关系很容易永远停留在表面。",[115,6372,6373],{},"因为亲密关系需要的不只是回答问题，还需要一点点主动暴露自己。",[115,6375,6376],{},"比如\"我今天其实心情不太好\"，或者\"最近工作有点无聊\"，甚至只是\"我最近在学 Python，但是感觉这玩意儿比想象中折磨人\"。",[115,6378,6379],{},"这种分享其实没有多严重，但它是在告诉对方：这里有一点真实的我。",[115,6381,6382],{},"然后看对方会不会接住。如果对方愿意回应，那就可以再多打开一点。如果对方敷衍、嘲笑，甚至利用这个弱点攻击自己，那就退回来。",[115,6384,6385,6386],{},"于是我开始理解所谓的\"试探\"其实不是故意攻击别人的边界，真正的试探是：",[125,6387,6388],{},"给出一点自己，然后看对方怎么对待它。",[130,6390,6392],{"id":6391},"可我又发现好像很多时候是我更在乎别人","可我又发现，好像很多时候是我更在乎别人",[115,6394,6395],{},"这又产生了另一个问题。",[115,6397,6398],{},"有时候我会觉得：\"为什么我对别人好像比别人对我更在意？\"",[115,6400,6401],{},"我会记得别人说过的话，会注意一些细节，也会在意朋友最近过得怎么样。但别人好像并没有那么在乎我。",[115,6403,6404],{},"一开始很容易把它理解成：\"他们是不是根本不在乎我？\"",[115,6406,6407],{},"可是继续想以后，发现还存在另一种可能：也许我根本没有给别人足够多了解我的机会。",[115,6409,6410],{},"因为我很少主动说自己的事情。别人甚至不知道我在乎什么，不知道我最近经历了什么，也不知道我把他们看得多重要。",[115,6412,6413],{},"所以\"别人不在乎我\"和\"我很少让别人有机会了解我\"，其实是两件完全不同的事情。",[115,6415,6416],{},"当然，人与人的投入程度本来就可能不一样。两个人可以都把对方当朋友，但心理上的重要程度完全不同。这很正常。",[115,6418,6419,6420],{},"所以可能真正需要学习的，不是要求所有关系都绝对对等，而是：",[125,6421,6422],{},"根据实际的反馈调整自己的投入。",[130,6424,6425],{"id":6425},"但人际关系好像根本没有一套精确公式",[115,6427,6428],{},"聊到这里，我开始有点焦虑。",[115,6430,6431],{},"因为我很想知道：到底什么程度才算朋友？主动多少次算正常？什么话算越界？什么时候应该更进一步？对方多久不联系我，才说明他不在乎我？",[115,6433,6434],{},"但最后发现，这些东西根本没有精确答案。人与人之间没有数学公式。",[115,6436,6437],{},"不存在\"主动三次 = 对方重视你\"，也不存在\"聊到某个程度 = 可以成为朋友\"。",[115,6439,6440],{},"最后只能留下几条很粗的原则：",[140,6442,6443,6446,6449,6452,6455,6458],{},[143,6444,6445],{},"尊重自己，也尊重别人。",[143,6447,6448],{},"亲密是逐渐增加的。",[143,6450,6451],{},"给一点自己，观察对方如何回应。",[143,6453,6454],{},"关系可以不完全对等，但不能长期只有一方投入。",[143,6456,6457],{},"别人的拒绝不代表自己的价值下降。",[143,6459,6460],{},"自己做错了可以修复，但不是所有关系都必须留下。",[115,6462,6463],{},"这些东西更像坐标系，而不是答案。",[115,6465,6466],{},"所谓\"分寸感\"，可能本来就不是读书读出来的，而是在一次次真实互动里慢慢长出来的。",[130,6468,6470],{"id":6469},"网络上为什么这么容易现实里为什么这么难","网络上为什么这么容易，现实里为什么这么难？",[115,6472,6473],{},"后来我又想到一个特别有意思的事情。",[115,6475,6476],{},"网上其实很容易试探陌生人的边界。你可以不断说一些更过分的话，然后观察对方什么时候生气。",[115,6478,6479],{},"因为网络互动的成本太低了：不知道对方是谁，以后也不一定再见，说错了，大不了关掉聊天窗口。",[115,6481,6482],{},"但现实不一样。现实里的一个人可能明天还会见到，可能有共同朋友，可能在同一个活动里，甚至可能慢慢变成自己的朋友。",[115,6484,6485],{},"所以现实社交的成本更高，也正因为如此，现实关系的建立速度更慢。",[115,6487,6488],{},"但它也因此更加真实。网络上的互动可能几分钟就完成一次边界测试，现实关系可能需要几周、几个月，甚至几年。",[130,6490,6492],{"id":6491},"于是问题变成了现实中到底怎么认识陌生人","于是问题变成了：现实中到底怎么认识陌生人？",[115,6494,6495],{},"这是后来最实际的问题。",[115,6497,6498],{},"我发现，单纯说\"去认识陌生人\"其实没有意义，因为马上就会遇到一个问题：我为什么要和这个陌生人说话？",[115,6500,6501],{},"如果两个人只是站在街上，谁也不认识谁，没有共同活动，也没有共同目标，主动开口确实很困难。",[115,6503,6504,6505],{},"所以真正合理的路径可能不是\"陌生人 → 直接认识\"，而是：",[125,6506,6507],{},"先找到一个圈子。",[115,6509,6510],{},"这个圈子可以是：羽毛球、足球、跑团、徒步、桌游、音乐、摄影、DIY、编程 \u002F AI、读书会、志愿活动、固定课程。",[115,6512,6513],{},"重点不是活动本身有多高级，而是：大家有一个共同理由出现在这里。",[115,6515,6516],{},"于是\"为什么跟你说话\"这个问题自然消失了。",[130,6518,6519],{"id":6519},"关系原来是这样慢慢长出来的",[115,6521,6522],{},"第一次见面，可能只是：\"你好，你也第一次来吗？\"",[115,6524,6525],{},"第二次：\"诶，又来了。\"",[115,6527,6528],{},"第三次：\"你平时周几来？\"",[115,6530,6531],{},"第四次：\"下周还来吗？\"",[115,6533,6534],{},"第五次：\"下次一起？\"",[115,6536,6537],{},"再往后：\"结束以后一起吃个饭？\"",[115,6539,6540],{},"原来真正的关系并不是\"陌生人 → 好朋友\"，而是：",[115,6542,6543],{},"陌生人 → 熟面孔 → 认识 → 熟悉 → 一起做事情 → 朋友 → 更亲近。",[115,6545,6546],{},"这让我觉得，过去可能一直把\"交朋友\"想得太重了。",[115,6548,6549,6550],{},"第一次见面根本不需要决定\"这个人能不能成为我的朋友？\"，只需要：",[125,6551,6552],{},"让下一次见面发生。",[130,6554,6556],{"id":6555},"可是我现在独居也没什么爱好","可是我现在独居，也没什么爱好",[115,6558,6559],{},"到这里又碰到了最现实的问题。",[115,6561,6562],{},"我现在独居，下班以后基本就是自己的生活，而且也没有什么特别明确的爱好。",[115,6564,6565],{},"这时候我才意识到：也许问题根本不是\"我不会社交\"，而是我的生活里根本没有多少社交发生的土壤。",[115,6567,6568],{},"如果每天都是：上班 → 回家 → 手机 → 电脑 → 睡觉。",[115,6570,6571],{},"那么即使一个人的社交能力完全正常，也很难凭空认识新的朋友。",[115,6573,6574],{},"所以需要改变的首先不是\"聊天技巧\"，而是生活结构。",[130,6576,6578],{"id":6577},"可羽毛球一个人怎么打呢","可羽毛球一个人怎么打呢？😂",[115,6580,6581],{},"聊到这里甚至出现了一个很现实的问题：羽毛球一个人怎么打？😂",[115,6583,6584],{},"确实。这也暴露出了前面思考里的一个漏洞。",[115,6586,6587],{},"我们真正需要寻找的不是\"一个人也可以完成的爱好\"，而是\"一个人可以自然进入一群人的活动\"。",[115,6589,6590],{},"所以羽毛球可以通过新手局、拼场、固定约球、初学课程进入。",[115,6592,6593],{},"如果连陌生人拼场都觉得压力很大，那就先选择课程型活动。一个人报名，老师组织，大家一起练，慢慢认识旁边的人。",[115,6595,6596],{},"这样心理压力会低很多。",[130,6598,6600],{"id":6599},"所以我到底有没有必要先找到真正的爱好","所以我到底有没有必要先找到\"真正的爱好\"？",[115,6602,6603],{},"后来我觉得没有。",[115,6605,6606,6607],{},"不需要先找到一个\"我此生热爱的活动\"，只需要问自己：",[125,6608,6609],{},"什么事情我愿意每周出去做两个小时？",[115,6611,6612],{},"第一次觉得一般，很正常。第三次可能觉得还行。十次以后，也许就真的产生兴趣了。",[115,6614,6615],{},"甚至有可能：爱好、朋友和生活圈是一起长出来的。",[130,6617,6619],{"id":6618},"真正困难的可能只是第一步","真正困难的，可能只是第一步",[115,6621,6622],{},"可是即便所有道理都明白了，第一步还是很难。",[115,6624,6625],{},"因为第一步里面包含太多不确定性：我要不要去？去了会不会尴尬？会不会一个人站在那里？别人会不会不理我？我该说什么？如果不好玩怎么办？",[115,6627,6628],{},"于是大脑会非常自然地告诉自己：\"算了，今天先不去。\"",[115,6630,6631],{},"然后回家。手机一刷。电脑一开。舒服了。第二天继续。",[115,6633,6634],{},"所以第一步真正需要解决的，不是\"我要马上变成一个会社交的人\"，而是：",[115,6636,6637],{},[125,6638,6639],{},"我要让自己第一次离开原来的生活轨道。",[130,6641,6642],{"id":6642},"第一课甚至可以简单到什么都不发生",[115,6644,6645],{},"所以最后我给自己设想的目标已经非常低了。",[115,6647,6648],{},"不是\"我要认识一个新朋友\"，而是：",[115,6650,6651],{},[125,6652,6653],{},"\"这周去一个以前不会去的地方待一个小时。\"",[115,6655,6656],{},"不要求认识任何人，不要求聊天，不要求交朋友，甚至不要求喜欢那个地方。",[115,6658,6659],{},"只要求：去。",[115,6661,6662],{},"如果去了，任务就完成了。如果和别人说了两句话，那是额外奖励。",[115,6664,6665,6666],{},"真正训练的，其实是：",[125,6667,6668],{},"从一个人的生活，进入一个有其他人的生活。",[130,6670,6671],{"id":6671},"然后我突然意识到",[115,6673,6674],{},"聊到最后，我对这整件事情有了一个很奇怪但又很准确的感觉：",[115,6676,6677],{},"我的人生好像缺了一课。",[115,6679,6680],{},"不是缺了一门知识，而是缺了一门实践课。",[115,6682,6683],{},"学校教过数学，教过英语，教过专业，后来工作又教了业务。但好像从来没有一门课真正教过：",[115,6685,6686],{},"怎么认识陌生人。怎么和别人建立朋友关系。怎么表达自己的需要。怎么判断人与人的距离。怎么打开自己。怎么接受拒绝。怎么处理冲突。怎么面对一个人慢慢离开自己的生活。",[115,6688,6689],{},"这些东西，很多人可能是在青春期、宿舍、社团、恋爱和朋友关系里，一点一点摔出来的。",[115,6691,6692],{},"而我可能只是没有经历那么多。",[115,6694,6695],{},"所以现在才发现：原来还有这么一门课。",[130,6697,6699],{"id":6698},"这门课现在开始学也不晚","这门课现在开始学，也不晚",[115,6701,6702],{},"而且这门课其实没有标准答案。",[115,6704,6705],{},"它不像数学，没有\"输入 A → 输出 B\"，也没有\"主动三次 → 对方就是你的朋友\"，更没有\"聊到某个程度 → 就可以进入亲密关系\"。",[115,6707,6708],{},"真正能够依靠的，只有一些底层原则：",[115,6710,6711],{},"尊重自己，也尊重别人。边界可以打开，但不能消失。给一点自己，观察对方如何回应。对方愿意靠近，就多走一步；对方不愿意，就停下来。别人的拒绝不意味着自己的价值下降。自己做错了可以修复，但不是所有关系都必须保住。",[115,6713,6714],{},"剩下的东西：靠经历。",[130,6716,6717],{"id":6717},"所以现在不应该继续想明白了",[115,6719,6720],{},"这可能是这次聊天最后真正让我意识到的事情。",[115,6722,6723],{},"我们已经讨论了：自尊、边界、亲密、朋友、投入的不对等、陌生人、网络社交、现实社交、圈子、兴趣、独居、第一步。",[115,6725,6726],{},"如果继续坐在这里分析，还可以继续分析很多。",[115,6728,6729],{},"但分析到这里，已经足够了。因为真正缺的东西已经不是理论，而是实践。",[115,6731,6732,6733],{},"所以接下来可能只需要做一件很小的事情：",[125,6734,6735],{},"去一次。",[115,6737,6738],{},"可能第一次很尴尬。可能一个朋友都没认识。可能回来以后只觉得：\"就这？\"",[115,6740,6741],{},"也没关系。因为这不是考试。",[115,6743,6744],{},"这本身就是课程。",[115,6746,6747,6748],{},"人与人的关系，很多时候不是想明白以后才敢开始，而是：",[125,6749,6750],{},"开始以后，才慢慢想明白。",[115,6752,6753],{},"也许所谓社交能力，也根本不是一种天生的性格。它只是一个人经过足够多次：",[115,6755,6756],{},"认识 → 互动 → 暴露 → 反馈 → 调整 → 再认识",[115,6758,6759],{},"之后，逐渐形成的一种生活能力。",[115,6761,6762],{},"所以如果真的要给自己的人生补上这一课，那么第一课可能非常简单：去一个有人的地方。",[115,6764,6765],{},"第二课：留下来。",[115,6767,6768],{},"第三课：和一个人说一句话。",[115,6770,6771],{},"然后慢慢地，认识一些人，也让一些人有机会认识真正的自己。",[115,6773,6774],{},"这门课没有毕业考试。",[115,6776,6777],{},"但可以从现在开始。",{"title":625,"searchDepth":732,"depth":732,"links":6779},[6780,6781,6782,6783,6784,6785,6786,6787,6788,6789,6790,6791,6792,6793,6794,6795,6796],{"id":6241,"depth":732,"text":6241},{"id":6276,"depth":732,"text":6277},{"id":6316,"depth":732,"text":6317},{"id":6352,"depth":732,"text":6352},{"id":6391,"depth":732,"text":6392},{"id":6425,"depth":732,"text":6425},{"id":6469,"depth":732,"text":6470},{"id":6491,"depth":732,"text":6492},{"id":6519,"depth":732,"text":6519},{"id":6555,"depth":732,"text":6556},{"id":6577,"depth":732,"text":6578},{"id":6599,"depth":732,"text":6600},{"id":6618,"depth":732,"text":6619},{"id":6642,"depth":732,"text":6642},{"id":6671,"depth":732,"text":6671},{"id":6698,"depth":732,"text":6699},{"id":6717,"depth":732,"text":6717},"随笔","2026-09-20","从一次关于情绪和自尊的思考开始，我一步步追问边界、亲密、友谊和现实社交，最后发现自己真正缺少的可能不是社交技巧，而是一次次真实的人际实践。",{},"\u002Fwriting\u002F2026-09-20-i-never-learned-how-to-build-relationships","2026-09-20T02:06:59+08:00",{"title":6215,"description":6799},"i-never-learned-how-to-build-relationships","writing\u002F2026-09-20-i-never-learned-how-to-build-relationships",[6807,6808,6809,6810,6811,6812],"自尊","边界","亲密关系","社交","独居","自我成长","PRb0ToQohkcQbkVsXeOn7F3KrqSja60zkMKQ7Hntblw",{"id":6815,"title":6816,"body":6817,"category":824,"date":6798,"description":7815,"extension":827,"hidden":828,"kind":829,"meta":7816,"navigation":30,"path":3538,"publishedAt":7817,"seo":7818,"series":102,"slug":7819,"stem":7820,"tags":7821,"unit":6211,"updated":825,"__hash__":7822},"writing\u002Fwriting\u002F2026-09-20-python-week-2-functions-abstraction-modularization.md","Week 2 学习计划｜Python 函数、作用域、闭包与模块化",{"type":112,"value":6818,"toc":7779},[6819,6829,6831,6833,6843,6845,6859,6861,6865,6867,6870,6877,6921,6923,6926,6953,6956,6960,6976,6985,6994,7030,7035,7057,7068,7072,7085,7090,7095,7143,7153,7157,7167,7179,7185,7189,7237,7243,7254,7258,7263,7267,7308,7319,7332,7336,7351,7355,7363,7369,7398,7408,7417,7421,7448,7452,7474,7489,7493,7508,7520,7522,7534,7566,7568,7570,7574,7576,7582,7584,7591,7593,7601,7603,7615,7617,7619,7621,7629,7641,7645,7647,7668,7670,7674,7676,7684,7686,7694,7696,7698,7702,7708,7711,7738,7740,7777],[115,6820,117,6821,123,6823,6825,6826,863],{},[119,6822,122],{"href":121},[125,6824,3544],{},"，按总纲 Part II「每周固定执行模板」编排。计划篇只保留任务与要求，实际产出见 ",[119,6827,6828],{"href":6199},"Week 2 学习成果",[130,6830,133],{"id":132},[135,6832,138],{"id":137},[140,6834,6835],{},[143,6836,6837,6838,185,6840,6842],{},"能理解并使用 function \u002F parameter \u002F default argument \u002F ",[60,6839,3577],{},[60,6841,3580],{}," \u002F scope \u002F closure \u002F lambda \u002F import \u002F module \u002F package。",[135,6844,149],{"id":148},[140,6846,6847],{},[143,6848,6849,6850,185,6852,185,6854,185,6856,6858],{},"手写参数组合、闭包、decorator 简化版、自定义 module；并把 Week 1 项目重构为 ",[60,6851,3667],{},[60,6853,3670],{},[60,6855,3673],{},[60,6857,3676],{},"，加入 dataclass、type hints、logging。",[135,6860,158],{"id":157},[140,6862,6863],{},[143,6864,163],{},[130,6866,167],{"id":166},[115,6868,6869],{},"总纲 Week 2 学习内容：",[115,6871,6872,6873,185,6875,6842],{},"function \u002F parameter \u002F default argument \u002F ",[60,6874,3577],{},[60,6876,3580],{},[140,6878,6879,6892,6898,6908],{},[143,6880,6881,6886,6887,185,6889,6891],{},[119,6882,6885],{"href":6883,"rel":6884},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fcontrolflow.html#more-on-defining-functions",[750],"More on Defining Functions","：positional \u002F keyword \u002F default 参数、",[60,6888,3577],{},[60,6890,3580],{},"、lambda",[143,6893,6894,6897],{},[119,6895,6096],{"href":6094,"rel":6896},[750],"：LEGB、global \u002F nonlocal、闭包",[143,6899,6900,6905,6906],{},[119,6901,6904],{"href":6902,"rel":6903},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fmodules.html",[750],"模块与包","：import、module、package、",[60,6907,4417],{},[143,6909,6910,195,6913,195,6916,6920],{},[119,6911,1463],{"href":1459,"rel":6912},[750],[119,6914,6106],{"href":6104,"rel":6915},[750],[119,6917,1453],{"href":6918,"rel":6919},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Flogging.html",[750],"：服务本周的重构任务",[130,6922,231],{"id":230},[115,6924,6925],{},"总纲 Week 2 要求手写：",[140,6927,6929,6935,6941,6947],{"className":6928},[238],[143,6930,6932,6934],{"className":6931},[242],[244,6933],{"disabled":30,"type":246}," 参数组合",[143,6936,6938,6940],{"className":6937},[242],[244,6939],{"disabled":30,"type":246}," 闭包",[143,6942,6944,6946],{"className":6943},[242],[244,6945],{"disabled":30,"type":246}," decorator 简化版",[143,6948,6950,6952],{"className":6949},[242],[244,6951],{"disabled":30,"type":246}," 自定义 module",[115,6954,6955],{},"按日拆解如下（每天 1.5–2h：学习 45min → 实验 45min → 验收 15min）。",[135,6957,6959],{"id":6958},"day-1函数进阶参数作用域闭包152h","Day 1｜函数进阶：参数、作用域、闭包（1.5–2h）",[115,6961,6962,6965,6966,185,6968,6970,6971,185,6973,6975],{},[125,6963,6964],{},"学习 45min","：阅读 More on Defining Functions 与 Scopes and Namespaces，掌握 positional \u002F keyword \u002F default 参数、",[60,6967,3577],{},[60,6969,3580],{},"、可变默认参数陷阱、LEGB、",[60,6972,3587],{},[60,6974,3590],{},"、闭包、函数是一等对象、lambda。",[115,6977,6978,6981,6982,181],{},[125,6979,6980],{},"实验 45min","：创建 ",[60,6983,6984],{},"function_demo.py",[4640,6986,6987,6992],{},[143,6988,6989,6991],{},[125,6990,3882],{},"：positional \u002F keyword \u002F default 三种传参方式",[143,6993,3931],{},[619,6995,6997],{"className":717,"code":6996,"language":719,"meta":625,"style":625},"def append_to(item, target=[]):\n    target.append(item)\n    return target\n\nprint(append_to(1))\nprint(append_to(2))\nprint(append_to(3, []))\n",[60,6998,6999,7003,7007,7011,7015,7020,7025],{"__ignoreMap":625},[723,7000,7001],{"class":725,"line":726},[723,7002,3941],{},[723,7004,7005],{"class":725,"line":732},[723,7006,3946],{},[723,7008,7009],{"class":725,"line":34},[723,7010,3951],{},[723,7012,7013],{"class":725,"line":1068},[723,7014,1060],{"emptyLinePlaceholder":30},[723,7016,7017],{"class":725,"line":1074},[723,7018,7019],{},"print(append_to(1))\n",[723,7021,7022],{"class":725,"line":1080},[723,7023,7024],{},"print(append_to(2))\n",[723,7026,7027],{"class":725,"line":1086},[723,7028,7029],{},"print(append_to(3, []))\n",[115,7031,7032,7033,2575],{},"写出三次输出并解释原因，再用 ",[60,7034,3984],{},[4640,7036,7037,7044,7050],{"start":34},[143,7038,7039,948,7041,7043],{},[125,7040,3991],{},[60,7042,4097],{},"：得到 1、2、3，并验证两个计数器相互独立",[143,7045,7046,7049],{},[125,7047,7048],{},"decorator 简化版","：写一个简易装饰器，包住一个函数，在调用前后打印日志，理解“函数作为参数传入、返回新函数”与闭包的关系",[143,7051,7052,7053,7056],{},"lambda：构造 ",[60,7054,7055],{},"(name, age, score)"," 数据，用 lambda 多字段排序",[115,7058,7059,7062,7063,4814,7065,7067],{},[125,7060,7061],{},"验收 15min","：不用资料解释默认参数何时求值、为什么 list 默认值危险、",[60,7064,3577],{},[60,7066,3580],{}," 有什么区别、闭包捕获的是值还是变量、“函数是对象”意味着哪三件事。",[135,7069,7071],{"id":7070},"day-2自定义-module-与包152h","Day 2｜自定义 module 与包（1.5–2h）",[115,7073,7074,7076,7077,7079,7080,185,7082,7084],{},[125,7075,6964],{},"：阅读 Modules 与 Packages，掌握 module \u002F package \u002F subpackage、三种 import、",[60,7078,4426],{},"、re-export、",[60,7081,3604],{},[60,7083,3600],{},"、循环导入。",[115,7086,7087,7089],{},[125,7088,6980],{},"：创建包结构：",[619,7091,7093],{"className":7092,"code":4242,"language":624},[622],[60,7094,4242],{"__ignoreMap":625},[4640,7096,7097,7107,7121,7133],{},[143,7098,7099,7100,195,7102,195,7104],{},"三种 import：",[60,7101,4351],{},[60,7103,4367],{},[60,7105,7106],{},"from mypkg.core import xxx",[143,7108,7109,7112,7113,7116,7117,7120],{},[125,7110,7111],{},"自定义 module","：在 ",[60,7114,7115],{},"mypkg\u002Fcore.py"," 中编写自己的函数模块，并用 re-export 让 ",[60,7118,7119],{},"from mypkg import xxx"," 可直接使用",[143,7122,7123,7124,1471,7127,7129,7130,7132],{},"模块运行方式：",[60,7125,7126],{},"python core.py",[60,7128,4486],{}," 观察 ",[60,7131,4417],{}," 差异",[143,7134,7135,7136,195,7139,7142],{},"循环导入：人为制造 ",[60,7137,7138],{},"a.py → import b",[60,7140,7141],{},"b.py → import a","，观察错误并修复",[115,7144,7145,7147,7148,4814,7150,7152],{},[125,7146,7061],{},"：回答 import 同一模块两次代码执行几次及为什么、",[60,7149,4506],{},[60,7151,4510],{}," 的区别、循环导入为什么发生、最常见解决思路。",[135,7154,7156],{"id":7155},"day-3闭包与作用域深化152h","Day 3｜闭包与作用域深化（1.5–2h）",[115,7158,7159,7161,7162,185,7164,7166],{},[125,7160,6964],{},"：深入 LEGB、闭包捕获规则、",[60,7163,3587],{},[60,7165,3590],{}," 的边界，重点吃透总纲那条验收：",[619,7168,7169],{"className":717,"code":3769,"language":719,"meta":625,"style":625},[60,7170,7171,7175],{"__ignoreMap":625},[723,7172,7173],{"class":725,"line":726},[723,7174,3776],{},[723,7176,7177],{"class":725,"line":732},[723,7178,3781],{},[115,7180,3826,7181,3830,7183,3788],{},[60,7182,3845],{},[60,7184,3787],{},[115,7186,7187,181],{},[125,7188,6980],{},[619,7190,7191],{"className":717,"code":4862,"language":719,"meta":625,"style":625},[60,7192,7193,7197,7201,7205,7209,7213,7217,7221,7225,7229,7233],{"__ignoreMap":625},[723,7194,7195],{"class":725,"line":726},[723,7196,4869],{},[723,7198,7199],{"class":725,"line":732},[723,7200,4006],{},[723,7202,7203],{"class":725,"line":34},[723,7204,4878],{},[723,7206,7207],{"class":725,"line":1068},[723,7208,4016],{},[723,7210,7211],{"class":725,"line":1074},[723,7212,4021],{},[723,7214,7215],{"class":725,"line":1080},[723,7216,4026],{},[723,7218,7219],{"class":725,"line":1086},[723,7220,4895],{},[723,7222,7223],{"class":725,"line":1092},[723,7224,1060],{"emptyLinePlaceholder":30},[723,7226,7227],{"class":725,"line":1098},[723,7228,3776],{},[723,7230,7231],{"class":725,"line":1104},[723,7232,4908],{},[723,7234,7235],{"class":725,"line":1110},[723,7236,4913],{},[115,7238,7239,7240,7242],{},"解释闭包如何“记住”定义时的环境，以及 ",[60,7241,3590],{}," 在其中起什么作用。",[115,7244,7245,7248,7249,3830,7251,7253],{},[125,7246,7247],{},"验收 20min","：不用资料解释为什么 ",[60,7250,3845],{},[60,7252,3787],{}," 里的变量。",[135,7255,7257],{"id":7256},"day-4decorator-简化版152h","Day 4｜decorator 简化版（1.5–2h）",[115,7259,7260,7262],{},[125,7261,6964],{},"：理解 decorator 本质是“接受函数、返回新函数”的高阶函数，依赖闭包保存原函数。",[115,7264,7265,181],{},[125,7266,6980],{},[619,7268,7270],{"className":717,"code":7269,"language":719,"meta":625,"style":625},"def log(func):\n    def wrapper(*args, **kwargs):\n        print(f\"call {func.__name__} args={args} kwargs={kwargs}\")\n        return func(*args, **kwargs)\n    return wrapper\n\n@log\ndef add(a, b):\n    return a + b\n",[60,7271,7272,7276,7280,7284,7288,7292,7296,7300,7304],{"__ignoreMap":625},[723,7273,7274],{"class":725,"line":726},[723,7275,5090],{},[723,7277,7278],{"class":725,"line":732},[723,7279,5095],{},[723,7281,7282],{"class":725,"line":34},[723,7283,5100],{},[723,7285,7286],{"class":725,"line":1068},[723,7287,5105],{},[723,7289,7290],{"class":725,"line":1074},[723,7291,5110],{},[723,7293,7294],{"class":725,"line":1080},[723,7295,1060],{"emptyLinePlaceholder":30},[723,7297,7298],{"class":725,"line":1086},[723,7299,5119],{},[723,7301,7302],{"class":725,"line":1092},[723,7303,5124],{},[723,7305,7306],{"class":725,"line":1098},[723,7307,5129],{},[115,7309,7310,7311,7314,7315,5160,7317,863],{},"验证装饰后 ",[60,7312,7313],{},"add(1, 2)"," 会先打印日志再返回 3。理解 ",[60,7316,5159],{},[60,7318,5163],{},[115,7320,7321,7323,7324,7326,7327,185,7329,7331],{},[125,7322,7061],{},"：解释 ",[60,7325,5159],{}," 做了什么事、为什么需要 ",[60,7328,3577],{},[60,7330,3580],{},"、装饰器与闭包的关系。",[135,7333,7335],{"id":7334},"day-5l2-重构一目录拆分-logging152h","Day 5｜L2 重构（一）：目录拆分 + logging（1.5–2h）",[115,7337,7338,7340,7341,195,7343,7345,7346,1471,7349,863],{},[125,7339,6964],{},"：阅读 logging 文档与 Logging HOWTO，掌握 Logger、Handler、Formatter、日志级别、",[60,7342,964],{},[60,7344,968],{},"；学习分层思想 ",[60,7347,7348],{},"storage → core → cli",[60,7350,5320],{},[115,7352,7353,181],{},[125,7354,6980],{},[4640,7356,7357],{},[143,7358,7359,7362],{},[125,7360,7361],{},"目录拆分","：把 Week 1 项目重构为：",[619,7364,7367],{"className":7365,"code":7366,"language":624},[622],"project\u002F\n├── app\u002F\n│   ├── __init__.py\n│   ├── core\u002F\n│   ├── storage\u002F\n│   ├── cli.py\n│   └── config.py\n├── tests\u002F\n└── config.json\n",[60,7368,7366],{"__ignoreMap":625},[4640,7370,7371,7383,7395],{"start":732},[143,7372,7373,7374,7376,7377,7379,7380],{},"配置与代码分离：创建 ",[60,7375,951],{},"，用 dataclass ",[60,7378,5517],{}," 实现 ",[60,7381,7382],{},"load_config()",[143,7384,7385,7387,7388,7390,7391,7394],{},[125,7386,1453],{},"：用 ",[60,7389,964],{}," 替换 ",[60,7392,7393],{},"print","，统一格式（时间、模块、级别、消息）",[143,7396,7397],{},"测试：保持原有测试可运行",[115,7399,7400,7401,7404,7405,7407],{},"重点实验（basicConfig）：分别在 storage、cli 调用 ",[60,7402,7403],{},"logging.basicConfig(...)","，总结 ",[60,7406,5606],{}," 应由入口统一配置。",[115,7409,7410,7412,7413,7416],{},[125,7411,7061],{},"：回答四层结构分别负责什么、为什么推荐 ",[60,7414,7415],{},"getLogger(__name__)","、哪些东西应 \u002F 不应放配置文件、什么信号说明“拆对了”与“越拆越乱”。",[135,7418,7420],{"id":7419},"day-6l2-重构二dataclass-与-type-hints152h","Day 6｜L2 重构（二）：dataclass 与 type hints（1.5–2h）",[115,7422,7423,7425,7426,195,7428,195,7431,195,7434,7436,7437,195,7439,195,7442,195,7445,863],{},[125,7424,6964],{},"：阅读 dataclasses 与 typing 文档，掌握 ",[60,7427,1291],{},[60,7429,7430],{},"field",[60,7432,7433],{},"default_factory",[60,7435,5833],{},"，以及变量 \u002F 参数 \u002F 返回值标注、",[60,7438,6073],{},[60,7440,7441],{},"Union",[60,7443,7444],{},"list[T]",[60,7446,7447],{},"dict[K, V]",[115,7449,7450,181],{},[125,7451,6980],{},[4640,7453,7454,7465,7471],{},[143,7455,7456,7457,195,7459,7462,7463],{},"为重构后的项目定义 dataclass 数据模型（如 ",[60,7458,5517],{},[60,7460,7461],{},"Record","），可变字段用 ",[60,7464,7433],{},[143,7466,7467,7470],{},[125,7468,7469],{},"type hints","：给 storage、core 的公开函数补全参数与返回值类型标注",[143,7472,7473],{},"运行 mypy 检查类型标注（作为重构的验证手段）",[115,7475,7476,7478,7479,7481,7482,7485,7486,7488],{},[125,7477,7061],{},"：回答 ",[60,7480,1291],{}," 自动生成哪些方法、",[60,7483,7484],{},"list[str]"," 为什么不能直接作默认值及正确写法、",[60,7487,6010],{}," 什么意思、类型标注会不会自动阻止错误运行。",[135,7490,7492],{"id":7491},"day-7重构验收152h","Day 7｜重构验收（1.5–2h）",[115,7494,7495,7498,7499,185,7501,185,7503,185,7505,7507],{},[125,7496,7497],{},"Part 1｜重构验收 60min","：确认重构后的 Week 1 项目：目录已拆为 ",[60,7500,3667],{},[60,7502,3670],{},[60,7504,3673],{},[60,7506,3676],{},"，dataclass、type hints、logging 均已接入，原测试全部通过。",[115,7509,7510,7513,7514,7516,7517,7519],{},[125,7511,7512],{},"Part 2｜周复盘 45min","：回答总纲验收——",[60,7515,3829],{}," 为什么还能访问 ",[60,7518,3787],{}," 中定义的变量；并记录本周手写的参数组合、闭包、decorator 简化版、自定义 module 各自产出在哪个文件。",[130,7521,325],{"id":324},[115,7523,7524,7525,195,7527,195,7529,195,7531,7533],{},"总纲 Week 2 要求：将 Week 1 重构为 ",[60,7526,3667],{},[60,7528,3670],{},[60,7530,3673],{},[60,7532,3676],{},"，并加入 dataclass、type hints、logging。",[140,7535,7537,7551,7556,7561],{"className":7536},[238],[143,7538,7540,7542,7543,185,7545,185,7547,185,7549],{"className":7539},[242],[244,7541],{"disabled":30,"type":246}," 目录重构为 ",[60,7544,3667],{},[60,7546,3670],{},[60,7548,3673],{},[60,7550,3676],{},[143,7552,7554,3682],{"className":7553},[242],[244,7555],{"disabled":30,"type":246},[143,7557,7559,3688],{"className":7558},[242],[244,7560],{"disabled":30,"type":246},[143,7562,7564,3694],{"className":7563},[242],[244,7565],{"disabled":30,"type":246},[130,7567,370],{"id":369},[135,7569,426],{"id":425},[140,7571,7572],{},[143,7573,3703],{},[135,7575,434],{"id":369},[140,7577,7578],{},[143,7579,3710,7580,3713],{},[60,7581,951],{},[135,7583,443],{"id":442},[140,7585,7586],{},[143,7587,7588,7589,863],{},"结论与详细记录见 ",[119,7590,6828],{"href":6199},[135,7592,452],{"id":451},[140,7594,7595],{},[143,7596,7597,7598,7600],{},"重构在不改变行为的前提下提升了可维护性；",[60,7599,968],{}," 只在入口调一次。",[130,7602,460],{"id":459},[140,7604,7605,7607,7613],{},[143,7606,3740],{},[143,7608,3743,7609,195,7611,1588],{},[60,7610,3604],{},[60,7612,3600],{},[143,7614,3750],{},[130,7616,475],{"id":474},[115,7618,3755],{},[135,7620,138],{"id":481},[140,7622,7624],{"className":7623},[238],[143,7625,7627,3766],{"className":7626},[242],[244,7628],{"disabled":30,"type":246},[619,7630,7631],{"className":717,"code":3769,"language":719,"meta":625,"style":625},[60,7632,7633,7637],{"__ignoreMap":625},[723,7634,7635],{"class":725,"line":726},[723,7636,3776],{},[723,7638,7639],{"class":725,"line":732},[723,7640,3781],{},[115,7642,3784,7643,3788],{},[60,7644,3787],{},[135,7646,149],{"id":493},[140,7648,7650,7655],{"className":7649},[238],[143,7651,7653,3799],{"className":7652},[242],[244,7654],{"disabled":30,"type":246},[143,7656,7658,3664,7660,185,7662,185,7664,185,7666,3813],{"className":7657},[242],[244,7659],{"disabled":30,"type":246},[60,7661,3667],{},[60,7663,3670],{},[60,7665,3673],{},[60,7667,3676],{},[135,7669,158],{"id":511},[140,7671,7672],{},[143,7673,516],{},[130,7675,520],{"id":519},[522,7677,7678],{},[115,7679,3826,7680,3830,7682,3833],{},[60,7681,3829],{},[60,7683,3787],{},[130,7685,530],{"id":529},[140,7687,7688,7691],{},[143,7689,7690],{},"待收尾：Week 1 项目重构后的 dataclass \u002F type hints \u002F logging 完整落地与测试",[143,7692,7693],{},"decorator 简化版已列入本周手写项，需确认产出文件",[130,7695,539],{"id":538},[115,7697,149],{},[130,7699,7701],{"id":7700},"week-2-最终项目结构","Week 2 最终项目结构",[619,7703,7706],{"className":7704,"code":7705,"language":624},[622],"project\u002F\n├── app\u002F\n│   ├── __init__.py\n│   ├── core\u002F\n│   │   └── ...\n│   ├── storage\u002F\n│   │   └── ...\n│   ├── cli.py\n│   └── config.py\n├── tests\u002F\n│   └── ...\n├── config.json\n└── ...\n",[60,7707,7705],{"__ignoreMap":625},[130,7709,7710],{"id":7710},"本周产出清单",[140,7712,7713,7718,7723,7726,7729,7732,7735],{},[143,7714,7715,7717],{},[60,7716,6984],{},"（参数组合、闭包、decorator 简化版、lambda）",[143,7719,7720,7722],{},[60,7721,3653],{},"（自定义 module 与包）",[143,7724,7725],{},"Week 1 项目完成目录拆分",[143,7727,7728],{},"logging 接入",[143,7730,7731],{},"配置文件接入",[143,7733,7734],{},"dataclass 与 type hints",[143,7736,7737],{},"mypy 检查通过",[130,7739,741],{"id":741},[140,7741,7742,7747,7754,7762,7767,7772],{},[143,7743,7744,6089],{},[119,7745,6088],{"href":6086,"rel":7746},[750],[143,7748,7749],{},[119,7750,7753],{"href":7751,"rel":7752},"https:\u002F\u002Fdocs.python.org\u002F3\u002Freference\u002Fdatamodel.html",[750],"Python Data Model \u002F 对象模型",[143,7755,7756,1471,7759],{},[119,7757,1463],{"href":1459,"rel":7758},[750],[119,7760,6106],{"href":6104,"rel":7761},[750],[143,7763,7764],{},[119,7765,6112],{"href":1448,"rel":7766},[750],[143,7768,7769],{},[119,7770,6119],{"href":6117,"rel":7771},[750],[143,7773,7774],{},[119,7775,6125],{"href":772,"rel":7776},[750],[790,7778,792],{},{"title":625,"searchDepth":732,"depth":732,"links":7780},[7781,7786,7787,7796,7797,7803,7804,7809,7810,7811,7812,7813,7814],{"id":132,"depth":732,"text":133,"children":7782},[7783,7784,7785],{"id":137,"depth":34,"text":138},{"id":148,"depth":34,"text":149},{"id":157,"depth":34,"text":158},{"id":166,"depth":732,"text":167},{"id":230,"depth":732,"text":231,"children":7788},[7789,7790,7791,7792,7793,7794,7795],{"id":6958,"depth":34,"text":6959},{"id":7070,"depth":34,"text":7071},{"id":7155,"depth":34,"text":7156},{"id":7256,"depth":34,"text":7257},{"id":7334,"depth":34,"text":7335},{"id":7419,"depth":34,"text":7420},{"id":7491,"depth":34,"text":7492},{"id":324,"depth":732,"text":325},{"id":369,"depth":732,"text":370,"children":7798},[7799,7800,7801,7802],{"id":425,"depth":34,"text":426},{"id":369,"depth":34,"text":434},{"id":442,"depth":34,"text":443},{"id":451,"depth":34,"text":452},{"id":459,"depth":732,"text":460},{"id":474,"depth":732,"text":475,"children":7805},[7806,7807,7808],{"id":481,"depth":34,"text":138},{"id":493,"depth":34,"text":149},{"id":511,"depth":34,"text":158},{"id":519,"depth":732,"text":520},{"id":529,"depth":732,"text":530},{"id":538,"depth":732,"text":539},{"id":7700,"depth":732,"text":7701},{"id":7710,"depth":732,"text":7710},{"id":741,"depth":732,"text":741},"Week 2 学习计划：函数、作用域、闭包与模块化，从参数组合、闭包、decorator 简化版到自定义 module，并把 Week 1 项目重构为 core\u002Fstorage\u002Fcli\u002Fconfig 并加入 dataclass、type hints、logging。",{},"2026-09-20T01:51:31+08:00",{"title":6816,"description":7815},"python-week-2-functions-abstraction-modularization","writing\u002F2026-09-20-python-week-2-functions-abstraction-modularization",[837,6205,6206,5184,6207,6208,6209,6210,5951,1453],"T2Dko7XIk5Ppi2snDmZsiiik3Eod6EkmeYTrmP3R7xI",{"id":7824,"title":7825,"body":7826,"category":8345,"date":6798,"description":8346,"extension":827,"hidden":828,"kind":834,"meta":8347,"navigation":30,"path":8348,"publishedAt":8349,"seo":8350,"series":834,"slug":8351,"stem":8352,"tags":8353,"unit":834,"updated":834,"__hash__":8358},"writing\u002Fwriting\u002F2026-09-20-dca-spot-vs-futures.md","B站评论区幽默的定投哥们",{"type":112,"value":7827,"toc":8334},[7828,7831,7834,7837,7842,7849,7852,7855,7861,7864,7869,7872,7875,7880,7883,7888,7891,7896,7902,7905,7909,7912,7915,7918,7921,7927,7930,7933,7938,7941,7945,7948,7951,7954,7957,7960,7963,7966,7969,7972,7977,7981,7984,7987,7990,7993,7996,7999,8002,8005,8008,8011,8014,8017,8020,8023,8027,8030,8033,8036,8039,8044,8047,8064,8067,8071,8074,8077,8080,8083,8088,8091,8094,8099,8101,8105,8108,8113,8116,8119,8124,8127,8130,8133,8136,8139,8142,8145,8150,8154,8157,8160,8163,8168,8171,8177,8180,8183,8189,8192,8198,8205,8209,8212,8217,8220,8223,8228,8231,8234,8237,8240,8243,8246,8250,8253,8259,8265,8268,8271,8274,8279,8282,8287,8290,8293,8298,8301,8304,8309,8312,8315,8318,8323,8326,8329],[115,7829,7830],{},"定投现货和定投合约，可能只差两个字，结果天差地别。",[115,7832,7833],{},"今天刷到一个挺有意思的帖子。",[115,7835,7836],{},"UP 主发了一条动态，晒自己的现货定投战绩：",[522,7838,7839],{},[115,7840,7841],{},"大饼不会辜负任何一个坚定信仰者。ok啊，大饼从 2024 年 9 月 1 号的 6 万开始定投到 12 万，最近一年又跌回 6 万，现在重回 8 万。定投部分的大饼回本了，后续收益拭目以待，会在群里每周更新。优质资产只要能坚持住定投，不会辜负你的。",[115,7843,7844],{},[7845,7846],"img",{"alt":7847,"src":7848},"UP 主晒的现货定投记录与策略面板","\u002Fuploads\u002Fdca-bitcoin-dca-log.jpg",[115,7850,7851],{},"面板上的数字很朴素：已运行 747 天，累计投入约 5,819 USDT，每天 15:00 定投 15 USDT。这就是一个标准的、老老实实的现货定投。",[115,7853,7854],{},"然后看评论区，画风就变了：",[115,7856,7857],{},[7845,7858],{"alt":7859,"src":7860},"B站评论区：定投的讨论","\u002Fuploads\u002Fdca-comment-section.jpg",[115,7862,7863],{},"有人跑来跟帖：",[522,7865,7866],{},[115,7867,7868],{},"跟了，今天开始定投，天天爆仓妈的",[115,7870,7871],{},"\"定投\"和\"天天爆仓\"放在同一句话里，本身就很有喜感。",[115,7873,7874],{},"UP 主好心劝他：",[522,7876,7877],{},[115,7878,7879],{},"老老实实定投，别碰合约这种。",[115,7881,7882],{},"他回了一句更妙的：",[522,7884,7885],{},[115,7886,7887],{},"是的 下轮牛市止盈",[115,7889,7890],{},"然后还有人补刀：",[522,7892,7893],{},[115,7894,7895],{},"为什么不这轮止盈，熊跌的时候再定投？",[115,7897,7898,7899],{},"看到这里是真的绷不住了：",[125,7900,7901],{},"UP 主在晒现货定投，评论区来踢馆的哥们把\"定投\"直接做在了合约上。",[115,7903,7904],{},"不是哥们，定投不是应该定投现货吗？",[130,7906,7908],{"id":7907},"一正常人理解的定投是什么","一、正常人理解的\"定投\"是什么？",[115,7910,7911],{},"通常说的定投，是指：",[115,7913,7914],{},"固定时间 + 固定金额 + 持续买入某种资产。",[115,7916,7917],{},"比如每个月拿出 1000 元买某个指数基金，或者买某种现货资产。",[115,7919,7920],{},"上涨的时候买一点，下跌的时候也买一点。",[115,7922,7923,7924,863],{},"它的核心目的并不是预测市场什么时候涨、什么时候跌，而是",[125,7925,7926],{},"降低自己对择时的依赖",[115,7928,7929],{},"市场贵的时候买得少，便宜的时候买得多。",[115,7931,7932],{},"所以定投真正解决的问题，是：",[115,7934,7935],{},[125,7936,7937],{},"\"我什么时候买？\"",[115,7939,7940],{},"它并不能保证赚钱，也不意味着任何资产都适合定投。",[130,7942,7944],{"id":7943},"二问题来了为什么定投合约听起来这么离谱","二、问题来了：为什么\"定投合约\"听起来这么离谱？",[115,7946,7947],{},"因为合约和现货的风险结构完全不一样。",[115,7949,7950],{},"买现货，本质上是：",[115,7952,7953],{},"我花 1000 元，买价值 1000 元的资产。",[115,7955,7956],{},"哪怕资产价格跌了 50%，只要资产本身还存在，你的仓位通常还在那里。",[115,7958,7959],{},"但合约交易往往涉及：",[115,7961,7962],{},"杠杆、保证金、强平、资金费率、多空方向、短期波动风险。",[115,7964,7965],{},"假设有人用 10 倍杠杆做多。",[115,7967,7968],{},"那么标的价格出现较大幅度的不利波动，就可能让保证金迅速缩水，最终触发强平。",[115,7970,7971],{},"这时候就出现了一个非常尴尬的问题：",[115,7973,7974],{},[125,7975,7976],{},"定投的长期逻辑还没来得及发挥作用，仓位可能已经没了。",[130,7978,7980],{"id":7979},"三定投可以降低择时风险但不能消灭杠杆风险","三、定投可以降低择时风险，但不能消灭杠杆风险",[115,7982,7983],{},"这是这件事情最核心的地方。",[115,7985,7986],{},"假设一个人每个月买 1000 元现货。",[115,7988,7989],{},"市场：跌。",[115,7991,7992],{},"他：买。",[115,7994,7995],{},"市场继续跌。",[115,7997,7998],{},"他：继续买。",[115,8000,8001],{},"如果资产最终上涨，那么前面低价买入的仓位可能成为收益的一部分。",[115,8003,8004],{},"这就是典型的长期定投逻辑。",[115,8006,8007],{},"但如果换成高杠杆合约：",[115,8009,8010],{},"跌 → 仓位亏损 → 接近强平 → 强平。",[115,8012,8013],{},"下一次准备投入的 1000 元，并不能让上一笔已经被强平的仓位重新回来。",[115,8015,8016],{},"于是所谓的\"定投\"就可能变成：",[115,8018,8019],{},"定期投入资金 → 定期承担高杠杆风险 → 定期重新开仓。",[115,8021,8022],{},"这和传统意义上的 DCA 已经不是同一种风险结构了。",[130,8024,8026],{"id":8025},"四最搞笑的其实是下轮牛市止盈","四、最搞笑的其实是\"下轮牛市止盈\"",[115,8028,8029],{},"整个讨论里最有喜剧效果的一句话就是：",[115,8031,8032],{},"\"是的 下轮牛市止盈。\"",[115,8034,8035],{},"听起来非常长期主义。",[115,8037,8038],{},"但问题是：",[115,8040,8041],{},[125,8042,8043],{},"你得先活到下轮牛市。",[115,8045,8046],{},"而按下注的对象不同，命运也不同：",[140,8048,8049,8055],{},[143,8050,8051,8054],{},[125,8052,8053],{},"现货定投","（就是帖子开头那位 UP 主在做的）：熊市继续定投 → 慢慢积累仓位 → 等待下一轮行情。至少在逻辑上是可以成立的。",[143,8056,8057,8060,8061],{},[125,8058,8059],{},"高杠杆合约定投","（就是评论区那位哥们）：你判断几年后会涨，市场说\"我知道，但我现在先跌 30%\"，然后你的高杠杆仓位——",[125,8062,8063],{},"没了。",[115,8065,8066],{},"长期方向判断正确，并不意味着高杠杆仓位一定能够活到那个时候。",[115,8068,8069],{},[125,8070,8043],{},[115,8072,8073],{},"如果一个人买的是现货，那么：",[115,8075,8076],{},"熊市继续定投 → 慢慢积累仓位 → 等待下一轮行情",[115,8078,8079],{},"至少在逻辑上是可以成立的。",[115,8081,8082],{},"但如果一个人持续使用高杠杆合约，他面对的是另一套问题：",[115,8084,8085],{},[125,8086,8087],{},"你能不能让这个仓位活到你的长期判断兑现？",[115,8089,8090],{},"你判断：\"几年后会涨。\"",[115,8092,8093],{},"市场可能：\"我知道，但我现在先跌 30%。\"",[115,8095,8096,8097],{},"然后你的高杠杆仓位：",[125,8098,8063],{},[115,8100,8066],{},[130,8102,8104],{"id":8103},"五所以定投合约到底是什么","五、所以\"定投合约\"到底是什么？",[115,8106,8107],{},"如果一定要给它起一个名字，我觉得可以叫：",[115,8109,8110],{},[125,8111,8112],{},"长期主义地承担短期爆仓风险。",[115,8114,8115],{},"当然，这只是玩笑。",[115,8117,8118],{},"真正值得注意的是，它暴露出了一个很常见的投资认知问题：",[115,8120,8121],{},[125,8122,8123],{},"一个策略里的某个概念正确，并不意味着把这个概念套到任何东西上都正确。",[115,8125,8126],{},"\"定投\"是一种仓位建立方式。",[115,8128,8129],{},"\"合约\"则是一种带有杠杆、保证金和强平机制的交易工具。",[115,8131,8132],{},"把两者简单拼在一起：",[115,8134,8135],{},"定投 + 合约 = 定投合约",[115,8137,8138],{},"并不会自动得到：",[115,8140,8141],{},"长期投资 + 风险降低。",[115,8143,8144],{},"有时候得到的反而是：",[115,8146,8147],{},[125,8148,8149],{},"机械化承担高风险。",[130,8151,8153],{"id":8152},"六现货和合约的区别不只是哪个赚钱更多","六、现货和合约的区别，不只是\"哪个赚钱更多\"",[115,8155,8156],{},"很多人讨论投资的时候，容易只关注：",[115,8158,8159],{},"\"哪个收益高？\"",[115,8161,8162],{},"但更加重要的问题其实是：",[115,8164,8165],{},[125,8166,8167],{},"这个仓位能不能活到我的投资逻辑兑现？",[115,8169,8170],{},"这也是现货和合约之间非常重要的区别之一。",[115,8172,8173,8176],{},[125,8174,8175],{},"现货","可以长期持有、分批买入、熊市继续积累，不存在合约意义上的强平线。",[115,8178,8179],{},"当然，这并不意味着现货没有风险。",[115,8181,8182],{},"如果资产本身长期下跌，或者最终失去价值，现货投资者同样可能遭受巨大损失。",[115,8184,8185,8186],{},"所以：",[125,8187,8188],{},"现货 ≠ 安全。",[115,8190,8191],{},"它只是通常不存在合约那种由于杠杆和强平机制导致仓位突然归零的风险。",[115,8193,8194,8197],{},[125,8195,8196],{},"合约","则需要额外考虑：杠杆倍数、保证金率、强平价格、资金费率、仓位管理、止损、极端行情。",[115,8199,8200,8201,8204],{},"因此，合约更接近一种",[125,8202,8203],{},"交易工具","，而不是天然适合长期定投的资产持有方式。",[130,8206,8208],{"id":8207},"七真正值得长期坚持的可能不是定投两个字","七、真正值得长期坚持的，可能不是\"定投\"两个字",[115,8210,8211],{},"如果把这件事情再往深处想一步，会发现：",[115,8213,8214],{},[125,8215,8216],{},"投资策略从来不是几个流行词简单排列组合出来的。",[115,8218,8219],{},"不是\"定投 = 好\"，也不是\"合约 = 坏\"。",[115,8221,8222],{},"真正需要分析的是完整的风险结构：",[115,8224,8225],{},[125,8226,8227],{},"买什么 + 怎么买 + 用什么工具买 + 杠杆多少 + 能承受多大回撤 + 什么情况下退出。",[115,8229,8230],{},"例如：",[115,8232,8233],{},"长期看好某项资产 → 使用现货 → 分批建仓 → 控制仓位 → 承受正常波动 → 根据自己的目标和风险承受能力逐步退出。",[115,8235,8236],{},"这是一个完整的逻辑。",[115,8238,8239],{},"而：",[115,8241,8242],{},"长期看好某项资产 → 高杠杆做多 → 定期继续开仓 → 希望几年后牛市止盈。",[115,8244,8245],{},"虽然同样包含\"长期看好\"和\"定期投入\"，但中间的风险结构已经完全不同了。",[130,8247,8249],{"id":8248},"八这件事为什么会让人觉得特别好笑","八、这件事为什么会让人觉得特别好笑？",[115,8251,8252],{},"因为它把两个本来完全不同的概念硬凑在了一起。",[115,8254,8255,8256],{},"正常人的想象是：",[125,8257,8258],{},"定投 → 慢慢积累资产。",[115,8260,8261,8262],{},"结果这哥们：",[125,8263,8264],{},"定投 → 慢慢积累爆仓经验。",[115,8266,8267],{},"正常的长期投资：牛市不追、熊市继续积累、时间换空间。",[115,8269,8270],{},"他的长期投资：每天固定时间开仓、每天固定承担杠杆风险、然后希望几年以后自己还在场内。",[115,8272,8273],{},"于是整个逻辑就变成了：",[115,8275,8276],{},[125,8277,8278],{},"长期主义地短期归零。",[115,8280,8281],{},"甚至可以戏称为：",[115,8283,8284],{},[125,8285,8286],{},"DCA：Daily Capital Annihilation，每日资金毁灭计划。",[115,8288,8289],{},"当然，这只是一个玩笑。",[115,8291,8292],{},"但玩笑背后的道理其实很简单：",[115,8294,8295],{},[125,8296,8297],{},"长期持有的逻辑，必须建立在仓位能够长期存在的前提上。",[130,8299,8300],{"id":8300},"最后",[115,8302,8303],{},"所以看到\"定投合约\"这几个字，我第一反应确实就是：",[115,8305,8306,8308],{},[125,8307,7904],{}," 😂",[115,8310,8311],{},"定投最重要的意义之一，是降低自己对短期择时的依赖。",[115,8313,8314],{},"而高杠杆合约恰恰会放大短期价格波动，并引入强平风险。",[115,8316,8317],{},"所以，一个人可以长期看好某项资产，也可以采用定投策略，但这并不意味着：",[115,8319,8320],{},[125,8321,8322],{},"\"长期看好 + 定投 + 杠杆合约\"就天然等于长期投资。",[115,8324,8325],{},"有时候，真正的长期主义不是一直加仓。",[115,8327,8328],{},"而是：",[115,8330,8331],{},[125,8332,8333],{},"先保证自己的仓位能够活到自己判断兑现的那一天。",{"title":625,"searchDepth":732,"depth":732,"links":8335},[8336,8337,8338,8339,8340,8341,8342,8343,8344],{"id":7907,"depth":732,"text":7908},{"id":7943,"depth":732,"text":7944},{"id":7979,"depth":732,"text":7980},{"id":8025,"depth":732,"text":8026},{"id":8103,"depth":732,"text":8104},{"id":8152,"depth":732,"text":8153},{"id":8207,"depth":732,"text":8208},{"id":8248,"depth":732,"text":8249},{"id":8300,"depth":732,"text":8300},"观察","定投可以降低择时依赖，但把定投用在高杠杆合约上并不会消除风险，反而可能变成机械化承担爆仓风险。",{},"\u002Fwriting\u002F2026-09-20-dca-spot-vs-futures","2026-09-20T01:33:33+08:00",{"title":7825,"description":8346},"dca-spot-vs-futures","writing\u002F2026-09-20-dca-spot-vs-futures",[8354,8196,8175,8355,8356,8357],"定投","杠杆","加密货币","风险管理","Inem-bPsScDR_8zkoIVta741NIii8NhP9bhVLJ_H3Lk",{"id":8360,"title":8361,"body":8362,"category":824,"date":10613,"description":10614,"extension":827,"hidden":828,"kind":1515,"meta":10615,"navigation":30,"path":10616,"publishedAt":10617,"seo":10618,"series":102,"slug":10619,"stem":10620,"tags":10621,"unit":10626,"updated":825,"__hash__":10627},"writing\u002Fwriting\u002F2026-09-09-week1-learning-outcomes.md","Week 1 学习成果｜复杂度实测、对象模型与数据结构实战",{"type":112,"value":8363,"toc":10573},[8364,8377,8379,8381,8386,8388,8393,8395,8399,8401,8412,8422,8424,8469,8471,8491,8493,8495,8500,8502,8511,8513,8518,8688,8693,8698,8726,8728,8733,8735,8743,8745,8748,8750,8771,8773,8799,8801,8805,8807,8812,8815,8817,8838,8840,8842,8846,8854,8860,8877,8883,9009,9013,9017,9035,9040,9065,9073,9183,9188,9207,9212,9246,9252,9256,9264,9267,9274,9278,9287,9290,9313,9319,9324,9328,9331,9386,9391,9413,9446,9450,9467,9496,9500,9539,9563,9566,9608,9615,9619,9622,9626,9670,9674,9678,9695,9725,9729,9757,9791,9797,9801,9808,9812,9820,9823,9827,9832,9867,9872,9922,9927,9981,9986,10033,10037,10042,10085,10090,10105,10110,10180,10184,10238,10242,10247,10256,10261,10286,10291,10354,10359,10376,10383,10386,10481,10490,10492,10571],[115,8365,8366,8367,2442,8371,123,8373,8376],{},"本篇对应",[119,8368,8370],{"href":8369},"\u002Fwriting\u002F2026-08-25-week1-python-object-model-data-structures","Week 1 学习计划",[119,8372,122],{"href":121},[125,8374,8375],{},"Week 1 · Python 对象模型与核心数据结构","（等级 L2）一致，按总纲 Part II「每周固定执行模板」编排。计划篇保留任务与要求，这里集中放实际做出来的东西和实测数据。目前有书面成果的是 Day 2–4，Day 5–7 完成后再补进来。",[130,8378,133],{"id":132},[135,8380,138],{"id":137},[140,8382,8383],{},[143,8384,8385],{},"✅ 能解释可变对象 \u002F 不可变对象、引用，以及为什么 dict \u002F set 通常查找较快",[135,8387,149],{"id":148},[140,8389,8390],{},[143,8391,8392],{},"🟡 已独立完成 set 去重、frequency counter、多字段排序；KV Store 尚未产出书面成果",[135,8394,158],{"id":157},[140,8396,8397],{},[143,8398,516],{},[130,8400,167],{"id":166},[140,8402,8403,8406,8409],{},[143,8404,8405],{},"对象模型：对象三属性（id \u002F type \u002F 值）、mutable 与 immutable、引用与赋值",[143,8407,8408],{},"list \u002F dict \u002F set 的底层结构与复杂度：连续指针数组 vs 哈希表",[143,8410,8411],{},"大 O 的平均 \u002F 均摊前提：哈希冲突与 rehash 扩容",[115,8413,8414,8415,8418,8419,863],{},"本周实际掌握的核心链条是：",[125,8416,8417],{},"名字 → 对象 → 引用 → 作用域","，以及 ",[125,8420,8421],{},"list \u002F dict \u002F set 的复杂度边界",[130,8423,231],{"id":230},[140,8425,8427,8437,8446,8452],{"className":8426},[238],[143,8428,8430,8432,8433,8436],{"className":8429},[242],[244,8431],{"checked":30,"disabled":30,"type":246}," 手写：",[60,8434,8435],{},"object_model.md","（name → object → reference → scope 全文见下）",[143,8438,8440,8432,8442,8445],{"className":8439},[242],[244,8441],{"checked":30,"disabled":30,"type":246},[60,8443,8444],{},"complexity.md"," 复杂度对照表（实测数据见下）",[143,8447,8449,8451],{"className":8448},[242],[244,8450],{"checked":30,"disabled":30,"type":246}," 手写：set 去重（list 版 vs set 版）、frequency counter（手写 dict vs Counter）、多字段排序（取负法 vs stable sort）",[143,8453,8455,8457,8458,185,8460,185,8462,185,8464,185,8466,8468],{"className":8454},[242],[244,8456],{"disabled":30,"type":246}," 简单 KV Store（",[60,8459,5271],{},[60,8461,5274],{},[60,8463,5277],{},[60,8465,5280],{},[60,8467,5283],{},"）（待补）",[130,8470,325],{"id":324},[140,8472,8474],{"className":8473},[238],[143,8475,8477,8479,8480,185,8482,185,8484,185,8486,185,8488,8468],{"className":8476},[242],[244,8478],{"disabled":30,"type":246}," 完善第一版 KV Store 的目录结构（",[60,8481,3670],{},[60,8483,3667],{},[60,8485,3673],{},[60,8487,3676],{},[60,8489,8490],{},"tests\u002F",[130,8492,370],{"id":369},[135,8494,426],{"id":425},[140,8496,8497],{},[143,8498,8499],{},"哈希表（dict \u002F set）的 get \u002F set \u002F membership 应保持常数级；list 的查找与头部插入应随数据量线性增长。",[135,8501,434],{"id":369},[140,8503,8504],{},[143,8505,8506,8507,8510],{},"Python 3 + timeit，repeat=5 取均值，计时 ",[60,8508,8509],{},"timeit.timeit(lambda: func(data), number=N) \u002F N","；每轮测试前重新 copy 干净数据且不计入计时；search 测“查找不存在的值 n”（最坏情况），dict set \u002F set add 用不存在的新键测真实插入。",[135,8512,443],{"id":442},[140,8514,8515],{},[143,8516,8517],{},"实测数据（单位：秒\u002F次）：",[264,8519,8520,8539],{},[267,8521,8522],{},[270,8523,8524,8527,8530,8533,8536],{},[273,8525,8526],{},"操作",[273,8528,8529],{},"n=1,000",[273,8531,8532],{},"n=10,000",[273,8534,8535],{},"n=100,000",[273,8537,8538],{},"增长规律",[283,8540,8541,8558,8575,8592,8608,8624,8640,8656,8671],{},[270,8542,8543,8546,8549,8552,8555],{},[288,8544,8545],{},"list index",[288,8547,8548],{},"1.07e-7",[288,8550,8551],{},"1.13e-7",[288,8553,8554],{},"1.09e-7",[288,8556,8557],{},"恒定 → O(1)",[270,8559,8560,8563,8566,8569,8572],{},[288,8561,8562],{},"list append",[288,8564,8565],{},"9.4e-8",[288,8567,8568],{},"1.00e-7",[288,8570,8571],{},"1.25e-7",[288,8573,8574],{},"基本恒定 → 均摊 O(1)",[270,8576,8577,8580,8583,8586,8589],{},[288,8578,8579],{},"list insert(0)",[288,8581,8582],{},"2.46e-7",[288,8584,8585],{},"1.58e-6",[288,8587,8588],{},"1.32e-5",[288,8590,8591],{},"n×10 → 时间×10 → O(n)",[270,8593,8594,8597,8600,8603,8606],{},[288,8595,8596],{},"list search",[288,8598,8599],{},"4.73e-6",[288,8601,8602],{},"4.64e-5",[288,8604,8605],{},"4.75e-4",[288,8607,8591],{},[270,8609,8610,8613,8616,8619,8622],{},[288,8611,8612],{},"dict get",[288,8614,8615],{},"1.41e-7",[288,8617,8618],{},"1.31e-7",[288,8620,8621],{},"1.40e-7",[288,8623,8557],{},[270,8625,8626,8629,8632,8635,8638],{},[288,8627,8628],{},"dict set",[288,8630,8631],{},"2.07e-7",[288,8633,8634],{},"1.88e-7",[288,8636,8637],{},"1.92e-7",[288,8639,8557],{},[270,8641,8642,8645,8648,8651,8654],{},[288,8643,8644],{},"dict membership",[288,8646,8647],{},"1.04e-7",[288,8649,8650],{},"9.5e-8",[288,8652,8653],{},"9.6e-8",[288,8655,8557],{},[270,8657,8658,8661,8664,8667,8669],{},[288,8659,8660],{},"set add",[288,8662,8663],{},"1.10e-7",[288,8665,8666],{},"1.02e-7",[288,8668,8568],{},[288,8670,8557],{},[270,8672,8673,8676,8679,8682,8685],{},[288,8674,8675],{},"set membership",[288,8677,8678],{},"9.9e-8",[288,8680,8681],{},"1.29e-7",[288,8683,8684],{},"9.1e-8",[288,8686,8687],{},"恒定（小幅波动为噪声）→ O(1)",[115,8689,8690],{},[7845,8691],{"alt":625,"src":8692},"\u002Fuploads\u002F684ae7e5-cb2e-4eb8-b220-bcb49fea2bfc.png",[115,8694,8695],{},[125,8696,8697],{},"实验中的三个重要发现：",[4640,8699,8700,8714,8720],{},[143,8701,8702,8705,8706,8709,8710,8713],{},[125,8703,8704],{},"最好情况陷阱","：最初 search 测 ",[60,8707,8708],{},"0 in lst","，结果恒定在 ~8e-8 秒，看起来像 O(1)。改成 ",[60,8711,8712],{},"n in lst","（不存在，必须扫全表）后立刻变成标准线性增长。→ 测什么数据点，直接决定结论对不对。",[143,8715,8716,8719],{},[125,8717,8718],{},"固定开销稀释线性规律","：insert 在三个规模间只增长 6~8 倍而非 10 倍，因为测得的时间 = 固定开销（lambda 调用等 ~1e-7s）+ 线性成本。减去固定开销后，倍数恢复到 ×10 \u002F ×9。→ 小规模数据测出来的主要是调用开销，不是算法本身。",[143,8721,8722,8725],{},[125,8723,8724],{},"同为 O(n)，常数差 36 倍","：n=100,000 时 insert(0)（memmove 连续内存搬运）比 search（Python 层逐个比较）快约 36 倍。→ 大 O 只描述增长趋势，不描述绝对速度；真实选型要靠实测。",[135,8727,452],{"id":451},[140,8729,8730],{},[143,8731,8732],{},"实测确认了假设；delete 类操作与 1,000,000 规模当周未测，如实标注为“未实测”。",[130,8734,460],{"id":459},[140,8736,8737,8740],{},[143,8738,8739],{},"Python 对象模型与内存模型基础",[143,8741,8742],{},"基础数据结构（list \u002F dict \u002F set）与哈希表原理",[130,8744,475],{"id":474},[115,8746,8747],{},"总纲 Week 1 验收：",[135,8749,138],{"id":481},[140,8751,8753,8759,8765],{"className":8752},[238],[143,8754,8756,8758],{"className":8755},[242],[244,8757],{"checked":30,"disabled":30,"type":246}," 能解释可变对象 \u002F 不可变对象",[143,8760,8762,8764],{"className":8761},[242],[244,8763],{"checked":30,"disabled":30,"type":246}," 能解释引用",[143,8766,8768,8770],{"className":8767},[242],[244,8769],{"checked":30,"disabled":30,"type":246}," 能解释为什么 dict \u002F set 通常查找较快",[135,8772,149],{"id":493},[140,8774,8776,8782],{"className":8775},[238],[143,8777,8779,8781],{"className":8778},[242],[244,8780],{"checked":30,"disabled":30,"type":246}," 能独立完成 set 去重、frequency counter、多字段排序",[143,8783,8785,8787,8788,185,8790,185,8792,185,8794,185,8796,8798],{"className":8784},[242],[244,8786],{"disabled":30,"type":246}," 能独立完成 ",[60,8789,5271],{},[60,8791,5274],{},[60,8793,5277],{},[60,8795,5280],{},[60,8797,5283],{},"（KV Store 待补）",[135,8800,158],{"id":511},[140,8802,8803],{},[143,8804,516],{},[130,8806,520],{"id":519},[522,8808,8809],{},[115,8810,8811],{},"为什么 dict 平均 O(1)，但不能说 dict 永远 O(1)？",[115,8813,8814],{},"答案落在哈希冲突（同一槽位退化为链式探测，最坏 O(n)）与 rehash 扩容（超过负载因子阈值时单次插入瞬间 O(n)，均摊后仍 O(1)）上——大 O 有“平均 \u002F 均摊”的前提。",[130,8816,530],{"id":529},[140,8818,8819,8835],{},[143,8820,8821,8822,185,8825,185,8828,185,8831,8834],{},"总纲 Experiment 要求覆盖“删除性能”，但 delete 类操作（",[60,8823,8824],{},"list.pop(0)",[60,8826,8827],{},"list.remove()",[60,8829,8830],{},"dict del",[60,8832,8833],{},"set discard","）与 1,000,000 规模未测",[143,8836,8837],{},"简单 KV Store 与最终 Benchmark 尚未产出书面成果",[130,8839,539],{"id":538},[115,8841,149],{},[130,8843,8845],{"id":8844},"day-2-成果复杂度实测与对照表","Day 2 成果｜复杂度实测与对照表",[115,8847,8848,181,8850],{},[125,8849,2659],{},[119,8851,8853],{"href":8852},"\u002Fwriting\u002F2026-08-25-week1-python-object-model-data-structures#day-2list--dict--set--hash","Day 2｜List \u002F Dict \u002F Set + Hash",[115,8855,8856,8859],{},[125,8857,8858],{},"实验方法","（Python 3 + timeit，repeat=5 取均值）：",[140,8861,8862,8868,8871,8874],{},[143,8863,8864,8865,8867],{},"计时：",[60,8866,8509],{},"，得到单次操作耗时",[143,8869,8870],{},"每轮测试前重新 copy 干净数据，且 copy 不计入计时",[143,8872,8873],{},"search 测“查找不存在的值 n”（最坏情况），避开“搜第一个元素”的最好情况陷阱",[143,8875,8876],{},"dict set \u002F set add 用不存在的新键，测真实插入而非覆盖",[115,8878,8879,8882],{},[125,8880,8881],{},"实测数据","（单位：秒\u002F次）：",[264,8884,8885,8899],{},[267,8886,8887],{},[270,8888,8889,8891,8893,8895,8897],{},[273,8890,8526],{},[273,8892,8529],{},[273,8894,8532],{},[273,8896,8535],{},[273,8898,8538],{},[283,8900,8901,8913,8925,8937,8949,8961,8973,8985,8997],{},[270,8902,8903,8905,8907,8909,8911],{},[288,8904,8545],{},[288,8906,8548],{},[288,8908,8551],{},[288,8910,8554],{},[288,8912,8557],{},[270,8914,8915,8917,8919,8921,8923],{},[288,8916,8562],{},[288,8918,8565],{},[288,8920,8568],{},[288,8922,8571],{},[288,8924,8574],{},[270,8926,8927,8929,8931,8933,8935],{},[288,8928,8579],{},[288,8930,8582],{},[288,8932,8585],{},[288,8934,8588],{},[288,8936,8591],{},[270,8938,8939,8941,8943,8945,8947],{},[288,8940,8596],{},[288,8942,8599],{},[288,8944,8602],{},[288,8946,8605],{},[288,8948,8591],{},[270,8950,8951,8953,8955,8957,8959],{},[288,8952,8612],{},[288,8954,8615],{},[288,8956,8618],{},[288,8958,8621],{},[288,8960,8557],{},[270,8962,8963,8965,8967,8969,8971],{},[288,8964,8628],{},[288,8966,8631],{},[288,8968,8634],{},[288,8970,8637],{},[288,8972,8557],{},[270,8974,8975,8977,8979,8981,8983],{},[288,8976,8644],{},[288,8978,8647],{},[288,8980,8650],{},[288,8982,8653],{},[288,8984,8557],{},[270,8986,8987,8989,8991,8993,8995],{},[288,8988,8660],{},[288,8990,8663],{},[288,8992,8666],{},[288,8994,8568],{},[288,8996,8557],{},[270,8998,8999,9001,9003,9005,9007],{},[288,9000,8675],{},[288,9002,8678],{},[288,9004,8681],{},[288,9006,8684],{},[288,9008,8687],{},[115,9010,9011],{},[7845,9012],{"alt":625,"src":8692},[115,9014,9015],{},[125,9016,8697],{},[4640,9018,9019,9027,9031],{},[143,9020,9021,8705,9023,8709,9025,8713],{},[125,9022,8704],{},[60,9024,8708],{},[60,9026,8712],{},[143,9028,9029,8719],{},[125,9030,8718],{},[143,9032,9033,8725],{},[125,9034,8724],{},[115,9036,9037],{},[125,9038,9039],{},"重点理解：",[140,9041,9042,9049,9052,9055,9062],{},[143,9043,9044,9045,9048],{},"list 为什么随机访问 O(1)\n→ list 底层是连续的指针数组，",[60,9046,9047],{},"lst[i]"," 通过「首地址 + i × 指针大小」直接算出位置，一步到位，与 n 无关。",[143,9050,9051],{},"list 为什么头部插入 O(n)\n→ 连续内存要保序，insert(0) 必须把后面全部 n 个元素整体后移一位（C 层 memmove）。实测 n=100,000 时约 1.3e-5 秒，随 n 线性增长。",[143,9053,9054],{},"dict 为什么平均查找 O(1)\n→ 哈希表：key 经 hash 函数算出槽位，直接跳过去取，不需要遍历。实测 get 在 n=1,000~100,000 间稳定在 ~1.4e-7 秒，纹丝不动。",[143,9056,9057,9058,9061],{},"set 为什么 membership 平均 O(1)\n→ set 本质是只有 key 没有 value 的哈希表，",[60,9059,9060],{},"x in s"," 同样直接算槽位。实测 ~1e-7 秒，甚至比 list 的 index 还快（哈希路径更短）。",[143,9063,9064],{},"为什么“平均 O(1)”不等于“永远 O(1)”\n→ 哈希冲突：多个 key 落进同一槽位时退化为链式探测，最坏 O(n)。\n→ rehash 扩容：元素数超过负载因子阈值时，哈希表要扩容并重新安置全部元素，单次插入瞬间变 O(n)，均摊后仍是 O(1)。\n→ 所以大 O 有“平均\u002F均摊”的前提；benchmark 测大量操作取平均，恰好把这个因素平均掉了。",[115,9066,9067,9072],{},[125,9068,9069,9071],{},[60,9070,8444],{}," 复杂度对照表","（✅ = 本次实测验证；未标 = 理论值，本轮未测）：",[264,9074,9075,9088],{},[267,9076,9077],{},[270,9078,9079,9081,9084,9086],{},[273,9080,8526],{},[273,9082,9083],{},"list",[273,9085,5511],{},[273,9087,5271],{},[283,9089,9090,9106,9124,9150,9166],{},[270,9091,9092,9095,9100,9103],{},[288,9093,9094],{},"随机访问",[288,9096,9097,9099],{},[125,9098,2073],{}," ✅",[288,9101,9102],{},"—（无索引概念）",[288,9104,9105],{},"—（不支持下标）",[270,9107,9108,9111,9115,9121],{},[288,9109,9110],{},"查找",[288,9112,9113,9099],{},[125,9114,2089],{},[288,9116,9117,9120],{},[125,9118,9119],{},"O(1) 平均"," ✅（按 key）",[288,9122,9123],{},"—",[270,9125,9126,9129,9142,9146],{},[288,9127,9128],{},"插入",[288,9130,9131,9132,9135,9136,9139,9140,9099],{},"append 尾部 ",[125,9133,9134],{},"O(1) 均摊"," ✅；",[9137,9138],"br",{},"insert(0) 头部 ",[125,9141,2089],{},[288,9143,9144,9099],{},[125,9145,9119],{},[288,9147,9148,9099],{},[125,9149,9119],{},[270,9151,9152,9155,9161,9164],{},[288,9153,9154],{},"删除",[288,9156,9157,9158,9160],{},"pop() 尾部 O(1)；",[9137,9159],{},"pop(0)\u002Fremove O(n)（未实测）",[288,9162,9163],{},"O(1) 平均（未实测）",[288,9165,9163],{},[270,9167,9168,9171,9175,9179],{},[288,9169,9170],{},"membership",[288,9172,9173,9099],{},[125,9174,2089],{},[288,9176,9177,9099],{},[125,9178,9119],{},[288,9180,9181,9099],{},[125,9182,9119],{},[115,9184,9185],{},[125,9186,9187],{},"原因解释：",[140,9189,9190,9195,9201],{},[143,9191,9192,9194],{},[125,9193,9083],{},"：底层是连续指针数组。按下标访问是算术运算 → O(1)；查找、头部插入\u002F删除都要扫描或移动大量元素 → O(n)；尾部 append 均摊 O(1)（偶尔扩容拷贝全表，摊到每次可忽略）。",[143,9196,9197,9200],{},[125,9198,9199],{},"dict \u002F set","：底层是哈希表。get\u002Fset\u002Fadd\u002Fdel\u002Fin 都先算 hash 定位槽位，直接到达 → 平均 O(1)。代价：无序、更占内存、key 必须可哈希（不可变类型）。",[143,9202,9203,9206],{},[125,9204,9205],{},"选型口诀","：按下标快速访问 → list；按 key 快速存取\u002F判断存在性 → dict\u002Fset；频繁头部插删 → 用 collections.deque。",[115,9208,9209],{},[125,9210,9211],{},"待补项（Day 3 或有空时）：",[140,9213,9215,9229,9235],{"className":9214},[238],[143,9216,9218,9220,9221,195,9223,195,9225,195,9227],{"className":9217},[242],[244,9219],{"disabled":30,"type":246}," 补测 delete 类操作：",[60,9222,8824],{},[60,9224,8827],{},[60,9226,8830],{},[60,9228,8833],{},[143,9230,9232,9234],{"className":9231},[242],[244,9233],{"disabled":30,"type":246}," 补测 1,000,000 规模（注意调小 number，search 会很慢）",[143,9236,9238,9240,9241,4507,9244],{"className":9237},[242],[244,9239],{"disabled":30,"type":246}," 可选：加 collections.deque 对比 ",[60,9242,9243],{},"popleft",[60,9245,8824],{},[115,9247,9248,9251],{},[125,9249,9250],{},"未做的部分","（delete、100 万规模）如实标注了“未实测\u002F未测”。",[130,9253,9255],{"id":9254},"day-3-成果object_modelmd","Day 3 成果｜object_model.md",[115,9257,9258,181,9260],{},[125,9259,2659],{},[119,9261,9263],{"href":9262},"\u002Fwriting\u002F2026-08-25-week1-python-object-model-data-structures#day-3function--scope--copy","Day 3｜Function + Scope + Copy",[115,9265,9266],{},"验收要求写一页解释 name → object → reference → scope 的文档，以下是当天产出的全文：",[522,9268,9269],{},[115,9270,9271,9272],{},"Day 3 验收文档 · 核心链条：",[125,9273,8417],{},[135,9275,9277],{"id":9276},"一总览一行代码里发生了什么","一、总览：一行代码里发生了什么",[619,9279,9281],{"className":717,"code":9280,"language":719,"meta":625,"style":625},"a = [1, 2, 3]\n",[60,9282,9283],{"__ignoreMap":625},[723,9284,9285],{"class":725,"line":726},[723,9286,9280],{},[115,9288,9289],{},"这一行做了两件事：",[4640,9291,9292,9299],{},[143,9293,9294,9295,9298],{},"内存里创建了一个 list ",[125,9296,9297],{},"object","（对象）",[143,9300,9301,9302,9305,9306,9308,9309,9312],{},"创建了一个 ",[125,9303,9304],{},"name","（名字）",[60,9307,119],{},"，让它 ",[125,9310,9311],{},"reference","（引用\u002F指向）这个对象",[619,9314,9317],{"className":9315,"code":9316,"language":624},[622],"a（name，标签）\n   │ reference\n   ▼\n[1, 2, 3]（object，内存中真实的数据）\n",[60,9318,9316],{"__ignoreMap":625},[115,9320,9321],{},[125,9322,9323],{},"赋值只是贴标签，永远不复制对象。",[135,9325,9327],{"id":9326},"二object对象","二、object（对象）",[115,9329,9330],{},"Python 中一切数据都是对象。每个对象有三个属性：",[264,9332,9333,9346],{},[267,9334,9335],{},[270,9336,9337,9340,9343],{},[273,9338,9339],{},"属性",[273,9341,9342],{},"获取方式",[273,9344,9345],{},"特点",[283,9347,9348,9361,9373],{},[270,9349,9350,9353,9358],{},[288,9351,9352],{},"身份 id",[288,9354,9355],{},[60,9356,9357],{},"id(x)",[288,9359,9360],{},"唯一，对象存活期内不变",[270,9362,9363,9365,9370],{},[288,9364,275],{},[288,9366,9367],{},[60,9368,9369],{},"type(x)",[288,9371,9372],{},"决定对象能做什么操作",[270,9374,9375,9378,9383],{},[288,9376,9377],{},"值",[288,9379,9380,9382],{},[60,9381,4943],{}," 本身",[288,9384,9385],{},"mutable 可改 \u002F immutable 不可改",[9387,9388,9390],"h4",{"id":9389},"mutable-vs-immutable","mutable vs immutable",[140,9392,9393,9403],{},[143,9394,9395,9398,9399,9402],{},[125,9396,9397],{},"mutable（可变）","：list、dict、set → 可以",[125,9400,9401],{},"原地修改","，id 不变",[143,9404,9405,9408,9409,9412],{},[125,9406,9407],{},"immutable（不可变）","：int、str、tuple → 不能原地修改，任何“修改”\n都会",[125,9410,9411],{},"创建新对象","，让名字指向新地方",[619,9414,9416],{"className":717,"code":9415,"language":719,"meta":625,"style":625},"a = [1, 2, 3]\na[0] = 99      # 原地改，id(a) 不变\n\ns = \"hello\"\ns = s + \"!\"    # 不是原地改！创建了新字符串 \"hello!\"，s 改指向它\n               # 旧的 \"hello\" 没人引用了，被垃圾回收\n",[60,9417,9418,9422,9427,9431,9436,9441],{"__ignoreMap":625},[723,9419,9420],{"class":725,"line":726},[723,9421,9280],{},[723,9423,9424],{"class":725,"line":732},[723,9425,9426],{},"a[0] = 99      # 原地改，id(a) 不变\n",[723,9428,9429],{"class":725,"line":34},[723,9430,1060],{"emptyLinePlaceholder":30},[723,9432,9433],{"class":725,"line":1068},[723,9434,9435],{},"s = \"hello\"\n",[723,9437,9438],{"class":725,"line":1074},[723,9439,9440],{},"s = s + \"!\"    # 不是原地改！创建了新字符串 \"hello!\"，s 改指向它\n",[723,9442,9443],{"class":725,"line":1080},[723,9444,9445],{},"               # 旧的 \"hello\" 没人引用了，被垃圾回收\n",[135,9447,9449],{"id":9448},"三name-与-reference","三、name 与 reference",[140,9451,9452,9458],{},[143,9453,9454,9455],{},"name（变量名）不“装”数据，只是指向对象的",[125,9456,9457],{},"标签",[143,9459,9460,9463,9464,9466],{},[60,9461,9462],{},"b = a"," 复制的是 ",[125,9465,9311],{},"，不是 object：",[619,9468,9470],{"className":717,"code":9469,"language":719,"meta":625,"style":625},"a = [1, 2, 3]\nb = a              # b 和 a 两个标签，指向同一个对象\nb.append(4)\nprint(a)           # [1, 2, 3, 4] ← a 也“变”了（其实是同一个对象变了）\nprint(a is b)      # True：同一个对象\n",[60,9471,9472,9476,9481,9486,9491],{"__ignoreMap":625},[723,9473,9474],{"class":725,"line":726},[723,9475,9280],{},[723,9477,9478],{"class":725,"line":732},[723,9479,9480],{},"b = a              # b 和 a 两个标签，指向同一个对象\n",[723,9482,9483],{"class":725,"line":34},[723,9484,9485],{},"b.append(4)\n",[723,9487,9488],{"class":725,"line":1068},[723,9489,9490],{},"print(a)           # [1, 2, 3, 4] ← a 也“变”了（其实是同一个对象变了）\n",[723,9492,9493],{"class":725,"line":1074},[723,9494,9495],{},"print(a is b)      # True：同一个对象\n",[9387,9497,9499],{"id":9498},"vs-is","== vs is",[264,9501,9502,9512],{},[267,9503,9504],{},[270,9505,9506,9509],{},[273,9507,9508],{},"运算符",[273,9510,9511],{},"比较的是什么",[283,9513,9514,9526],{},[270,9515,9516,9521],{},[288,9517,9518],{},[60,9519,9520],{},"==",[288,9522,9523,9524],{},"对象的",[125,9525,9377],{},[270,9527,9528,9533],{},[288,9529,9530],{},[60,9531,9532],{},"is",[288,9534,9523,9535,9538],{},[125,9536,9537],{},"id","（是否同一个对象）",[619,9540,9542],{"className":717,"code":9541,"language":719,"meta":625,"style":625},"a = [1, 2, 3]\nb = [1, 2, 3]\na == b   # True  ：值一样\na is b   # False ：两个不同的对象\n",[60,9543,9544,9548,9553,9558],{"__ignoreMap":625},[723,9545,9546],{"class":725,"line":726},[723,9547,9280],{},[723,9549,9550],{"class":725,"line":732},[723,9551,9552],{},"b = [1, 2, 3]\n",[723,9554,9555],{"class":725,"line":34},[723,9556,9557],{},"a == b   # True  ：值一样\n",[723,9559,9560],{"class":725,"line":1068},[723,9561,9562],{},"a is b   # False ：两个不同的对象\n",[9387,9564,9565],{"id":9565},"拷贝三级表",[264,9567,9568,9577],{},[267,9569,9570],{},[270,9571,9572,9574],{},[273,9573,3891],{},[273,9575,9576],{},"效果",[283,9578,9579,9588,9598],{},[270,9580,9581,9585],{},[288,9582,9583],{},[60,9584,9462],{},[288,9586,9587],{},"只复制引用，完全共享",[270,9589,9590,9595],{},[288,9591,9592],{},[60,9593,9594],{},"copy.copy(a)",[288,9596,9597],{},"浅拷贝：只拷最外层，内层仍共享",[270,9599,9600,9605],{},[288,9601,9602],{},[60,9603,9604],{},"copy.deepcopy(a)",[288,9606,9607],{},"深拷贝：所有层级全拷，完全独立",[115,9609,9610,9611,9614],{},"浅拷贝对内层",[125,9612,9613],{},"一律只传引用","（不管可变不可变）。\n共享 immutable 对象无害（改不了）；共享 mutable 对象才有坑——\n这就是浅拷贝问题只出现在嵌套可变对象身上的原因。",[135,9616,9618],{"id":9617},"四scope作用域","四、scope（作用域）",[115,9620,9621],{},"name 通过 reference 连接 object，而**“这个名字指向哪个对象”由 scope 决定**。",[9387,9623,9625],{"id":9624},"legb-查找顺序读取变量时","LEGB 查找顺序（读取变量时）",[264,9627,9628,9636],{},[267,9629,9630],{},[270,9631,9632,9634],{},[273,9633,1228],{},[273,9635,4971],{},[283,9637,9638,9646,9654,9662],{},[270,9639,9640,9644],{},[288,9641,9642,4981],{},[125,9643,4980],{},[288,9645,4984],{},[270,9647,9648,9652],{},[288,9649,9650,4992],{},[125,9651,4991],{},[288,9653,4995],{},[270,9655,9656,9660],{},[288,9657,9658,5003],{},[125,9659,5002],{},[288,9661,5006],{},[270,9663,9664,9668],{},[288,9665,9666,5014],{},[125,9667,5013],{},[288,9669,5017],{},[115,9671,5020,9672,863],{},[125,9673,5023],{},[9387,9675,9677],{"id":9676},"最重要的一条规则赋值即局部","⭐ 最重要的一条规则：赋值即局部",[140,9679,9680,9686],{},[143,9681,9682,9685],{},[125,9683,9684],{},"读取","一个名字：按 LEGB 向外查找",[143,9687,9688,9691,9692],{},[125,9689,9690],{},"赋值","一个名字：直接在 L 层创建局部变量，",[125,9693,9694],{},"不向外查找",[619,9696,9698],{"className":717,"code":9697,"language":719,"meta":625,"style":625},"x = 10\ndef func():\n    print(x)   # 读取 → LEGB 找到全局 x → 10\ndef func2():\n    x = 20     # 赋值 → 在函数内新建局部 x，全局 x 不受影响\n",[60,9699,9700,9705,9710,9715,9720],{"__ignoreMap":625},[723,9701,9702],{"class":725,"line":726},[723,9703,9704],{},"x = 10\n",[723,9706,9707],{"class":725,"line":732},[723,9708,9709],{},"def func():\n",[723,9711,9712],{"class":725,"line":34},[723,9713,9714],{},"    print(x)   # 读取 → LEGB 找到全局 x → 10\n",[723,9716,9717],{"class":725,"line":1068},[723,9718,9719],{},"def func2():\n",[723,9721,9722],{"class":725,"line":1074},[723,9723,9724],{},"    x = 20     # 赋值 → 在函数内新建局部 x，全局 x 不受影响\n",[9387,9726,9728],{"id":9727},"global-nonlocal覆盖默认规则","global \u002F nonlocal：覆盖默认规则",[264,9730,9731,9739],{},[267,9732,9733],{},[270,9734,9735,9737],{},[273,9736,5036],{},[273,9738,5039],{},[283,9740,9741,9749],{},[270,9742,9743,9747],{},[288,9744,9745],{},[60,9746,5048],{},[288,9748,5051],{},[270,9750,9751,9755],{},[288,9752,9753],{},[60,9754,5058],{},[288,9756,5061],{},[619,9758,9760],{"className":717,"code":9759,"language":719,"meta":625,"style":625},"x = 10\ndef change():\n    global x    # 不再“赋值即局部”，直接改全局的 x\n    x = 20\nchange()\nprint(x)        # 20\n",[60,9761,9762,9766,9771,9776,9781,9786],{"__ignoreMap":625},[723,9763,9764],{"class":725,"line":726},[723,9765,9704],{},[723,9767,9768],{"class":725,"line":732},[723,9769,9770],{},"def change():\n",[723,9772,9773],{"class":725,"line":34},[723,9774,9775],{},"    global x    # 不再“赋值即局部”，直接改全局的 x\n",[723,9777,9778],{"class":725,"line":1068},[723,9779,9780],{},"    x = 20\n",[723,9782,9783],{"class":725,"line":1074},[723,9784,9785],{},"change()\n",[723,9787,9788],{"class":725,"line":1080},[723,9789,9790],{},"print(x)        # 20\n",[115,9792,9793,9794,9796],{},"不加声明却赋值，又想读写外层\u002F全局变量 → 报\n",[60,9795,5067],{},"（因为赋值让名字变局部，局部还没赋值就被读取了）。",[135,9798,9800],{"id":9799},"五一句话总结","五、一句话总结",[522,9802,9803],{},[115,9804,9805],{},[125,9806,9807],{},"对象活在内存里（object），名字只是标签；\n赋值复制的是引用；\n名字指向哪个对象，由作用域规则决定——\n读取走 LEGB，赋值即局部（除非 global \u002F nonlocal）。",[130,9809,9811],{"id":9810},"day-4-成果数据结构实战","Day 4 成果｜数据结构实战",[115,9813,9814,181,9816],{},[125,9815,2659],{},[119,9817,9819],{"href":9818},"\u002Fwriting\u002F2026-08-25-week1-python-object-model-data-structures#day-4%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%AE%9E%E6%88%98","Day 4｜数据结构实战",[115,9821,9822],{},"三道任务的完整解法、对比和当时写下的总结。",[135,9824,9826],{"id":9825},"数据去重list-版-vs-set-版","数据去重：list 版 vs set 版",[115,9828,9829,181],{},[125,9830,9831],{},"list 版本",[619,9833,9835],{"className":717,"code":9834,"language":719,"meta":625,"style":625},"def drop_duplicate_list(data):\n    results = []\n    for i in data:\n        if i not in results:  # 每次判断最坏要比对所有元素 → O(n)\n            results.append(i)\n    return results\n",[60,9836,9837,9842,9847,9852,9857,9862],{"__ignoreMap":625},[723,9838,9839],{"class":725,"line":726},[723,9840,9841],{},"def drop_duplicate_list(data):\n",[723,9843,9844],{"class":725,"line":732},[723,9845,9846],{},"    results = []\n",[723,9848,9849],{"class":725,"line":34},[723,9850,9851],{},"    for i in data:\n",[723,9853,9854],{"class":725,"line":1068},[723,9855,9856],{},"        if i not in results:  # 每次判断最坏要比对所有元素 → O(n)\n",[723,9858,9859],{"class":725,"line":1074},[723,9860,9861],{},"            results.append(i)\n",[723,9863,9864],{"class":725,"line":1080},[723,9865,9866],{},"    return results\n",[115,9868,9869,181],{},[125,9870,9871],{},"set\u002Fdict 版本",[619,9873,9875],{"className":717,"code":9874,"language":719,"meta":625,"style":625},"def drop_duplicate_set(data):\n    results = []\n    seen = set()\n    for i in data:\n        if i['id'] not in seen:  # 哈希查找 → O(1)\n            seen.add(i['id'])\n            results.append(i)\n    return results\n\nprint(drop_duplicate_set(users))\n",[60,9876,9877,9882,9886,9891,9895,9900,9905,9909,9913,9917],{"__ignoreMap":625},[723,9878,9879],{"class":725,"line":726},[723,9880,9881],{},"def drop_duplicate_set(data):\n",[723,9883,9884],{"class":725,"line":732},[723,9885,9846],{},[723,9887,9888],{"class":725,"line":34},[723,9889,9890],{},"    seen = set()\n",[723,9892,9893],{"class":725,"line":1068},[723,9894,9851],{},[723,9896,9897],{"class":725,"line":1074},[723,9898,9899],{},"        if i['id'] not in seen:  # 哈希查找 → O(1)\n",[723,9901,9902],{"class":725,"line":1080},[723,9903,9904],{},"            seen.add(i['id'])\n",[723,9906,9907],{"class":725,"line":1086},[723,9908,9861],{},[723,9910,9911],{"class":725,"line":1092},[723,9912,9866],{},[723,9914,9915],{"class":725,"line":1098},[723,9916,1060],{"emptyLinePlaceholder":30},[723,9918,9919],{"class":725,"line":1104},[723,9920,9921],{},"print(drop_duplicate_set(users))\n",[115,9923,9924,181],{},[125,9925,9926],{},"复杂度比较（任务要求）",[264,9928,9929,9949],{},[267,9930,9931],{},[270,9932,9933,9936,9943,9946],{},[273,9934,9935],{},"版本",[273,9937,9938,9939,9942],{},"单次 ",[60,9940,9941],{},"in"," 判断",[273,9944,9945],{},"总复杂度",[273,9947,9948],{},"数据量 100 万时",[283,9950,9951,9966],{},[270,9952,9953,9956,9959,9963],{},[288,9954,9955],{},"list 版",[288,9957,9958],{},"最坏 O(n)，挨个翻",[288,9960,9961],{},[125,9962,2105],{},[288,9964,9965],{},"10¹² 次操作 😱",[270,9967,9968,9971,9974,9978],{},[288,9969,9970],{},"set 版",[288,9972,9973],{},"O(1)，哈希直达",[288,9975,9976],{},[125,9977,2089],{},[288,9979,9980],{},"10⁶ 次操作 ✅",[115,9982,9983,181],{},[125,9984,9985],{},"学习总结",[140,9987,9988,9994,10018],{},[143,9989,9990,9993],{},[125,9991,9992],{},"去重 = 构建新列表，不是删除原数据","。边遍历边 remove 会让元素前移导致漏检（经典 bug）；正确做法是原数据不动，把“第一次出现的”收集到新列表。",[143,9995,9996,9999,10000,4584,10003,10006,10007],{},[125,9997,9998],{},"可哈希规则","（踩坑：",[60,10001,10002],{},"set.add({\"id\":1})",[60,10004,10005],{},"unhashable type: 'dict'","）：\n",[140,10008,10009,10012,10015],{},[143,10010,10011],{},"可哈希（能进 set \u002F 当 dict 键）：int、str、float、bool、tuple",[143,10013,10014],{},"不可哈希：list、dict、set",[143,10016,10017],{},"原因：set 靠哈希值当“门牌号”定位元素，可变对象内容会变，门牌号会失效",[143,10019,10020,181,10023,10026,10027,10029,10030,863],{},[125,10021,10022],{},"set 与 list 分工模式",[60,10024,10025],{},"seen","（set）只存 id 负责快速判断；",[60,10028,442],{},"（list）存完整 dict 负责输出。",[125,10031,10032],{},"set 管判断，list 管输出",[135,10034,10036],{"id":10035},"频率统计手写-dict-vs-counter","频率统计：手写 dict vs Counter",[115,10038,10039,181],{},[125,10040,10041],{},"不使用 Counter",[619,10043,10045],{"className":717,"code":10044,"language":719,"meta":625,"style":625},"def count_fruit(data):\n    results = dict()\n    for w in data:\n        results[w] = results.get(w, 0) + 1\n        # 键存在 → 取值+1；不存在 → 默认 0 再+1（地道写法 ⭐）\n    return results\n\nprint(count_fruit(words))\n",[60,10046,10047,10052,10057,10062,10067,10072,10076,10080],{"__ignoreMap":625},[723,10048,10049],{"class":725,"line":726},[723,10050,10051],{},"def count_fruit(data):\n",[723,10053,10054],{"class":725,"line":732},[723,10055,10056],{},"    results = dict()\n",[723,10058,10059],{"class":725,"line":34},[723,10060,10061],{},"    for w in data:\n",[723,10063,10064],{"class":725,"line":1068},[723,10065,10066],{},"        results[w] = results.get(w, 0) + 1\n",[723,10068,10069],{"class":725,"line":1074},[723,10070,10071],{},"        # 键存在 → 取值+1；不存在 → 默认 0 再+1（地道写法 ⭐）\n",[723,10073,10074],{"class":725,"line":1080},[723,10075,9866],{},[723,10077,10078],{"class":725,"line":1086},[723,10079,1060],{"emptyLinePlaceholder":30},[723,10081,10082],{"class":725,"line":1092},[723,10083,10084],{},"print(count_fruit(words))\n",[115,10086,10087,181],{},[125,10088,10089],{},"使用 Counter",[619,10091,10093],{"className":717,"code":10092,"language":719,"meta":625,"style":625},"from collections import Counter\nprint(Counter(words), Counter(words).most_common(2))\n",[60,10094,10095,10100],{"__ignoreMap":625},[723,10096,10097],{"class":725,"line":726},[723,10098,10099],{},"from collections import Counter\n",[723,10101,10102],{"class":725,"line":732},[723,10103,10104],{},"print(Counter(words), Counter(words).most_common(2))\n",[115,10106,10107,181],{},[125,10108,10109],{},"两种方案比较（任务要求）",[264,10111,10112,10125],{},[267,10113,10114],{},[270,10115,10116,10119,10122],{},[273,10117,10118],{},"能力",[273,10120,10121],{},"手写 dict",[273,10123,10124],{},"Counter",[283,10126,10127,10138,10149,10164],{},[270,10128,10129,10132,10135],{},[288,10130,10131],{},"统计",[288,10133,10134],{},"手写循环",[288,10136,10137],{},"✅ 自动",[270,10139,10140,10143,10146],{},[288,10141,10142],{},"缺键",[288,10144,10145],{},"💥 KeyError",[288,10147,10148],{},"✅ 返回 0 不报错",[270,10150,10151,10154,10157],{},[288,10152,10153],{},"排名",[288,10155,10156],{},"手写 sorted 一长串",[288,10158,10159,10160,10163],{},"✅ ",[60,10161,10162],{},"most_common(n)"," 一行",[270,10165,10166,10169,10172],{},[288,10167,10168],{},"运算",[288,10170,10171],{},"自己想办法",[288,10173,10159,10174,195,10177],{},[60,10175,10176],{},"c1 + c2",[60,10178,10179],{},"c1 - c2",[115,10181,10182,181],{},[125,10183,9985],{},[140,10185,10186,10193,10209,10222,10228],{},[143,10187,10188,10189,10192],{},"演进过三种写法：①两遍扫描（先全部登记为 0 再累加）→ ②if\u002Felse 一遍扫描 → ③ ",[60,10190,10191],{},"results[w] = results.get(w, 0) + 1"," 一行搞定。三种都对，get 版最地道。",[143,10194,10195,181,10198,10201,10202,10205,10206,863],{},[125,10196,10197],{},"dict 的 in 查的是键",[60,10199,10200],{},"x in d"," ≡ ",[60,10203,10204],{},"x in d.keys()","；查值要 ",[60,10207,10208],{},"x in d.values()",[143,10210,10211,10214,10215,948,10218,10221],{},[60,10212,10213],{},"most_common(2)"," 返回的是",[125,10216,10217],{},"元组的列表",[60,10219,10220],{},"[('apple', 3), ('banana', 2)]","，不是 dict，要按格式打印得再遍历。",[143,10223,10224,10227],{},[125,10225,10226],{},"启示","：遇到常见套路，先查标准库有没有现成工具（Counter 就是“为频率统计量身定做的 dict 子类”）。",[143,10229,10230,10233,10234,10237],{},[60,10231,10232],{},"\"\"\"...\"\"\""," 不是注释，是没被赋值的字符串；多行注释用每行 ",[60,10235,10236],{},"#","（编辑器 Ctrl+\u002F）。",[135,10239,10241],{"id":10240},"多字段排序取负法-vs-stable-sort","多字段排序：取负法 vs stable sort",[115,10243,10244],{},[125,10245,10246],{},"武器 1：取负法（一次排序）",[619,10248,10250],{"className":717,"code":10249,"language":719,"meta":625,"style":625},"sorted(users, key=lambda u: (-u['score'], u['age'], u['name']))\n",[60,10251,10252],{"__ignoreMap":625},[723,10253,10254],{"class":725,"line":726},[723,10255,10249],{},[115,10257,10258],{},[125,10259,10260],{},"武器 2：stable sort（三次排序）",[619,10262,10264],{"className":717,"code":10263,"language":719,"meta":625,"style":625},"user = sorted(user, key=lambda u: u[\"name\"])                 # 先排第三优先级\nuser = sorted(user, key=lambda u: u[\"age\"])                  # 再排第二优先级\nuser = sorted(user, key=lambda u: u[\"score\"], reverse=True)  # 最后排主键\nprint(user)\n",[60,10265,10266,10271,10276,10281],{"__ignoreMap":625},[723,10267,10268],{"class":725,"line":726},[723,10269,10270],{},"user = sorted(user, key=lambda u: u[\"name\"])                 # 先排第三优先级\n",[723,10272,10273],{"class":725,"line":732},[723,10274,10275],{},"user = sorted(user, key=lambda u: u[\"age\"])                  # 再排第二优先级\n",[723,10277,10278],{"class":725,"line":34},[723,10279,10280],{},"user = sorted(user, key=lambda u: u[\"score\"], reverse=True)  # 最后排主键\n",[723,10282,10283],{"class":725,"line":1068},[723,10284,10285],{},"print(user)\n",[115,10287,10288,181],{},[125,10289,10290],{},"sorted 与 Sorting HOW TO 的理解（任务要求必须自己理解）",[140,10292,10293,10334,10344],{},[143,10294,10295,10298],{},[125,10296,10297],{},"key 为什么是函数而不是直接写键名？",[140,10299,10300,10307,10310,10320,10331],{},[143,10301,10302,10303,10306],{},"dict 之间没有定义 ",[60,10304,10305],{},"\u003C"," 比较，且 Python 不知道你想按哪个字段排",[143,10308,10309],{},"key 是“提取比较依据”的机器：每个元素先被 key 函数加工，sorted 拿返回值排队",[143,10311,10312,10313,195,10316,10319],{},"数据不动、规则随传随换（",[60,10314,10315],{},"len",[60,10317,10318],{},"str.lower","、lambda 都能当 key）",[143,10321,10322,10323,10326,10327,10330],{},"⚠️ ",[60,10324,10325],{},"key=\"age\""," 报错 ",[60,10328,10329],{},"'str' object is not callable","——key 必须是 callable",[143,10332,10333],{},"函数是对象（Day 3）的直接应用：把 lambda 当参数传进去",[143,10335,10336,10339,10340,10343],{},[125,10337,10338],{},"元组优先级","：key 返回元组时从左到右比，",[125,10341,10342],{},"第 1 位 = 主键","，平手才比第 2 位。元组顺序必须和要求的优先级一字不差（踩坑：曾错写成 score, name, age，数据碰巧掩盖了错误）。取负法局限：字符串不能取负。",[143,10345,10346,10349,10350,10353],{},[125,10347,10348],{},"stable sort（稳定排序）","：值相等的元素排序后保持原相对顺序。所以",[125,10351,10352],{},"从最次要的键开始排","——主键最后排，主键并列者保持的是上一轮排好的次要键顺序。越重要的键越晚排，越晚的越权威。",[115,10355,10356,181],{},[125,10357,10358],{},"两种武器的统一心智模型",[619,10360,10364],{"className":10361,"code":10362,"language":10363,"meta":625,"style":625},"language-plain shiki shiki-themes github-light github-dark","取负法：[主键 | 次键 | 最次] ← 空间上从左到右，第 1 位最权威\n三连排：最次 → 次键 → 主键 ← 时间上从早到晚，最后排的最权威\n","plain",[60,10365,10366,10371],{"__ignoreMap":625},[723,10367,10368],{"class":725,"line":726},[723,10369,10370],{},"取负法：[主键 | 次键 | 最次] ← 空间上从左到右，第 1 位最权威\n",[723,10372,10373],{"class":725,"line":732},[723,10374,10375],{},"三连排：最次 → 次键 → 主键 ← 时间上从早到晚，最后排的最权威\n",[115,10377,10378,10379,10382],{},"正确输出（两种方法一致）：",[125,10380,10381],{},"C(19,95) → B(20,95) → A(20,90)","（score 并列的 B、C 由 age 升序决定）",[135,10384,10385],{"id":10385},"可复用模式与待改进",[619,10387,10389],{"className":717,"code":10388,"language":719,"meta":625,"style":625},"# 模式 1：过滤收集（读旧的、造新的）\nresult = []\nfor item in data:\n    if 条件(item):\n        result.append(item)\n\n# 模式 2：set 辅助去重\u002F查重\nseen = set()\nresult = []\nfor item in data:\n    key = item[\"id\"]\n    if key not in seen:\n        seen.add(key)\n        result.append(item)\n\n# 模式 3：get 频率统计\ncounts = {}\nfor w in words:\n    counts[w] = counts.get(w, 0) + 1\n",[60,10390,10391,10396,10401,10406,10411,10416,10420,10425,10430,10434,10438,10443,10448,10453,10457,10461,10466,10471,10476],{"__ignoreMap":625},[723,10392,10393],{"class":725,"line":726},[723,10394,10395],{},"# 模式 1：过滤收集（读旧的、造新的）\n",[723,10397,10398],{"class":725,"line":732},[723,10399,10400],{},"result = []\n",[723,10402,10403],{"class":725,"line":34},[723,10404,10405],{},"for item in data:\n",[723,10407,10408],{"class":725,"line":1068},[723,10409,10410],{},"    if 条件(item):\n",[723,10412,10413],{"class":725,"line":1074},[723,10414,10415],{},"        result.append(item)\n",[723,10417,10418],{"class":725,"line":1080},[723,10419,1060],{"emptyLinePlaceholder":30},[723,10421,10422],{"class":725,"line":1086},[723,10423,10424],{},"# 模式 2：set 辅助去重\u002F查重\n",[723,10426,10427],{"class":725,"line":1092},[723,10428,10429],{},"seen = set()\n",[723,10431,10432],{"class":725,"line":1098},[723,10433,10400],{},[723,10435,10436],{"class":725,"line":1104},[723,10437,10405],{},[723,10439,10440],{"class":725,"line":1110},[723,10441,10442],{},"    key = item[\"id\"]\n",[723,10444,10445],{"class":725,"line":1116},[723,10446,10447],{},"    if key not in seen:\n",[723,10449,10450],{"class":725,"line":1122},[723,10451,10452],{},"        seen.add(key)\n",[723,10454,10455],{"class":725,"line":1128},[723,10456,10415],{},[723,10458,10459],{"class":725,"line":1133},[723,10460,1060],{"emptyLinePlaceholder":30},[723,10462,10463],{"class":725,"line":1139},[723,10464,10465],{},"# 模式 3：get 频率统计\n",[723,10467,10468],{"class":725,"line":1145},[723,10469,10470],{},"counts = {}\n",[723,10472,10473],{"class":725,"line":1151},[723,10474,10475],{},"for w in words:\n",[723,10477,10478],{"class":725,"line":1157},[723,10479,10480],{},"    counts[w] = counts.get(w, 0) + 1\n",[115,10482,10483,10486,10487,863],{},[125,10484,10485],{},"待改进","：原理理解快但代码落地慢，明日规矩——",[125,10488,10489],{},"每个概念学完 10 分钟内跑第一行代码",[130,10491,741],{"id":741},[140,10493,10494,10502,10509,10516,10523,10530,10537,10544,10550,10557,10564],{},[143,10495,10496,10501],{},[119,10497,10500],{"href":10498,"rel":10499},"https:\u002F\u002Fdocs.python.org\u002F3\u002F",[750],"Python 官方文档","：教程与标准库入口",[143,10503,10504],{},[119,10505,10508],{"href":10506,"rel":10507},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fcopy.html",[750],"copy 模块（浅拷贝 \u002F 深拷贝）",[143,10510,10511],{},[119,10512,10515],{"href":10513,"rel":10514},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fdatastructures.html",[750],"数据结构教程（list \u002F tuple \u002F dict \u002F set）",[143,10517,10518],{},[119,10519,10522],{"href":10520,"rel":10521},"https:\u002F\u002Fdocs.python.org\u002F3\u002Fglossary.html#term-hashable",[750],"Glossary：hashable",[143,10524,10525],{},[119,10526,10529],{"href":10527,"rel":10528},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHash_table",[750],"Hash table（哈希表）",[143,10531,10532],{},[119,10533,10536],{"href":10534,"rel":10535},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FBig_O_notation",[750],"Big-O notation（大 O 记号）",[143,10538,10539],{},[119,10540,10543],{"href":10541,"rel":10542},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fclasses.html#python-scopes-and-namespaces",[750],"Python 作用域与命名空间（LEGB）",[143,10545,10546],{},[119,10547,10549],{"href":2378,"rel":10548},[750],"Sorting HOW TO（sorted \u002F key \u002F lambda \u002F stable sort）",[143,10551,10552],{},[119,10553,10556],{"href":10554,"rel":10555},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fcollections.html#collections.Counter",[750],"collections.Counter（频率统计）",[143,10558,10559],{},[119,10560,10563],{"href":10561,"rel":10562},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Ftimeit.html",[750],"timeit（基准测试计时）",[143,10565,10566],{},[119,10567,10570],{"href":10568,"rel":10569},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fmatplotlib",[750],"matplotlib（画图）",[790,10572,792],{},{"title":625,"searchDepth":732,"depth":732,"links":10574},[10575,10580,10581,10582,10583,10589,10590,10595,10596,10597,10598,10599,10606,10612],{"id":132,"depth":732,"text":133,"children":10576},[10577,10578,10579],{"id":137,"depth":34,"text":138},{"id":148,"depth":34,"text":149},{"id":157,"depth":34,"text":158},{"id":166,"depth":732,"text":167},{"id":230,"depth":732,"text":231},{"id":324,"depth":732,"text":325},{"id":369,"depth":732,"text":370,"children":10584},[10585,10586,10587,10588],{"id":425,"depth":34,"text":426},{"id":369,"depth":34,"text":434},{"id":442,"depth":34,"text":443},{"id":451,"depth":34,"text":452},{"id":459,"depth":732,"text":460},{"id":474,"depth":732,"text":475,"children":10591},[10592,10593,10594],{"id":481,"depth":34,"text":138},{"id":493,"depth":34,"text":149},{"id":511,"depth":34,"text":158},{"id":519,"depth":732,"text":520},{"id":529,"depth":732,"text":530},{"id":538,"depth":732,"text":539},{"id":8844,"depth":732,"text":8845},{"id":9254,"depth":732,"text":9255,"children":10600},[10601,10602,10603,10604,10605],{"id":9276,"depth":34,"text":9277},{"id":9326,"depth":34,"text":9327},{"id":9448,"depth":34,"text":9449},{"id":9617,"depth":34,"text":9618},{"id":9799,"depth":34,"text":9800},{"id":9810,"depth":732,"text":9811,"children":10607},[10608,10609,10610,10611],{"id":9825,"depth":34,"text":9826},{"id":10035,"depth":34,"text":10036},{"id":10240,"depth":34,"text":10241},{"id":10385,"depth":34,"text":10385},{"id":741,"depth":732,"text":741},"2026-09-09","Week 1 的实际产出：Day 2 复杂度实测与对照表、Day 3 object_model.md 全文、Day 4 数据结构实战的解法与总结。",{},"\u002Fwriting\u002F2026-09-09-week1-learning-outcomes","2026-09-09T10:19:09+08:00",{"title":8361,"description":10614},"week1-learning-outcomes","writing\u002F2026-09-09-week1-learning-outcomes",[837,10622,10623,10624,10625],"data-structures","big-o","benchmark","learning-outcomes","week-1","TrydEZfIsfC3BglKwV2XSdEp3rl7fWE8wKmsNwdU3ho",{"id":10629,"title":10630,"body":10631,"category":824,"date":12998,"description":12999,"extension":827,"hidden":828,"kind":829,"meta":13000,"navigation":30,"path":121,"publishedAt":13001,"seo":13002,"series":102,"slug":834,"stem":13003,"tags":13004,"unit":834,"updated":13009,"__hash__":13010},"writing\u002Fwriting\u002F2026-08-31-one-year-data-ai-plan.md","一年学习计划：从 Python 到 Data\u002FAI 系统的 52 周",{"type":112,"value":10632,"toc":12902},[10633,10636,10639,10688,10692,10696,10732,10736,10768,10772,10803,10806,10838,10841,10877,10880,10912,10918,10922,10926,10959,10963,10994,10998,11029,11033,11064,11068,11094,11099,11103,11107,11138,11142,11174,11178,11210,11214,11218,11245,11249,11275,11279,11311,11315,11341,11345,11376,11380,11384,11410,11414,11440,11444,11475,11479,11505,11509,11535,11539,11567,11572,11576,11580,11606,11610,11636,11640,11661,11665,11691,11695,11726,11731,11735,11739,11760,11764,11795,11799,11825,11829,11860,11865,11869,11873,11899,11903,11929,11933,11964,11968,12001,12006,12010,12014,12040,12044,12076,12080,12106,12111,12115,12119,12146,12150,12171,12175,12196,12200,12221,12226,12230,12233,12237,12255,12259,12276,12280,12297,12301,12318,12322,12326,12342,12346,12359,12363,12366,12427,12433,12437,12440,12445,12598,12602,12606,12623,12627,12641,12645,12659,12662,12666,12670,12673,12677,12680,12684,12687,12691,12694,12697,12701,12704,12721,12724,12728,12733,12747,12752,12766,12771,12785,12789,12833,12836,12840,12843,12875,12878,12892,12895,12899],[115,10634,10635],{},"这是 52 周 Data \u002F AI 能力体系的执行计划：执行规范、十二个阶段的主计划、每周固定执行模板、L1\u002FL2\u002FL3 标准、AI 使用分级、七个长期资产与最终验收。一句话执行原则——人负责理解、判断、推导、设计和验收；AI 负责加速实现。",[130,10637,10638],{"id":10638},"执行规范",[140,10640,10641,10647,10653,10659,10670,10676,10682],{},[143,10642,10643,10646],{},[125,10644,10645],{},"频率","：每周 3 次为标准，4 次为理想，2 次为最低。",[143,10648,10649,10652],{},[125,10650,10651],{},"单次时长","：45–120 分钟。",[143,10654,10655,10658],{},[125,10656,10657],{},"每周必须交付","：代码、实验、测试或报告。",[143,10660,10661,10663,10664,10666,10667,10669],{},[125,10662,138],{},"：能解释原理；",[125,10665,149],{},"：能独立实现；",[125,10668,158],{},"：能解释原理、失败原因与权衡。",[143,10671,10672,10675],{},[125,10673,10674],{},"AI 使用","：A0 禁用 AI；A1 查文档、解释报错；A2 辅助调试与设计；A3 开放使用 AI 完成工程实现。",[143,10677,10678,10681],{},[125,10679,10680],{},"执行原则","：允许局部未完成，不允许连续两周零学习。不因落后而无限补课。",[143,10683,10684,10687],{},[125,10685,10686],{},"数据规范","：所有个人项目使用合成数据或公开数据，不使用公司经营数据。",[130,10689,10691],{"id":10690},"phase-i-python-与编程思维week-16","Phase I · Python 与编程思维（Week 1–6）",[135,10693,10695],{"id":10694},"week-1-对象模型与数据结构","Week 1 · 对象模型与数据结构",[140,10697,10698,10703,10709,10715,10721,10726],{},[143,10699,10700,10702],{},[125,10701,824],{},"：引用、可变性、list、tuple、dict、set、hash、复杂度初识。",[143,10704,10705,10708],{},[125,10706,10707],{},"手写","：基本数据结构操作、frequency counter、KV Store。",[143,10710,10711,10714],{},[125,10712,10713],{},"实验","：比较 list、dict、set 的查找性能。",[143,10716,10717,10720],{},[125,10718,10719],{},"交付","：支持 set\u002Fget\u002Fdelete\u002Fexists\u002Fkeys 的 KV Store。",[143,10722,10723,10725],{},[125,10724,4118],{},"：能解释引用、可变性，以及 dict\u002Fset 查找通常较快的原因。",[143,10727,10728,10731],{},[125,10729,10730],{},"等级","：L2。",[135,10733,10735],{"id":10734},"week-2-函数作用域与模块化","Week 2 · 函数、作用域与模块化",[140,10737,10738,10743,10748,10754,10759,10764],{},[143,10739,10740,10742],{},[125,10741,824],{},"：函数、参数、作用域、闭包、装饰器、模块、包、类型提示。",[143,10744,10745,10747],{},[125,10746,10707],{},"：闭包、简化装饰器、自定义模块。",[143,10749,10750,10753],{},[125,10751,10752],{},"AI Build","：重构 Week 1 项目，加入 dataclass、类型提示和日志。",[143,10755,10756,10758],{},[125,10757,10719],{},"：模块化 Python 项目。",[143,10760,10761,10763],{},[125,10762,4118],{},"：能解释闭包为何可以访问外层函数的局部变量。",[143,10765,10766,10731],{},[125,10767,10730],{},[135,10769,10771],{"id":10770},"week-3-异常测试与-debug","Week 3 · 异常、测试与 Debug",[140,10773,10774,10779,10784,10789,10794,10799],{},[143,10775,10776,10778],{},[125,10777,824],{},"：exception、traceback、pytest、fixture、mock、回归测试。",[143,10780,10781,10783],{},[125,10782,10707],{},"：边界测试、异常测试、非法输入测试。",[143,10785,10786,10788],{},[125,10787,10713],{},"：人为制造类型错误、状态错误和边界错误。",[143,10790,10791,10793],{},[125,10792,10719],{},"：带测试的 KV Store。",[143,10795,10796,10798],{},[125,10797,4118],{},"：能独立定位、解释并修复常见 bug。",[143,10800,10801,10731],{},[125,10802,10730],{},[135,10804,1914],{"id":10805},"week-4-算法与复杂度",[140,10807,10808,10813,10818,10823,10828,10833],{},[143,10809,10810,10812],{},[125,10811,824],{},"：Big-O、递归、搜索、排序、二分查找、归并排序。",[143,10814,10815,10817],{},[125,10816,10707],{},"：线性搜索、二分查找、插入排序、归并排序。",[143,10819,10820,10822],{},[125,10821,10713],{},"：比较 O(n)、O(n log n)、O(n²) 的运行表现。",[143,10824,10825,10827],{},[125,10826,10719],{},"：算法实现与性能对比报告。",[143,10829,10830,10832],{},[125,10831,4118],{},"：能判断时间复杂度、空间复杂度与最坏情况。",[143,10834,10835,10837],{},[125,10836,10730],{},"：L3。",[135,10839,1541],{"id":10840},"week-5-常用数据结构",[140,10842,10843,10848,10853,10858,10863,10868,10873],{},[143,10844,10845,10847],{},[125,10846,824],{},"：Hash Table、Stack、Queue、Heap、Priority Queue。",[143,10849,10850,10852],{},[125,10851,10707],{},"：Stack、Queue、简化 MinHeap。",[143,10854,10855,10857],{},[125,10856,10752],{},"：Top-K、频率排名、LRU。",[143,10859,10860,10862],{},[125,10861,10713],{},"：比较排序与 Heap 实现 Top-K 的成本。",[143,10864,10865,10867],{},[125,10866,10719],{},"：数据结构练习集。",[143,10869,10870,10872],{},[125,10871,4118],{},"：能解释为什么 Top-K 通常不必对全部数据排序。",[143,10874,10875,10731],{},[125,10876,10730],{},[135,10878,858],{"id":10879},"week-6-图算法与数据处理工具",[140,10881,10882,10887,10892,10897,10902,10907],{},[143,10883,10884,10886],{},[125,10885,824],{},"：Graph、BFS、DFS、CSV、JSON、NumPy、pandas 入门。",[143,10888,10889,10891],{},[125,10890,10707],{},"：BFS、DFS、连通分量。",[143,10893,10894,10896],{},[125,10895,10752],{},"：实现 Reader → Processor → Storage → CLI。",[143,10898,10899,10901],{},[125,10900,10719],{},"：Python Engineering Toolkit 第一版。",[143,10903,10904,10906],{},[125,10905,4118],{},"：AI-Off 实现 BFS\u002FDFS，并能独立修改项目模块。",[143,10908,10909,10911],{},[125,10910,10730],{},"：L2\u002FL3。",[115,10913,10914,10917],{},[125,10915,10916],{},"阶段产物","：Asset 1 · Python Engineering Toolkit",[130,10919,10921],{"id":10920},"phase-ii-sqlpostgresql-与数据分析week-711","Phase II · SQL、PostgreSQL 与数据分析（Week 7–11）",[135,10923,10925],{"id":10924},"week-7-sql-core","Week 7 · SQL Core",[140,10927,10928,10933,10939,10945,10950,10955],{},[143,10929,10930,10932],{},[125,10931,824],{},"：关系模型、主外键、NULL、SELECT、WHERE、GROUP BY、HAVING。",[143,10934,10935,10938],{},[125,10936,10937],{},"练习","：至少 20 道 SQL 题。",[143,10940,10941,10944],{},[125,10942,10943],{},"Build","：创建合成客户、产品、订单、合同、物流数据库。",[143,10946,10947,10949],{},[125,10948,10719],{},"：数据库 Schema 与基础查询集。",[143,10951,10952,10954],{},[125,10953,4118],{},"：能独立完成过滤、聚合、分组统计。",[143,10956,10957,10731],{},[125,10958,10730],{},[135,10960,10962],{"id":10961},"week-8-join-与窗口函数","Week 8 · JOIN 与窗口函数",[140,10964,10965,10970,10975,10980,10985,10990],{},[143,10966,10967,10969],{},[125,10968,824],{},"：JOIN、CTE、子查询、Window Function。",[143,10971,10972,10974],{},[125,10973,10937],{},"：Top-N、排名、累计值、移动平均、最新记录。",[143,10976,10977,10979],{},[125,10978,10713],{},"：制造 1:1、1:N、N:N 关系，观察 JOIN 后的行数。",[143,10981,10982,10984],{},[125,10983,10719],{},"：至少 20 道 SQL 题及答案。",[143,10986,10987,10989],{},[125,10988,4118],{},"：能预测重复行、数据丢失和行数膨胀。",[143,10991,10992,10837],{},[125,10993,10730],{},[135,10995,10997],{"id":10996},"week-9-数据建模","Week 9 · 数据建模",[140,10999,11000,11005,11010,11015,11020,11025],{},[143,11001,11002,11004],{},[125,11003,824],{},"：函数依赖、1NF\u002F2NF\u002F3NF、规范化、反规范化。",[143,11006,11007,11009],{},[125,11008,10943],{},"：设计客户、合同、订单、产品、物流模型。",[143,11011,11012,11014],{},[125,11013,10713],{},"：从超级大表开始，分析更新、插入、删除异常，再重新建模。",[143,11016,11017,11019],{},[125,11018,10719],{},"：ER 图、数据字典、建模说明。",[143,11021,11022,11024],{},[125,11023,4118],{},"：能解释规范化、冗余和查询性能的权衡。",[143,11026,11027,10837],{},[125,11028,10730],{},[135,11030,11032],{"id":11031},"week-10-索引执行计划与事务","Week 10 · 索引、执行计划与事务",[140,11034,11035,11040,11045,11050,11055,11060],{},[143,11036,11037,11039],{},[125,11038,824],{},"：B+Tree 原理、Index、EXPLAIN、事务、隔离级别。",[143,11041,11042,11044],{},[125,11043,10943],{},"：生成测试数据并编写查询基准。",[143,11046,11047,11049],{},[125,11048,10713],{},"：比较无索引、单列索引、联合索引的效果。",[143,11051,11052,11054],{},[125,11053,10719],{},"：至少 3 组 SQL 优化实验。",[143,11056,11057,11059],{},[125,11058,4118],{},"：能提出慢查询假设，并用执行计划验证。",[143,11061,11062,10837],{},[125,11063,10730],{},[135,11065,11067],{"id":11066},"week-11-sql-与数据分析项目","Week 11 · SQL 与数据分析项目",[140,11069,11070,11075,11080,11085,11090],{},[143,11071,11072,11074],{},[125,11073,824],{},"：pandas 分组、合并、缺失值处理、数据清洗、Matplotlib。",[143,11076,11077,11079],{},[125,11078,10943],{},"：PostgreSQL → SQL → pandas → 图表\u002F仪表盘。",[143,11081,11082,11084],{},[125,11083,10719],{},"：20 个分析问题、5 个复杂 SQL、3 个窗口函数场景、分析报告。",[143,11086,11087,11089],{},[125,11088,4118],{},"：能从业务问题独立完成数据查询、解释和可视化。",[143,11091,11092,10837],{},[125,11093,10730],{},[115,11095,11096,11098],{},[125,11097,10916],{},"：Asset 2 · Database & Analytics",[130,11100,11102],{"id":11101},"phase-iii-工程化与数据库原理week-1214","Phase III · 工程化与数据库原理（Week 12–14）",[135,11104,11106],{"id":11105},"week-12-linuxgit-与工程基础","Week 12 · Linux、Git 与工程基础",[140,11108,11109,11114,11119,11124,11129,11134],{},[143,11110,11111,11113],{},[125,11112,824],{},"：Shell、进程、权限、环境变量、管道、日志、Git 分支与合并。",[143,11115,11116,11118],{},[125,11117,10937],{},"：grep\u002Ffind\u002Fawk\u002Fsed\u002Fps\u002Fssh\u002Ftar\u002Fchmod。",[143,11120,11121,11123],{},[125,11122,10943],{},"：为项目建立虚拟环境、依赖文件、日志与测试。",[143,11125,11126,11128],{},[125,11127,10719],{},"：可在干净环境复现的项目。",[143,11130,11131,11133],{},[125,11132,4118],{},"：能独立查进程、查日志、查文件、处理 Git 冲突。",[143,11135,11136,10731],{},[125,11137,10730],{},[135,11139,11141],{"id":11140},"week-13-api-与-docker","Week 13 · API 与 Docker",[140,11143,11144,11149,11154,11160,11165,11170],{},[143,11145,11146,11148],{},[125,11147,824],{},"：HTTP、REST、JSON、FastAPI、Docker、Compose。",[143,11150,11151,11153],{},[125,11152,10752],{},"：将数据分析项目封装成 API。",[143,11155,11156,11159],{},[125,11157,11158],{},"Human Core","：理解 Request → Application → Database → Response。",[143,11161,11162,11164],{},[125,11163,10719],{},"：可运行的只读数据 API。",[143,11166,11167,11169],{},[125,11168,4118],{},"：能解释请求流、容器、网络和数据持久化。",[143,11171,11172,10731],{},[125,11173,10730],{},[135,11175,11177],{"id":11176},"week-14-数据库内部机制","Week 14 · 数据库内部机制",[140,11179,11180,11185,11190,11196,11201,11206],{},[143,11181,11182,11184],{},[125,11183,824],{},"：B+Tree、Page、Buffer、事务、MVCC、锁、查询计划。",[143,11186,11187,11189],{},[125,11188,10713],{},"：PostgreSQL 多会话事务实验，比较索引与全表扫描。",[143,11191,11192,11195],{},[125,11193,11194],{},"选做","：简化 B+Tree 插入与查询。",[143,11197,11198,11200],{},[125,11199,10719],{},"：数据库机制实验报告。",[143,11202,11203,11205],{},[125,11204,4118],{},"：能解释事务可见性、索引成本和执行路径。",[143,11207,11208,10837],{},[125,11209,10730],{},[130,11211,11213],{"id":11212},"phase-iv-概率统计与数学week-1519","Phase IV · 概率统计与数学（Week 15–19）",[135,11215,11217],{"id":11216},"week-15-概率论","Week 15 · 概率论",[140,11219,11220,11225,11231,11236,11241],{},[143,11221,11222,11224],{},[125,11223,824],{},"：条件概率、独立性、贝叶斯公式、全概率。",[143,11226,11227,11230],{},[125,11228,11229],{},"手算","：联合概率与条件概率。",[143,11232,11233,11235],{},[125,11234,10943],{},"：Monte Carlo 模拟。",[143,11237,11238,11240],{},[125,11239,10719],{},"：概率计算练习与模拟报告。",[143,11242,11243,10837],{},[125,11244,10730],{},[135,11246,11248],{"id":11247},"week-16-随机变量与分布","Week 16 · 随机变量与分布",[140,11250,11251,11256,11261,11266,11271],{},[143,11252,11253,11255],{},[125,11254,824],{},"：PMF、PDF、CDF、期望、方差、协方差、常见分布。",[143,11257,11258,11260],{},[125,11259,11229],{},"：期望、方差与协方差。",[143,11262,11263,11265],{},[125,11264,10943],{},"：分布采样器。",[143,11267,11268,11270],{},[125,11269,10719],{},"：理论值与模拟值对比。",[143,11272,11273,10911],{},[125,11274,10730],{},[135,11276,11278],{"id":11277},"week-17-统计推断","Week 17 · 统计推断",[140,11280,11281,11286,11292,11297,11302,11307],{},[143,11282,11283,11285],{},[125,11284,824],{},"：MLE、MAP、置信区间、假设检验、p-value、Bootstrap、A\u002FB Test。",[143,11287,11288,11291],{},[125,11289,11290],{},"推导","：高斯分布 MLE。",[143,11293,11294,11296],{},[125,11295,10713],{},"：抽样偏差、小样本和多重检验。",[143,11298,11299,11301],{},[125,11300,10719],{},"：完整统计推断实验。",[143,11303,11304,11306],{},[125,11305,4118],{},"：能准确解释 p-value 的含义与局限。",[143,11308,11309,10837],{},[125,11310,10730],{},[135,11312,11314],{"id":11313},"week-18-线性代数","Week 18 · 线性代数",[140,11316,11317,11322,11327,11332,11337],{},[143,11318,11319,11321],{},[125,11320,824],{},"：向量、矩阵、点积、线性变换、秩、基、投影、最小二乘。",[143,11323,11324,11326],{},[125,11325,11229],{},"：Ax、AᵀA、小规模最小二乘。",[143,11328,11329,11331],{},[125,11330,10943],{},"：NumPy 线性变换实验。",[143,11333,11334,11336],{},[125,11335,10719],{},"：计算过程与几何解释。",[143,11338,11339,10837],{},[125,11340,10730],{},[135,11342,11344],{"id":11343},"week-19-微积分与优化","Week 19 · 微积分与优化",[140,11346,11347,11352,11357,11362,11367,11372],{},[143,11348,11349,11351],{},[125,11350,824],{},"：导数、偏导、梯度、链式法则、梯度下降。",[143,11353,11354,11356],{},[125,11355,11229],{},"：线性回归梯度。",[143,11358,11359,11361],{},[125,11360,10943],{},"：实现 Gradient Descent。",[143,11363,11364,11366],{},[125,11365,10713],{},"：比较不同学习率对收敛的影响。",[143,11368,11369,11371],{},[125,11370,10719],{},"：梯度推导与收敛曲线。",[143,11373,11374,10837],{},[125,11375,10730],{},[130,11377,11379],{"id":11378},"phase-v-机器学习week-2025","Phase V · 机器学习（Week 20–25）",[135,11381,11383],{"id":11382},"week-20-linear-regression","Week 20 · Linear Regression",[140,11385,11386,11391,11396,11401,11406],{},[143,11387,11388,11390],{},[125,11389,824],{},"：最小二乘、Normal Equation、Gradient Descent。",[143,11392,11393,11395],{},[125,11394,10707],{},"：NumPy 线性回归。",[143,11397,11398,11400],{},[125,11399,10713],{},"：比较收敛速度与数值稳定性。",[143,11402,11403,11405],{},[125,11404,10719],{},"：回归实验报告。",[143,11407,11408,10837],{},[125,11409,10730],{},[135,11411,11413],{"id":11412},"week-21-logistic-regression","Week 21 · Logistic Regression",[140,11415,11416,11421,11426,11431,11436],{},[143,11417,11418,11420],{},[125,11419,824],{},"：Sigmoid、Log Loss、交叉熵、L1\u002FL2 正则化。",[143,11422,11423,11425],{},[125,11424,10707],{},"：二分类核心计算。",[143,11427,11428,11430],{},[125,11429,10713],{},"：比较正则化前后的参数变化。",[143,11432,11433,11435],{},[125,11434,10719],{},"：二分类模型与实验报告。",[143,11437,11438,10837],{},[125,11439,10730],{},[135,11441,11443],{"id":11442},"week-22-树模型","Week 22 · 树模型",[140,11445,11446,11451,11456,11461,11466,11471],{},[143,11447,11448,11450],{},[125,11449,824],{},"：Decision Tree、Random Forest、Boosting。",[143,11452,11453,11455],{},[125,11454,10707],{},"：简化 Decision Tree。",[143,11457,11458,11460],{},[125,11459,10752],{},"：使用 scikit-learn 训练 Random Forest 与 Boosting 模型。",[143,11462,11463,11465],{},[125,11464,10719],{},"：三类模型比较。",[143,11467,11468,11470],{},[125,11469,4118],{},"：能解释模型选择依据。",[143,11472,11473,10911],{},[125,11474,10730],{},[135,11476,11478],{"id":11477},"week-23-泛化与评估","Week 23 · 泛化与评估",[140,11480,11481,11486,11491,11496,11501],{},[143,11482,11483,11485],{},[125,11484,824],{},"：Train\u002FValidation\u002FTest、交叉验证、Bias\u002FVariance、Data Leakage、Distribution Shift。",[143,11487,11488,11490],{},[125,11489,10707],{},"：评估指标与简化交叉验证。",[143,11492,11493,11495],{},[125,11494,10713],{},"：主动制造数据泄漏并排查。",[143,11497,11498,11500],{},[125,11499,10719],{},"：模型评估实验。",[143,11502,11503,10837],{},[125,11504,10730],{},[135,11506,11508],{"id":11507},"week-24-特征工程与训练流水线","Week 24 · 特征工程与训练流水线",[140,11510,11511,11516,11521,11526,11531],{},[143,11512,11513,11515],{},[125,11514,824],{},"：缺失值、异常值、类别编码、特征选择、Pipeline。",[143,11517,11518,11520],{},[125,11519,10943],{},"：建立完整训练和验证流程。",[143,11522,11523,11525],{},[125,11524,10719],{},"：可重复运行的 ML Pipeline。",[143,11527,11528,11530],{},[125,11529,4118],{},"：能解释预处理与数据泄漏的关系。",[143,11532,11533,10731],{},[125,11534,10730],{},[135,11536,11538],{"id":11537},"week-25-ml-综合项目","Week 25 · ML 综合项目",[140,11540,11541,11547,11553,11558,11563],{},[143,11542,11543,11546],{},[125,11544,11545],{},"流程","：Data → Cleaning → Feature → Model → Validation → Error Analysis → Report。",[143,11548,11549,11552],{},[125,11550,11551],{},"要求","：比较 Linear、Logistic、Tree、Random Forest、Boosting 中适用的模型。",[143,11554,11555,11557],{},[125,11556,10719],{},"：代码、实验记录、指标、错误分析和报告。",[143,11559,11560,11562],{},[125,11561,4118],{},"：能回答 Why、Why not、What failed、What next。",[143,11564,11565,10837],{},[125,11566,10730],{},[115,11568,11569,11571],{},[125,11570,10916],{},"：Asset 4 · ML System",[130,11573,11575],{"id":11574},"phase-vi-数据工程week-2630","Phase VI · 数据工程（Week 26–30）",[135,11577,11579],{"id":11578},"week-26-etl-与数据质量","Week 26 · ETL 与数据质量",[140,11581,11582,11587,11592,11597,11602],{},[143,11583,11584,11586],{},[125,11585,824],{},"：Extract、Transform、Load、数据校验、日志、错误处理。",[143,11588,11589,11591],{},[125,11590,10943],{},"：CSV\u002FJSON → 清洗 → PostgreSQL。",[143,11593,11594,11596],{},[125,11595,10719],{},"：可重复运行的 ETL 脚本。",[143,11598,11599,11601],{},[125,11600,4118],{},"：能处理非法数据、缺失值与失败记录。",[143,11603,11604,10731],{},[125,11605,10730],{},[135,11607,11609],{"id":11608},"week-27-数据仓库","Week 27 · 数据仓库",[140,11611,11612,11617,11622,11627,11632],{},[143,11613,11614,11616],{},[125,11615,824],{},"：OLTP\u002FOLAP、ODS、DWD、DWS、ADS、事实表、维度表、星型模型。",[143,11618,11619,11621],{},[125,11620,10943],{},"：建立简化数仓分层。",[143,11623,11624,11626],{},[125,11625,10719],{},"：数据流图、表结构和分析 SQL。",[143,11628,11629,11631],{},[125,11630,4118],{},"：能解释为什么分析系统不一定直接查询业务数据库。",[143,11633,11634,10731],{},[125,11635,10730],{},[135,11637,11639],{"id":11638},"week-28-parquet-与列式存储","Week 28 · Parquet 与列式存储",[140,11641,11642,11647,11652,11657],{},[143,11643,11644,11646],{},[125,11645,824],{},"：行存储、列存储、压缩、分区、Predicate Pushdown。",[143,11648,11649,11651],{},[125,11650,10713],{},"：比较 CSV 与 Parquet 的大小、读取时间和列读取效率。",[143,11653,11654,11656],{},[125,11655,10719],{},"：性能对比报告。",[143,11658,11659,10837],{},[125,11660,10730],{},[135,11662,11664],{"id":11663},"week-29-工作流调度","Week 29 · 工作流调度",[140,11666,11667,11672,11677,11682,11687],{},[143,11668,11669,11671],{},[125,11670,824],{},"：DAG、依赖、重试、Backfill、任务状态。",[143,11673,11674,11676],{},[125,11675,10943],{},"：使用 Airflow 或等价调度器编排 ETL。",[143,11678,11679,11681],{},[125,11680,10719],{},"：可重跑的调度工作流。",[143,11683,11684,11686],{},[125,11685,4118],{},"：能解释任务失败、重试与重复执行的影响。",[143,11688,11689,10731],{},[125,11690,10730],{},[135,11692,11694],{"id":11693},"week-30-幂等与增量处理","Week 30 · 幂等与增量处理",[140,11696,11697,11702,11707,11712,11717,11722],{},[143,11698,11699,11701],{},[125,11700,824],{},"：Idempotency、增量加载、重复数据、数据质量检查。",[143,11703,11704,11706],{},[125,11705,10713],{},"：制造重复输入、任务失败、部分写入。",[143,11708,11709,11711],{},[125,11710,11194],{},"：将部分处理逻辑迁移到 Spark。",[143,11713,11714,11716],{},[125,11715,10719],{},"：有数据质量检查与恢复机制的 Pipeline。",[143,11718,11719,11721],{},[125,11720,4118],{},"：能解释为什么重试可能产生重复数据。",[143,11723,11724,10837],{},[125,11725,10730],{},[115,11727,11728,11730],{},[125,11729,10916],{},"：Asset 3 · Data Engineering Pipeline",[130,11732,11734],{"id":11733},"phase-vii-深度学习与-transformerweek-3134","Phase VII · 深度学习与 Transformer（Week 31–34）",[135,11736,11738],{"id":11737},"week-31-神经网络","Week 31 · 神经网络",[140,11740,11741,11746,11751,11756],{},[143,11742,11743,11745],{},[125,11744,824],{},"：Tensor、Forward、Loss、Backward、Optimizer、Autograd。",[143,11747,11748,11750],{},[125,11749,10707],{},"：NumPy 两层 MLP。",[143,11752,11753,11755],{},[125,11754,10719],{},"：前向传播、反向传播和梯度验证。",[143,11757,11758,10837],{},[125,11759,10730],{},[135,11761,11763],{"id":11762},"week-32-attention","Week 32 · Attention",[140,11765,11766,11771,11776,11781,11786,11791],{},[143,11767,11768,11770],{},[125,11769,824],{},"：Query、Key、Value、点积、缩放、Softmax、加权求和。",[143,11772,11773,11775],{},[125,11774,10707],{},"：NumPy Attention。",[143,11777,11778,11780],{},[125,11779,10713],{},"：改变序列长度、维度和缩放参数。",[143,11782,11783,11785],{},[125,11784,10719],{},"：Attention 实现与实验记录。",[143,11787,11788,11790],{},[125,11789,4118],{},"：能解释公式与张量维度。",[143,11792,11793,10837],{},[125,11794,10730],{},[135,11796,11798],{"id":11797},"week-33-transformer-block","Week 33 · Transformer Block",[140,11800,11801,11806,11811,11816,11821],{},[143,11802,11803,11805],{},[125,11804,824],{},"：Multi-Head Attention、FFN、Residual、LayerNorm、位置编码、Causal Mask。",[143,11807,11808,11810],{},[125,11809,10943],{},"：实现简化 Transformer Block。",[143,11812,11813,11815],{},[125,11814,10719],{},"：结构代码与架构说明。",[143,11817,11818,11820],{},[125,11819,4118],{},"：能从输入到输出解释完整计算流程。",[143,11822,11823,10837],{},[125,11824,10730],{},[135,11826,11828],{"id":11827},"week-34-tiny-language-model","Week 34 · Tiny Language Model",[140,11830,11831,11836,11841,11846,11851,11856],{},[143,11832,11833,11835],{},[125,11834,824],{},"：Tokenizer、Embedding、Logits、Softmax、Cross Entropy、Next-token Prediction。",[143,11837,11838,11840],{},[125,11839,10943],{},"：训练一个极小语言模型，或完成等价的可复现实验。",[143,11842,11843,11845],{},[125,11844,10713],{},"：改变模型大小、上下文长度和学习率。",[143,11847,11848,11850],{},[125,11849,10719],{},"：训练日志与推理实验。",[143,11852,11853,11855],{},[125,11854,4118],{},"：能解释训练与推理的主要差异。",[143,11857,11858,10911],{},[125,11859,10730],{},[115,11861,11862,11864],{},[125,11863,10916],{},"：Asset 5 · Mini Transformer",[130,11866,11868],{"id":11867},"phase-viii-rag-与-llm-engineeringweek-3538","Phase VIII · RAG 与 LLM Engineering（Week 35–38）",[135,11870,11872],{"id":11871},"week-35-embedding-与向量检索","Week 35 · Embedding 与向量检索",[140,11874,11875,11880,11885,11890,11895],{},[143,11876,11877,11879],{},[125,11878,824],{},"：Embedding、Chunk、Cosine Similarity、Top-K、Vector DB。",[143,11881,11882,11884],{},[125,11883,10707],{},"：余弦相似度和 Top-K。",[143,11886,11887,11889],{},[125,11888,10943],{},"：Document → Chunk → Embedding → Search。",[143,11891,11892,11894],{},[125,11893,10719],{},"：本地文档检索原型。",[143,11896,11897,10731],{},[125,11898,10730],{},[135,11900,11902],{"id":11901},"week-36-bm25-与混合检索","Week 36 · BM25 与混合检索",[140,11904,11905,11910,11915,11920,11925],{},[143,11906,11907,11909],{},[125,11908,824],{},"：Keyword Search、BM25、Dense Retrieval、Hybrid Retrieval、Reranker。",[143,11911,11912,11914],{},[125,11913,10713],{},"：比较关键词、向量与混合检索。",[143,11916,11917,11919],{},[125,11918,10719],{},"：检索对比报告。",[143,11921,11922,11924],{},[125,11923,4118],{},"：能解释为什么纯向量检索不一定最好。",[143,11926,11927,10837],{},[125,11928,10730],{},[135,11930,11932],{"id":11931},"week-37-rag-评估","Week 37 · RAG 评估",[140,11934,11935,11940,11945,11950,11955,11960],{},[143,11936,11937,11939],{},[125,11938,824],{},"：Recall@K、Precision@K、MRR、NDCG、Faithfulness、检索失败。",[143,11941,11942,11944],{},[125,11943,10943],{},"：建立固定测试集，建议先从 30–50 个问题开始。",[143,11946,11947,11949],{},[125,11948,10713],{},"：比较分块、检索参数与检索策略。",[143,11951,11952,11954],{},[125,11953,10719],{},"：评测数据集和指标报告。",[143,11956,11957,11959],{},[125,11958,4118],{},"：每次优化均记录修改前后结果。",[143,11961,11962,10837],{},[125,11963,10730],{},[135,11965,11967],{"id":11966},"week-38-production-rag","Week 38 · Production RAG",[140,11969,11970,11975,11981,11987,11992,11997],{},[143,11971,11972,11974],{},[125,11973,10943],{},"：Parser → Chunker → Embedding → Retriever → Reranker → LLM → Citation。",[143,11976,11977,11980],{},[125,11978,11979],{},"增加","：Metadata Filter、Logging、Cache、Error Handling、引用校验。",[143,11982,11983,11986],{},[125,11984,11985],{},"AI","：A3。",[143,11988,11989,11991],{},[125,11990,10719],{},"：可评估、可复现的 RAG 应用。",[143,11993,11994,11996],{},[125,11995,4118],{},"：能解释完整链路及主要失败模式。",[143,11998,11999,10911],{},[125,12000,10730],{},[115,12002,12003,12005],{},[125,12004,10916],{},"：Asset 6 · RAG System",[130,12007,12009],{"id":12008},"phase-ix-agent-与系统可靠性week-3941","Phase IX · Agent 与系统可靠性（Week 39–41）",[135,12011,12013],{"id":12012},"week-39-tool-calling","Week 39 · Tool Calling",[140,12015,12016,12021,12026,12031,12036],{},[143,12017,12018,12020],{},[125,12019,824],{},"：Function Calling、Structured Output、Schema、Validation。",[143,12022,12023,12025],{},[125,12024,10943],{},"：LLM → Decision → Tool → Result → LLM。",[143,12027,12028,12030],{},[125,12029,10719],{},"：一个受限工具调用程序。",[143,12032,12033,12035],{},[125,12034,4118],{},"：能解释为什么必须校验模型输出。",[143,12037,12038,10731],{},[125,12039,10730],{},[135,12041,12043],{"id":12042},"week-40-agent-workflow","Week 40 · Agent Workflow",[140,12045,12046,12051,12056,12062,12067,12072],{},[143,12047,12048,12050],{},[125,12049,824],{},"：State、Loop、Planning、Observation、Memory。",[143,12052,12053,12055],{},[125,12054,10943],{},"：只读 SQL Agent。",[143,12057,12058,12061],{},[125,12059,12060],{},"安全","：Read-only、SQL Allowlist、Timeout、Result Limit、Human Approval。",[143,12063,12064,12066],{},[125,12065,10719],{},"：受限 SQL Agent。",[143,12068,12069,12071],{},[125,12070,4118],{},"：能解释工具权限和数据访问边界。",[143,12073,12074,10837],{},[125,12075,10730],{},[135,12077,12079],{"id":12078},"week-41-reliability-与-observability","Week 41 · Reliability 与 Observability",[140,12081,12082,12087,12092,12097,12102],{},[143,12083,12084,12086],{},[125,12085,824],{},"：Retry、Timeout、Fallback、Cache、Tracing、Latency、Cost、Prompt Injection。",[143,12088,12089,12091],{},[125,12090,10713],{},"：制造工具失败、错误检索、超时和恶意输入。",[143,12093,12094,12096],{},[125,12095,10719],{},"：故障演练报告与可靠性改进。",[143,12098,12099,12101],{},[125,12100,4118],{},"：能解释为什么 Agent 能运行不代表 Agent 可靠。",[143,12103,12104,10837],{},[125,12105,10730],{},[115,12107,12108,12110],{},[125,12109,10916],{},"：Asset 6 · Agent System",[130,12112,12114],{"id":12113},"phase-x-综合-dataai-系统week-4245","Phase X · 综合 Data\u002FAI 系统（Week 42–45）",[135,12116,12118],{"id":12117},"week-42-需求与系统设计","Week 42 · 需求与系统设计",[140,12120,12121,12127,12132,12137,12142],{},[143,12122,12123,12126],{},[125,12124,12125],{},"任务","：确定问题、用户、数据、指标、系统边界。",[143,12128,12129,12131],{},[125,12130,10943],{},"：绘制架构图，设计数据库与 API。",[143,12133,12134,12136],{},[125,12135,10719],{},"：需求文档、数据字典、Architecture Decision Record。",[143,12138,12139,12141],{},[125,12140,4118],{},"：能解释每个组件的必要性与替代方案。",[143,12143,12144,10837],{},[125,12145,10730],{},[135,12147,12149],{"id":12148},"week-43-数据层与分析层","Week 43 · 数据层与分析层",[140,12151,12152,12157,12162,12167],{},[143,12153,12154,12156],{},[125,12155,10943],{},"：数据导入 → ETL → PostgreSQL → 分析 SQL。",[143,12158,12159,12161],{},[125,12160,11979],{},"：数据质量检查、日志、测试。",[143,12163,12164,12166],{},[125,12165,10719],{},"：可复现的数据处理链路。",[143,12168,12169,10731],{},[125,12170,10730],{},[135,12172,12174],{"id":12173},"week-44-ml-或-rag-核心功能","Week 44 · ML 或 RAG 核心功能",[140,12176,12177,12182,12187,12192],{},[143,12178,12179,12181],{},[125,12180,12125],{},"：选择 ML、RAG，或在核心链路完成后再增加第二项。",[143,12183,12184,12186],{},[125,12185,11551],{},"：有基线、有测试集、有评估指标。",[143,12188,12189,12191],{},[125,12190,10719],{},"：核心功能模块及实验报告。",[143,12193,12194,10911],{},[125,12195,10730],{},[135,12197,12199],{"id":12198},"week-45-api测试与部署","Week 45 · API、测试与部署",[140,12201,12202,12207,12212,12217],{},[143,12203,12204,12206],{},[125,12205,10943],{},"：API、简单 UI、日志、异常处理、Docker 部署。",[143,12208,12209,12211],{},[125,12210,5679],{},"：单元测试、集成测试、关键失败场景。",[143,12213,12214,12216],{},[125,12215,10719],{},"：能从空环境复现的应用。",[143,12218,12219,10731],{},[125,12220,10730],{},[115,12222,12223,12225],{},[125,12224,10916],{},"：Asset 7 · Integrated Data\u002FAI System",[130,12227,12229],{"id":12228},"phase-xi-定向强化week-4649","Phase XI · 定向强化（Week 46–49）",[115,12231,12232],{},"根据实际短板分配时间，不要求每项平均投入。",[135,12234,12236],{"id":12235},"week-46-sql-与数据库强化","Week 46 · SQL 与数据库强化",[140,12238,12239,12245,12250],{},[143,12240,12241,12244],{},[125,12242,12243],{},"复习","：JOIN、窗口函数、建模、索引与事务。",[143,12246,12247,12249],{},[125,12248,10937],{},"：独立完成 10–20 道综合 SQL 题。",[143,12251,12252,12254],{},[125,12253,10719],{},"：SQL 错题集与查询优化记录。",[135,12256,12258],{"id":12257},"week-47-python-与算法强化","Week 47 · Python 与算法强化",[140,12260,12261,12266,12271],{},[143,12262,12263,12265],{},[125,12264,12243],{},"：函数、数据结构、复杂度、BFS\u002FDFS、二分查找。",[143,12267,12268,12270],{},[125,12269,10937],{},"：完成 AI-Off 编程练习。",[143,12272,12273,12275],{},[125,12274,10719],{},"：核心算法实现与错误记录。",[135,12277,12279],{"id":12278},"week-48-数学与-ml-强化","Week 48 · 数学与 ML 强化",[140,12281,12282,12287,12292],{},[143,12283,12284,12286],{},[125,12285,12243],{},"：概率、最小二乘、MLE、梯度、过拟合、评估。",[143,12288,12289,12291],{},[125,12290,10937],{},"：独立推导核心公式。",[143,12293,12294,12296],{},[125,12295,10719],{},"：数学与 ML 答辩记录。",[135,12298,12300],{"id":12299},"week-49-系统可靠性强化","Week 49 · 系统可靠性强化",[140,12302,12303,12308,12313],{},[143,12304,12305,12307],{},[125,12306,12243],{},"：测试、部署、数据质量、RAG 评估和安全。",[143,12309,12310,12312],{},[125,12311,10937],{},"：主动制造故障并修复。",[143,12314,12315,12317],{},[125,12316,10719],{},"：故障演练与系统改进报告。",[130,12319,12321],{"id":12320},"phase-xii-最终验收week-5052","Phase XII · 最终验收（Week 50–52）",[135,12323,12325],{"id":12324},"week-50-项目验收","Week 50 · 项目验收",[140,12327,12328,12331,12334,12337],{},[143,12329,12330],{},"从空环境运行完整项目。",[143,12332,12333],{},"执行测试，记录成功率、错误和性能。",[143,12335,12336],{},"检查数据来源、许可和安全边界。",[143,12338,12339,12341],{},[125,12340,10719],{},"：可复现的最终版本。",[135,12343,12345],{"id":12344},"week-51-技术答辩与作品集","Week 51 · 技术答辩与作品集",[140,12347,12348,12351,12354],{},[143,12349,12350],{},"整理 README、架构图、技术决策记录和实验报告。",[143,12352,12353],{},"独立解释系统数据流、核心机制、失败场景与设计取舍。",[143,12355,12356,12358],{},[125,12357,10719],{},"：完整作品集。",[135,12360,12362],{"id":12361},"week-52-ai-off-最终抽检","Week 52 · AI-Off 最终抽检",[115,12364,12365],{},"随机完成以下任务：",[4640,12367,12368,12373,12379,12385,12391,12397,12403,12409,12415,12421],{},[143,12369,12370,12372],{},[125,12371,837],{},"：实现一个 LRU。",[143,12374,12375,12378],{},[125,12376,12377],{},"Algorithm","：实现 BFS、DFS 或 Binary Search。",[143,12380,12381,12384],{},[125,12382,12383],{},"SQL","：根据业务问题写复杂查询。",[143,12386,12387,12390],{},[125,12388,12389],{},"Database","：解释索引、全表扫描和事务。",[143,12392,12393,12396],{},[125,12394,12395],{},"Math","：推导线性回归核心公式。",[143,12398,12399,12402],{},[125,12400,12401],{},"ML","：解释过拟合与模型评估。",[143,12404,12405,12408],{},[125,12406,12407],{},"Transformer","：解释 Attention 的计算流程。",[143,12410,12411,12414],{},[125,12412,12413],{},"RAG","：分析检索 Recall 下降的原因。",[143,12416,12417,12420],{},[125,12418,12419],{},"Agent","：处理工具失败与权限问题。",[143,12422,12423,12426],{},[125,12424,12425],{},"System","：解释综合项目的架构选择。",[115,12428,12429,12432],{},[125,12430,12431],{},"最终交付","：项目仓库、技术文档、实验报告、AI-Off 验收记录和下一阶段计划。",[130,12434,12436],{"id":12435},"part-iii-每周固定执行模板","Part III · 每周固定执行模板",[115,12438,12439],{},"每周复制一次以下模板。",[115,12441,12442],{},[125,12443,12444],{},"Week XX · 本周主题",[4640,12446,12447,12462,12478,12493,12508,12526,12541,12563,12582],{},[143,12448,12449,12451],{},[125,12450,132],{},[140,12452,12453,12456,12459],{},[143,12454,12455],{},"☐ L1：理解核心概念。",[143,12457,12458],{},"☐ L2：独立实现核心任务。",[143,12460,12461],{},"☐ L3：完成实验并解释原理、失败原因与权衡。",[143,12463,12464,12467],{},[125,12465,12466],{},"Core Knowledge",[140,12468,12469,12472,12475],{},[143,12470,12471],{},"本周核心知识：",[143,12473,12474],{},"官方文档或课程：",[143,12476,12477],{},"必须掌握的概念：",[143,12479,12480,12482],{},[125,12481,11158],{},[140,12483,12484,12487,12490],{},[143,12485,12486],{},"☐ 独立手写：",[143,12488,12489],{},"☐ 独立实现：",[143,12491,12492],{},"☐ 数学推导或复杂度分析：",[143,12494,12495,12497],{},[125,12496,10752],{},[140,12498,12499,12502,12505],{},[143,12500,12501],{},"☐ AI 辅助实现：",[143,12503,12504],{},"☐ Code Review \u002F Debug：",[143,12506,12507],{},"☐ 测试与重构：",[143,12509,12510,12512],{},[125,12511,434],{},[140,12513,12514,12517,12520,12523],{},[143,12515,12516],{},"Hypothesis：",[143,12518,12519],{},"Experiment：",[143,12521,12522],{},"Result：",[143,12524,12525],{},"Conclusion：",[143,12527,12528,12530],{},[125,12529,459],{},[140,12531,12532,12535,12538],{},[143,12533,12534],{},"对应知识点：",[143,12536,12537],{},"练习题：",[143,12539,12540],{},"错误原因：",[143,12542,12543,12546],{},[125,12544,12545],{},"Acceptance",[140,12547,12548,12551,12554,12557,12560],{},[143,12549,12550],{},"☐ 能解释。",[143,12552,12553],{},"☐ 能独立实现。",[143,12555,12556],{},"☐ 能解释 Why。",[143,12558,12559],{},"☐ 能解释 Failure。",[143,12561,12562],{},"☐ 能解释 Trade-off。",[143,12564,12565,12568],{},[125,12566,12567],{},"本周复盘",[140,12569,12570,12573,12576,12579],{},[143,12571,12572],{},"已完成：",[143,12574,12575],{},"未完成：",[143,12577,12578],{},"最大问题：",[143,12580,12581],{},"下周调整：",[143,12583,12584,12587],{},[125,12585,12586],{},"Final Level",[140,12588,12589,12592,12595],{},[143,12590,12591],{},"L1 \u002F L2 \u002F L3",[143,12593,12594],{},"证据：",[143,12596,12597],{},"待补问题：",[130,12599,12601],{"id":12600},"part-iv-l1l2l3-执行标准","Part IV · L1\u002FL2\u002FL3 执行标准",[135,12603,12605],{"id":12604},"l1-explain","L1 · Explain",[140,12607,12608,12611,12614,12617,12620],{},[143,12609,12610],{},"能用自己的话解释。",[143,12612,12613],{},"能给出简单例子。",[143,12615,12616],{},"知道输入、输出和使用场景。",[143,12618,12619],{},"能预测简单结果。",[143,12621,12622],{},"能指出常见错误。",[135,12624,12626],{"id":12625},"l2-implement","L2 · Implement",[140,12628,12629,12632,12635,12638],{},[143,12630,12631],{},"核心逻辑能够独立实现。",[143,12633,12634],{},"可以查官方文档和 API。",[143,12636,12637],{},"不让 AI 代替核心思考。",[143,12639,12640],{},"能够通过测试定位和修复问题。",[135,12642,12644],{"id":12643},"l3-reason","L3 · Reason",[140,12646,12647,12650,12653,12656],{},[143,12648,12649],{},"能提出假设并设计实验。",[143,12651,12652],{},"能解释机制、复杂度和失败原因。",[143,12654,12655],{},"能比较替代方案。",[143,12657,12658],{},"能解释性能、正确性与维护成本的权衡。",[115,12660,12661],{},"L3 的判断依据是证据，而不是学习时长。",[130,12663,12665],{"id":12664},"part-v-ai-使用规范","Part V · AI 使用规范",[135,12667,12669],{"id":12668},"a0-ai-off","A0 · AI-Off",[115,12671,12672],{},"用于能力验收。适用：基础算法、数据结构、核心 SQL、数学推导、ML 核心机制、Attention。",[135,12674,12676],{"id":12675},"a1-ai-query","A1 · AI-Query",[115,12678,12679],{},"允许查询 API、官方文档、参数含义和错误信息。",[135,12681,12683],{"id":12682},"a2-ai-assisted","A2 · AI-Assisted",[115,12685,12686],{},"允许 AI 辅助 Code Review、Debug、性能分析、实验设计与方案比较；最终判断由自己完成。",[135,12688,12690],{"id":12689},"a3-ai-open","A3 · AI-Open",[115,12692,12693],{},"允许 AI 实现 FastAPI、Docker、Airflow、Spark API、PyTorch 工程代码、RAG、Agent、UI、测试与部署。",[115,12695,12696],{},"无论使用哪个等级，都必须能够解释最终代码的输入输出、关键依赖、主要错误路径和安全边界。",[130,12698,12700],{"id":12699},"part-vi-数智工程师考试-overlay","Part VI · 数智工程师考试 Overlay",[115,12702,12703],{},"每周额外安排 20–30 分钟：",[4640,12705,12706,12709,12712,12715,12718],{},[143,12707,12708],{},"对照正式考试大纲标记本周知识点。",[143,12710,12711],{},"完成 5–10 道针对性练习。",[143,12713,12714],{},"记录错误类型。",[143,12716,12717],{},"每四周做一次小测。",[143,12719,12720],{},"根据真实大纲补充未覆盖的知识点。",[115,12722,12723],{},"考试范围以正式大纲为准，不假定本计划自动覆盖全部考点。",[130,12725,12727],{"id":12726},"part-vii-学习节奏与中断恢复","Part VII · 学习节奏与中断恢复",[115,12729,12730],{},[125,12731,12732],{},"每周标准",[140,12734,12735,12738,12741,12744],{},[143,12736,12737],{},"Ideal：4 次。",[143,12739,12740],{},"Normal：3 次。",[143,12742,12743],{},"Busy：2 次。",[143,12745,12746],{},"禁止连续两周零学习。",[115,12748,12749],{},[125,12750,12751],{},"单次任务",[140,12753,12754,12757,12760,12763],{},[143,12755,12756],{},"Day A：45–60 分钟，理解知识。",[143,12758,12759],{},"Day B：60–90 分钟，独立实现。",[143,12761,12762],{},"Day C：45–90 分钟，实验和验收。",[143,12764,12765],{},"Day D：30–90 分钟，可选工程拓展。",[115,12767,12768],{},[125,12769,12770],{},"中断处理",[140,12772,12773,12776,12779,12782],{},[143,12774,12775],{},"疲惫日：完成 15 分钟最低任务。",[143,12777,12778],{},"中断一周：直接继续当前任务，不补齐全部欠账。",[143,12780,12781],{},"连续两周零学习：先做一个 30 分钟小任务恢复节奏。",[143,12783,12784],{},"某周未达标：记录实际等级，继续推进；后续阶段再复查。",[130,12786,12788],{"id":12787},"part-viii-七个长期资产","Part VIII · 七个长期资产",[4640,12790,12791,12797,12803,12809,12815,12821,12827],{},[143,12792,12793,12796],{},[125,12794,12795],{},"Python Engineering Toolkit","：文件处理、数据清洗、CLI、测试、日志。",[143,12798,12799,12802],{},[125,12800,12801],{},"Database & Analytics","：PostgreSQL、数据模型、SQL、执行计划和分析报告。",[143,12804,12805,12808],{},[125,12806,12807],{},"Data Engineering Pipeline","：ETL、数据仓库、调度、幂等和数据质量。",[143,12810,12811,12814],{},[125,12812,12813],{},"ML System","：数据处理、基线、训练、评估、误差分析。",[143,12816,12817,12820],{},[125,12818,12819],{},"Mini Transformer","：Attention、简化 Transformer Block 和模型实验。",[143,12822,12823,12826],{},[125,12824,12825],{},"RAG \u002F Agent System","：检索、评估、引用、工具调用、可靠性。",[143,12828,12829,12832],{},[125,12830,12831],{},"Integrated Data\u002FAI System","：整合数据处理、分析、ML 或 RAG、API、测试和部署。",[115,12834,12835],{},"项目可以共用代码仓库，但必须有清晰的模块边界、运行说明和验收证据。",[130,12837,12839],{"id":12838},"part-ix-最终验收标准","Part IX · 最终验收标准",[115,12841,12842],{},"52 周结束后，重点检查：",[140,12844,12845,12848,12851,12854,12857,12860,12863,12866,12869,12872],{},[143,12846,12847],{},"☐ 能否独立编写中等规模 Python 程序。",[143,12849,12850],{},"☐ 能否独立解决基础算法和数据结构问题。",[143,12852,12853],{},"☐ 能否独立完成复杂 SQL 查询。",[143,12855,12856],{},"☐ 能否解释数据库索引、事务和执行计划。",[143,12858,12859],{},"☐ 能否设计并实现 ETL Pipeline。",[143,12861,12862],{},"☐ 能否独立训练和评估基础 ML 模型。",[143,12864,12865],{},"☐ 能否推导核心数学公式。",[143,12867,12868],{},"☐ 能否解释 Attention 与 Transformer 的核心机制。",[143,12870,12871],{},"☐ 能否评估 RAG，并处理 Agent 工具失败与安全问题。",[143,12873,12874],{},"☐ 能否借助 AI 独立完成一个可复现的 Data\u002FAI 系统。",[115,12876,12877],{},"分别记录两个分数：",[140,12879,12880,12886],{},[143,12881,12882,12885],{},[125,12883,12884],{},"AI-Off","：独立掌握的核心能力。",[143,12887,12888,12891],{},[125,12889,12890],{},"AI-Assisted","：使用 AI 完成工程任务的能力。",[115,12893,12894],{},"最终目标不是记住所有框架，而是理解原理、掌握核心实现、能解释系统、会验证结果，并能够利用 AI 提高工程效率。",[130,12896,12898],{"id":12897},"part-x-一句话执行原则","Part X · 一句话执行原则",[115,12900,12901],{},"人负责理解、判断、推导、设计和验收；AI 负责加速实现。每周交付一个可验证的成果，允许局部失败，不允许长期中断。",{"title":625,"searchDepth":732,"depth":732,"links":12903},[12904,12905,12913,12920,12925,12932,12940,12947,12953,12959,12964,12970,12976,12981,12982,12987,12993,12994,12995,12996,12997],{"id":10638,"depth":732,"text":10638},{"id":10690,"depth":732,"text":10691,"children":12906},[12907,12908,12909,12910,12911,12912],{"id":10694,"depth":34,"text":10695},{"id":10734,"depth":34,"text":10735},{"id":10770,"depth":34,"text":10771},{"id":10805,"depth":34,"text":1914},{"id":10840,"depth":34,"text":1541},{"id":10879,"depth":34,"text":858},{"id":10920,"depth":732,"text":10921,"children":12914},[12915,12916,12917,12918,12919],{"id":10924,"depth":34,"text":10925},{"id":10961,"depth":34,"text":10962},{"id":10996,"depth":34,"text":10997},{"id":11031,"depth":34,"text":11032},{"id":11066,"depth":34,"text":11067},{"id":11101,"depth":732,"text":11102,"children":12921},[12922,12923,12924],{"id":11105,"depth":34,"text":11106},{"id":11140,"depth":34,"text":11141},{"id":11176,"depth":34,"text":11177},{"id":11212,"depth":732,"text":11213,"children":12926},[12927,12928,12929,12930,12931],{"id":11216,"depth":34,"text":11217},{"id":11247,"depth":34,"text":11248},{"id":11277,"depth":34,"text":11278},{"id":11313,"depth":34,"text":11314},{"id":11343,"depth":34,"text":11344},{"id":11378,"depth":732,"text":11379,"children":12933},[12934,12935,12936,12937,12938,12939],{"id":11382,"depth":34,"text":11383},{"id":11412,"depth":34,"text":11413},{"id":11442,"depth":34,"text":11443},{"id":11477,"depth":34,"text":11478},{"id":11507,"depth":34,"text":11508},{"id":11537,"depth":34,"text":11538},{"id":11574,"depth":732,"text":11575,"children":12941},[12942,12943,12944,12945,12946],{"id":11578,"depth":34,"text":11579},{"id":11608,"depth":34,"text":11609},{"id":11638,"depth":34,"text":11639},{"id":11663,"depth":34,"text":11664},{"id":11693,"depth":34,"text":11694},{"id":11733,"depth":732,"text":11734,"children":12948},[12949,12950,12951,12952],{"id":11737,"depth":34,"text":11738},{"id":11762,"depth":34,"text":11763},{"id":11797,"depth":34,"text":11798},{"id":11827,"depth":34,"text":11828},{"id":11867,"depth":732,"text":11868,"children":12954},[12955,12956,12957,12958],{"id":11871,"depth":34,"text":11872},{"id":11901,"depth":34,"text":11902},{"id":11931,"depth":34,"text":11932},{"id":11966,"depth":34,"text":11967},{"id":12008,"depth":732,"text":12009,"children":12960},[12961,12962,12963],{"id":12012,"depth":34,"text":12013},{"id":12042,"depth":34,"text":12043},{"id":12078,"depth":34,"text":12079},{"id":12113,"depth":732,"text":12114,"children":12965},[12966,12967,12968,12969],{"id":12117,"depth":34,"text":12118},{"id":12148,"depth":34,"text":12149},{"id":12173,"depth":34,"text":12174},{"id":12198,"depth":34,"text":12199},{"id":12228,"depth":732,"text":12229,"children":12971},[12972,12973,12974,12975],{"id":12235,"depth":34,"text":12236},{"id":12257,"depth":34,"text":12258},{"id":12278,"depth":34,"text":12279},{"id":12299,"depth":34,"text":12300},{"id":12320,"depth":732,"text":12321,"children":12977},[12978,12979,12980],{"id":12324,"depth":34,"text":12325},{"id":12344,"depth":34,"text":12345},{"id":12361,"depth":34,"text":12362},{"id":12435,"depth":732,"text":12436},{"id":12600,"depth":732,"text":12601,"children":12983},[12984,12985,12986],{"id":12604,"depth":34,"text":12605},{"id":12625,"depth":34,"text":12626},{"id":12643,"depth":34,"text":12644},{"id":12664,"depth":732,"text":12665,"children":12988},[12989,12990,12991,12992],{"id":12668,"depth":34,"text":12669},{"id":12675,"depth":34,"text":12676},{"id":12682,"depth":34,"text":12683},{"id":12689,"depth":34,"text":12690},{"id":12699,"depth":732,"text":12700},{"id":12726,"depth":732,"text":12727},{"id":12787,"depth":732,"text":12788},{"id":12838,"depth":732,"text":12839},{"id":12897,"depth":732,"text":12898},"2026-08-31","52 周 Data\u002FAI 执行计划：执行规范、十二个阶段的主计划、每周固定模板、L1\u002FL2\u002FL3 标准与 AI 使用分级，从 Python、SQL、数据库、概率统计与数学，到机器学习、数据工程、Transformer、RAG、Agent，最后是定向强化与最终验收。",{},"2026-08-31T15:02:31+08:00",{"title":10630,"description":12999},"writing\u002F2026-08-31-one-year-data-ai-plan",[13005,837,12383,13006,13007,13008,12407,12413,12419],"学习计划","数据库","数据工程","机器学习","2026-10-13","m0g9u1Us6BoZgyKhTUYPqun0G8l_X8fTBNnjpLuNJkM",{"id":13012,"title":13013,"body":13014,"category":824,"date":14154,"description":14155,"extension":827,"hidden":828,"kind":829,"meta":14156,"navigation":30,"path":8369,"publishedAt":14157,"seo":14158,"series":102,"slug":14159,"stem":14160,"tags":14161,"unit":10626,"updated":825,"__hash__":14164},"writing\u002Fwriting\u002F2026-08-25-week1-python-object-model-data-structures.md","Week 1 学习计划｜Python 对象模型与数据结构",{"type":112,"value":13015,"toc":14119},[13016,13026,13028,13030,13035,13037,13052,13054,13058,13060,13063,13066,13106,13108,13111,13144,13147,13151,13163,13185,13187,13212,13223,13237,13241,13247,13253,13262,13266,13276,13281,13304,13333,13344,13348,13351,13356,13385,13388,13393,13414,13418,13439,13454,13460,13484,13490,13494,13497,13518,13531,13587,13590,13615,13621,13627,13647,13650,13654,13670,13679,13692,13697,13702,13711,13717,13782,13788,13790,13793,13831,13833,13836,13838,13842,13844,13849,13851,13859,13861,13866,13868,13874,13876,13878,13880,13898,13900,13919,13921,13925,13927,13932,13934,13953,13955,13957,13960,14050,14057,14059,14117],[115,13017,117,13018,123,13020,13022,13023,863],{},[119,13019,122],{"href":121},[125,13021,8375],{},"，按总纲 Part II「每周固定执行模板」编排。总投入约 14–16 小时；原则是先自己做、卡住再查资料，核心代码不让 AI 直接生成。计划篇只保留任务与要求，实际产出和实测数据见",[119,13024,13025],{"href":10616},"Week 1 学习成果",[130,13027,133],{"id":132},[135,13029,138],{"id":137},[140,13031,13032],{},[143,13033,13034],{},"能解释可变对象 \u002F 不可变对象、引用，以及为什么 dict \u002F set 通常查找较快。",[135,13036,149],{"id":148},[140,13038,13039],{},[143,13040,13041,13042,185,13044,185,13046,185,13048,185,13050,863],{},"不看教程独立完成 set 去重、frequency counter，并独立实现简单 KV Store 的 ",[60,13043,5271],{},[60,13045,5274],{},[60,13047,5277],{},[60,13049,5280],{},[60,13051,5283],{},[135,13053,158],{"id":157},[140,13055,13056],{},[143,13057,163],{},[130,13059,167],{"id":166},[115,13061,13062],{},"总纲 Week 1 学习内容：",[115,13064,13065],{},"object \u002F reference \u002F mutable \u002F immutable \u002F list \u002F tuple \u002F dict \u002F set \u002F shallow copy \u002F deep copy \u002F hash。",[140,13067,13068,13084,13089,13100],{},[143,13069,13070,13075,13076,185,13079,4507,13081,13083],{},[119,13071,13074],{"href":13072,"rel":13073},"https:\u002F\u002Fdocs.python.org\u002F3\u002Freference\u002Fexecutionmodel.html#naming-and-binding",[750],"Naming and binding（名字、引用与赋值）","：object \u002F name \u002F reference \u002F assignment \u002F ",[60,13077,13078],{},"id()",[60,13080,9532],{},[60,13082,9520],{}," \u002F mutable \u002F immutable \u002F aliasing",[143,13085,13086],{},[119,13087,10508],{"href":10506,"rel":13088},[750],[143,13090,13091,195,13094,195,13097],{},[119,13092,10515],{"href":10513,"rel":13093},[750],[119,13095,10522],{"href":10520,"rel":13096},[750],[119,13098,10529],{"href":10527,"rel":13099},[750],[143,13101,13102,13105],{},[119,13103,10536],{"href":10534,"rel":13104},[750],"：支撑“为什么 dict \u002F set 查找较快”的复杂度背景",[130,13107,231],{"id":230},[115,13109,13110],{},"总纲 Week 1 要求手写：",[140,13112,13114,13120,13126,13132,13138],{"className":13113},[238],[143,13115,13117,13119],{"className":13116},[242],[244,13118],{"disabled":30,"type":246}," list 基本操作",[143,13121,13123,13125],{"className":13122},[242],[244,13124],{"disabled":30,"type":246}," dict 基本操作",[143,13127,13129,13131],{"className":13128},[242],[244,13130],{"disabled":30,"type":246}," set 去重",[143,13133,13135,13137],{"className":13134},[242],[244,13136],{"disabled":30,"type":246}," frequency counter",[143,13139,13141,13143],{"className":13140},[242],[244,13142],{"disabled":30,"type":246}," 简单 KV Store",[115,13145,13146],{},"按日拆解如下。",[135,13148,13150],{"id":13149},"day-1对象引用可变性152h","Day 1｜对象、引用、可变性（1.5–2h）",[115,13152,13153,13155,13156,195,13158,4507,13160,13162],{},[125,13154,6964],{},"：阅读 Naming and binding 与 copy 文档，掌握 object、name、reference、assignment、",[60,13157,13078],{},[60,13159,9532],{},[60,13161,9520],{},"、mutable \u002F immutable、aliasing、shallow copy、deep copy。",[115,13164,13165,13167,13168,13171,13172,195,13175,185,13178,13181,13182,13184],{},[125,13166,6980],{},"：自建 ",[60,13169,13170],{},"object_demo.py","，完成 ",[60,13173,13174],{},"a = b",[60,13176,13177],{},"a == b",[60,13179,13180],{},"a is b","、list alias、string immutable、tuple immutable、list mutable、",[60,13183,13078],{}," 变化、shallow copy、deep copy。",[115,13186,4684],{},[619,13188,13190],{"className":717,"code":13189,"language":719,"meta":625,"style":625},"a = [1, 2, [3, 4]]\nb = a\nc = a.copy()\nc[2].append(5)\n",[60,13191,13192,13197,13202,13207],{"__ignoreMap":625},[723,13193,13194],{"class":725,"line":726},[723,13195,13196],{},"a = [1, 2, [3, 4]]\n",[723,13198,13199],{"class":725,"line":732},[723,13200,13201],{},"b = a\n",[723,13203,13204],{"class":725,"line":34},[723,13205,13206],{},"c = a.copy()\n",[723,13208,13209],{"class":725,"line":1068},[723,13210,13211],{},"c[2].append(5)\n",[115,13213,13214,13215,195,13217,195,13219,13222],{},"写出 ",[60,13216,119],{},[60,13218,4106],{},[60,13220,13221],{},"c"," 并解释原因。",[115,13224,13225,13227,13228,13231,13232,1471,13234,13236],{},[125,13226,7061],{},"：不用资料解释 ",[60,13229,13230],{},"="," 做了什么、",[60,13233,9532],{},[60,13235,9520],{}," 区别、mutable \u002F immutable、shallow \u002F deep copy。",[135,13238,13240],{"id":13239},"day-2list-dict-set-hash152h","Day 2｜List \u002F Dict \u002F Set + Hash（1.5–2h）",[115,13242,13243,13246],{},[125,13244,13245],{},"学习 40min","：掌握 list、tuple、dict、set、hash、hash table、Big-O，理解 dict \u002F set 为什么通常查找较快。",[115,13248,13249,13252],{},[125,13250,13251],{},"实验 50min","：手写 list \u002F dict 的基本操作与 set 去重，理解哈希表如何支撑 O(1) 查找。",[115,13254,13255,13258,13259,13261],{},[125,13256,13257],{},"输出 30min","：写 ",[60,13260,8444],{}," 复杂度对照表。",[135,13263,13265],{"id":13264},"day-3function-scope-copy152h","Day 3｜Function + Scope + Copy（1.5–2h）",[115,13267,13268,13270,13271,185,13273,13275],{},[125,13269,6964],{},"：掌握 function object、parameter \u002F argument、return、default argument、",[60,13272,3577],{},[60,13274,3580],{},"、namespace、LEGB、global、nonlocal、shallow \u002F deep copy。",[115,13277,13278,181],{},[125,13279,13280],{},"编程实验 45min",[619,13282,13284],{"className":717,"code":13283,"language":719,"meta":625,"style":625},"def add(a, b):\n    ...\n\nf = add          # 验证函数是对象\n",[60,13285,13286,13290,13295,13299],{"__ignoreMap":625},[723,13287,13288],{"class":725,"line":726},[723,13289,5124],{},[723,13291,13292],{"class":725,"line":732},[723,13293,13294],{},"    ...\n",[723,13296,13297],{"class":725,"line":34},[723,13298,1060],{"emptyLinePlaceholder":30},[723,13300,13301],{"class":725,"line":1068},[723,13302,13303],{},"f = add          # 验证函数是对象\n",[619,13305,13307],{"className":717,"code":13306,"language":719,"meta":625,"style":625},"funcs = []\nfor i in range(3):\n    funcs.append(lambda: i)\n\nprint([f() for f in funcs])   # 解释为什么不是 [0, 1, 2]\n",[60,13308,13309,13314,13319,13324,13328],{"__ignoreMap":625},[723,13310,13311],{"class":725,"line":726},[723,13312,13313],{},"funcs = []\n",[723,13315,13316],{"class":725,"line":732},[723,13317,13318],{},"for i in range(3):\n",[723,13320,13321],{"class":725,"line":34},[723,13322,13323],{},"    funcs.append(lambda: i)\n",[723,13325,13326],{"class":725,"line":1068},[723,13327,1060],{"emptyLinePlaceholder":30},[723,13329,13330],{"class":725,"line":1074},[723,13331,13332],{},"print([f() for f in funcs])   # 解释为什么不是 [0, 1, 2]\n",[115,13334,13335,13337,13338,13340,13341,863],{},[125,13336,7247],{},"：写一页 ",[60,13339,8435],{},"，解释 ",[60,13342,13343],{},"name → object → reference → scope",[135,13345,13347],{"id":13346},"day-4数据结构实战2h","Day 4｜数据结构实战（2h）",[115,13349,13350],{},"今天不看教程，直接写。",[115,13352,13353],{},[125,13354,13355],{},"Task 1：数据去重",[619,13357,13359],{"className":717,"code":13358,"language":719,"meta":625,"style":625},"users = [\n    {\"id\": 1, \"name\": \"A\"},\n    {\"id\": 2, \"name\": \"B\"},\n    {\"id\": 1, \"name\": \"A\"},\n]\n",[60,13360,13361,13366,13371,13376,13380],{"__ignoreMap":625},[723,13362,13363],{"class":725,"line":726},[723,13364,13365],{},"users = [\n",[723,13367,13368],{"class":725,"line":732},[723,13369,13370],{},"    {\"id\": 1, \"name\": \"A\"},\n",[723,13372,13373],{"class":725,"line":34},[723,13374,13375],{},"    {\"id\": 2, \"name\": \"B\"},\n",[723,13377,13378],{"class":725,"line":1068},[723,13379,13370],{},[723,13381,13382],{"class":725,"line":1074},[723,13383,13384],{},"]\n",[115,13386,13387],{},"实现 list 版本与 set\u002Fdict 版本，比较复杂度。",[115,13389,13390],{},[125,13391,13392],{},"Task 2：frequency counter",[115,13394,13395,13396,13399,13400,185,13403,185,13406,13409,13410,13413],{},"输入 ",[60,13397,13398],{},"apple banana apple orange banana apple","，输出 ",[60,13401,13402],{},"apple: 3",[60,13404,13405],{},"banana: 2",[60,13407,13408],{},"orange: 1","。不使用 ",[119,13411,10124],{"href":10554,"rel":13412},[750]," 实现一次，再使用 Counter 实现一次，比较两种方案。",[135,13415,13417],{"id":13416},"day-5lru-cache225h","Day 5｜LRU Cache（2–2.5h）",[115,13419,13420,13423,13424,13427,13428,1471,13433,13438],{},[125,13421,13422],{},"理解 LRU 20min","：容量 3，访问序列 ",[60,13425,13426],{},"A B C A D","，推演最终缓存（参考 ",[119,13429,13432],{"href":13430,"rel":13431},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCache_replacement_policies#Least-recently_used_(LRU)",[750],"LRU 缓存淘汰策略",[119,13434,13437],{"href":13435,"rel":13436},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Ffunctools.html#functools.lru_cache",[750],"functools.lru_cache","）。",[115,13440,13441,7387,13444,7379,13447,195,13450,13453],{},[125,13442,13443],{},"第一版 40min",[60,13445,13446],{},"dict + list",[60,13448,13449],{},"get(key)",[60,13451,13452],{},"put(key, value)","，允许 O(n)。",[115,13455,13456,13459],{},[125,13457,13458],{},"分析 20min","：回答为什么这个实现不能做到 O(1)？",[115,13461,13462,13465,13466,952,13470,13475,13476,195,13479,195,13481,13483],{},[125,13463,13464],{},"第二版 60min","：设计 ",[119,13467,13469],{"href":10527,"rel":13468},[750],"HashMap",[119,13471,13474],{"href":13472,"rel":13473},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDoubly_linked_list",[750],"Doubly Linked List","，实现 ",[60,13477,13478],{},"LRUCache(capacity)",[60,13480,13449],{},[60,13482,13452],{},"，目标 get \u002F put 平均 O(1)。",[115,13485,13486,13489],{},[125,13487,13488],{},"测试 20min","：正常读取、重复读取、覆盖、淘汰、capacity = 1、capacity = 0、不存在 key。",[135,13491,13493],{"id":13492},"day-6简单-kv-store34h","Day 6｜简单 KV Store（3–4h）",[115,13495,13496],{},"这是本周核心任务。",[115,13498,13499,13502,13503,185,13506,185,13508,185,13511,185,13514,13517],{},[125,13500,13501],{},"设计 30min","：自己写需求 ",[60,13504,13505],{},"set(key, value)",[60,13507,13449],{},[60,13509,13510],{},"delete(key)",[60,13512,13513],{},"exists(key)",[60,13515,13516],{},"keys()","，确定 key 不存在怎么办、delete 不存在怎么办、set 已存在 key 怎么办、value 可以是什么类型。",[115,13519,13520,13523,13524,13527,13528,13530],{},[125,13521,13522],{},"设计数据结构 30min","：比较方案 A ",[60,13525,13526],{},"list[(key, value)]"," 与方案 B ",[60,13529,5511],{},"，写出下表：",[264,13532,13533,13545],{},[267,13534,13535],{},[270,13536,13537,13539,13542],{},[273,13538,8526],{},[273,13540,13541],{},"List",[273,13543,13544],{},"Dict",[283,13546,13547,13555,13563,13571,13579],{},[270,13548,13549,13551,13553],{},[288,13550,5271],{},[288,13552],{},[288,13554],{},[270,13556,13557,13559,13561],{},[288,13558,5274],{},[288,13560],{},[288,13562],{},[270,13564,13565,13567,13569],{},[288,13566,5277],{},[288,13568],{},[288,13570],{},[270,13572,13573,13575,13577],{},[288,13574,5280],{},[288,13576],{},[288,13578],{},[270,13580,13581,13583,13585],{},[288,13582,5283],{},[288,13584],{},[288,13586],{},[115,13588,13589],{},"最终解释为什么选择 dict。",[115,13591,13592,6981,13595,1471,13598,13475,13601,5653,13604,185,13606,185,13608,185,13610,185,13612,13614],{},[125,13593,13594],{},"独立实现 90min",[60,13596,13597],{},"kv_store\u002Fstore.py",[60,13599,13600],{},"kv_store\u002Ftest_store.py",[60,13602,13603],{},"KeyValueStore",[60,13605,5271],{},[60,13607,5274],{},[60,13609,5277],{},[60,13611,5280],{},[60,13613,5283],{},"。这一部分禁止 AI 直接生成。",[115,13616,13617,13620],{},[125,13618,13619],{},"测试 30min","：空 Store、set\u002Fget、覆盖、delete、不存在 key、多个 key、重复 delete、None、list \u002F dict 作为 value。",[115,13622,13623,13626],{},[125,13624,13625],{},"思考 30min","：如果 value 是 list，用户修改返回值会不会影响 Store 内部数据？",[619,13628,13630],{"className":717,"code":13629,"language":719,"meta":625,"style":625},"store.set(\"x\", [1, 2, 3])\ndata = store.get(\"x\")\ndata.append(4)\n",[60,13631,13632,13637,13642],{"__ignoreMap":625},[723,13633,13634],{"class":725,"line":726},[723,13635,13636],{},"store.set(\"x\", [1, 2, 3])\n",[723,13638,13639],{"class":725,"line":732},[723,13640,13641],{},"data = store.get(\"x\")\n",[723,13643,13644],{"class":725,"line":34},[723,13645,13646],{},"data.append(4)\n",[115,13648,13649],{},"写下你的设计选择（不用马上解决）。",[135,13651,13653],{"id":13652},"day-7验收3h","Day 7｜验收（3h）",[115,13655,13656,13659,13660,13664,13665,13438],{},[125,13657,13658],{},"Part 1｜Benchmark 60min","：比较 List KV Store vs Dict KV Store，数据量 1,000 \u002F 10,000 \u002F 100,000 \u002F 1,000,000，测试 get、set、delete、exists，用 ",[119,13661,13663],{"href":10561,"rel":13662},[750],"timeit"," 计时，画一张图（如 ",[119,13666,13669],{"href":13667,"rel":13668},"https:\u002F\u002Fmatplotlib.org\u002F",[750],"matplotlib",[115,13671,13672,13258,13675,13678],{},[125,13673,13674],{},"Part 2｜最终技术总结 45min",[60,13676,13677],{},"week01_review.md","，回答：",[115,13680,13681,13683,13684,13686,13687,185,13689,13691],{},[125,13682,837],{},"：变量是什么？",[60,13685,13230],{}," 是复制吗？",[60,13688,9532],{},[60,13690,9520],{},"？mutable \u002F immutable？shallow \u002F deep copy？LEGB？",[115,13693,13694,13696],{},[125,13695,1895],{},"：list 随机访问为什么 O(1)？list 头部插入为什么 O(n)？dict 为什么平均 O(1)？dict 什么情况下会退化？set 为什么适合 membership？",[115,13698,13699,13701],{},[125,13700,1900],{},"：为什么 dict 不够？为什么 list 不够？HashMap + Doubly Linked List 为什么可以 O(1)？",[115,13703,13704,13707,13708,13710],{},[125,13705,13706],{},"KV Store","：为什么最终选择 dict？每个操作复杂度是多少？",[60,13709,13516],{}," 为什么是 O(n)？数据量扩大 100 倍会怎样？",[115,13712,13713,13716],{},[125,13714,13715],{},"Part 3｜无 AI 答辩 60min","：关闭 AI 与资料，拿纸回答“为什么 dict 平均 O(1)，但不能说 dict 永远 O(1)”，必须自己讲完整：",[619,13718,13720],{"className":10361,"code":13719,"language":10363,"meta":625,"style":625},"key\n↓\nhash()\n↓\nhash value\n↓\nhash table\n↓\ncollision\n↓\naverage case\n↓\nworst case\n",[60,13721,13722,13727,13732,13737,13741,13746,13750,13755,13759,13764,13768,13773,13777],{"__ignoreMap":625},[723,13723,13724],{"class":725,"line":726},[723,13725,13726],{},"key\n",[723,13728,13729],{"class":725,"line":732},[723,13730,13731],{},"↓\n",[723,13733,13734],{"class":725,"line":34},[723,13735,13736],{},"hash()\n",[723,13738,13739],{"class":725,"line":1068},[723,13740,13731],{},[723,13742,13743],{"class":725,"line":1074},[723,13744,13745],{},"hash value\n",[723,13747,13748],{"class":725,"line":1080},[723,13749,13731],{},[723,13751,13752],{"class":725,"line":1086},[723,13753,13754],{},"hash table\n",[723,13756,13757],{"class":725,"line":1092},[723,13758,13731],{},[723,13760,13761],{"class":725,"line":1098},[723,13762,13763],{},"collision\n",[723,13765,13766],{"class":725,"line":1104},[723,13767,13731],{},[723,13769,13770],{"class":725,"line":1110},[723,13771,13772],{},"average case\n",[723,13774,13775],{"class":725,"line":1116},[723,13776,13731],{},[723,13778,13779],{"class":725,"line":1122},[723,13780,13781],{},"worst case\n",[115,13783,13784,13785,13787],{},"随机抽查：shallow copy 是什么？为什么 ",[60,13786,9462],{}," 不是复制？LRU 为什么需要两个数据结构？为什么 list lookup 是 O(n)？为什么 dict lookup 平均 O(1)？KV Store 为什么使用 dict？能脱离资料讲清楚，Week 1 完成。",[130,13789,325],{"id":324},[115,13791,13792],{},"总纲 Week 1 要求：完善第一版 KV Store 的目录结构。",[140,13794,13796,13803,13810,13817,13824],{"className":13795},[238],[143,13797,13799,948,13801],{"className":13798},[242],[244,13800],{"disabled":30,"type":246},[60,13802,3670],{},[143,13804,13806,948,13808],{"className":13805},[242],[244,13807],{"disabled":30,"type":246},[60,13809,3667],{},[143,13811,13813,948,13815],{"className":13812},[242],[244,13814],{"disabled":30,"type":246},[60,13816,3673],{},[143,13818,13820,948,13822],{"className":13819},[242],[244,13821],{"disabled":30,"type":246},[60,13823,3676],{},[143,13825,13827,948,13829],{"className":13826},[242],[244,13828],{"disabled":30,"type":246},[60,13830,8490],{},[130,13832,370],{"id":369},[115,13834,13835],{},"总纲 Week 1 要求：比较 list、dict、set 的查找、插入、删除实际性能。",[135,13837,426],{"id":425},[140,13839,13840],{},[143,13841,8499],{},[135,13843,434],{"id":369},[140,13845,13846],{},[143,13847,13848],{},"用 timeit 对 list index \u002F append \u002F insert(0) \u002F search，dict get \u002F set \u002F membership，set add \u002F membership 在 1,000 \u002F 10,000 \u002F 100,000 三个规模上计时（repeat=5 取均值）。",[135,13850,443],{"id":442},[140,13852,13853],{},[143,13854,13855,13856,863],{},"数据与结论见",[119,13857,13025],{"href":13858},"\u002Fwriting\u002F2026-09-09-week1-learning-outcomes#day-2-%E6%88%90%E6%9E%9C%E5%A4%8D%E6%9D%82%E5%BA%A6%E5%AE%9E%E6%B5%8B%E4%B8%8E%E5%AF%B9%E7%85%A7%E8%A1%A8",[135,13860,452],{"id":451},[140,13862,13863],{},[143,13864,13865],{},"验证了假设；delete 类操作与 1,000,000 规模当周未测，如实标注为“未实测”。",[130,13867,460],{"id":459},[140,13869,13870,13872],{},[143,13871,8739],{},[143,13873,8742],{},[130,13875,475],{"id":474},[115,13877,8747],{},[135,13879,138],{"id":481},[140,13881,13883,13888,13893],{"className":13882},[238],[143,13884,13886,8758],{"className":13885},[242],[244,13887],{"disabled":30,"type":246},[143,13889,13891,8764],{"className":13890},[242],[244,13892],{"disabled":30,"type":246},[143,13894,13896,8770],{"className":13895},[242],[244,13897],{"disabled":30,"type":246},[135,13899,149],{"id":493},[140,13901,13903],{"className":13902},[238],[143,13904,13906,13908,13909,185,13911,185,13913,185,13915,185,13917],{"className":13905},[242],[244,13907],{"disabled":30,"type":246}," 独立完成 ",[60,13910,5271],{},[60,13912,5274],{},[60,13914,5277],{},[60,13916,5280],{},[60,13918,5283],{},[135,13920,158],{"id":511},[140,13922,13923],{},[143,13924,516],{},[130,13926,520],{"id":519},[522,13928,13929],{},[115,13930,13931],{},"为什么 dict \u002F set 通常查找较快？如果哈希冲突或触发扩容，会发生什么？",[130,13933,530],{"id":529},[140,13935,13936,13947],{},[143,13937,8821,13938,185,13940,185,13942,185,13944,13946],{},[60,13939,8824],{},[60,13941,8827],{},[60,13943,8830],{},[60,13945,8833],{},"）与 1,000,000 规模当周未测",[143,13948,13949,13950,13952],{},"KV Store 的 ",[60,13951,5274],{}," 返回可变对象时是否应允许调用方修改内部数据，当周只写下设计选择未定论",[130,13954,539],{"id":538},[115,13956,149],{},[130,13958,13959],{"id":13959},"第一周最终产出",[619,13961,13963],{"className":10361,"code":13962,"language":10363,"meta":625,"style":625},"week01\u002F\n├── exercises\u002F\n│   ├── dedup.py\n│   ├── frequency.py\n│   └── sorting.py\n├── lru\u002F\n│   ├── lru_cache.py\n│   └── test_lru.py\n├── kv_store\u002F\n│   ├── store.py\n│   └── test_store.py\n├── benchmarks\u002F\n│   └── benchmark.py\n├── notes\u002F\n│   ├── object_model.md\n│   └── complexity.md\n└── week01_review.md\n",[60,13964,13965,13970,13975,13980,13985,13990,13995,14000,14005,14010,14015,14020,14025,14030,14035,14040,14045],{"__ignoreMap":625},[723,13966,13967],{"class":725,"line":726},[723,13968,13969],{},"week01\u002F\n",[723,13971,13972],{"class":725,"line":732},[723,13973,13974],{},"├── exercises\u002F\n",[723,13976,13977],{"class":725,"line":34},[723,13978,13979],{},"│   ├── dedup.py\n",[723,13981,13982],{"class":725,"line":1068},[723,13983,13984],{},"│   ├── frequency.py\n",[723,13986,13987],{"class":725,"line":1074},[723,13988,13989],{},"│   └── sorting.py\n",[723,13991,13992],{"class":725,"line":1080},[723,13993,13994],{},"├── lru\u002F\n",[723,13996,13997],{"class":725,"line":1086},[723,13998,13999],{},"│   ├── lru_cache.py\n",[723,14001,14002],{"class":725,"line":1092},[723,14003,14004],{},"│   └── test_lru.py\n",[723,14006,14007],{"class":725,"line":1098},[723,14008,14009],{},"├── kv_store\u002F\n",[723,14011,14012],{"class":725,"line":1104},[723,14013,14014],{},"│   ├── store.py\n",[723,14016,14017],{"class":725,"line":1110},[723,14018,14019],{},"│   └── test_store.py\n",[723,14021,14022],{"class":725,"line":1116},[723,14023,14024],{},"├── benchmarks\u002F\n",[723,14026,14027],{"class":725,"line":1122},[723,14028,14029],{},"│   └── benchmark.py\n",[723,14031,14032],{"class":725,"line":1128},[723,14033,14034],{},"├── notes\u002F\n",[723,14036,14037],{"class":725,"line":1133},[723,14038,14039],{},"│   ├── object_model.md\n",[723,14041,14042],{"class":725,"line":1139},[723,14043,14044],{},"│   └── complexity.md\n",[723,14046,14047],{"class":725,"line":1145},[723,14048,14049],{},"└── week01_review.md\n",[115,14051,14052,14053,14056],{},"时间不够时的优先级：",[125,14054,14055],{},"KV Store > LRU > 数据结构实验 > 理论阅读 > 笔记美化","。这周不追求“把 Python 学完”，把这几个问题真正吃透更重要。",[130,14058,741],{"id":741},[140,14060,14061,14066,14071,14076,14081,14086,14091,14096,14101,14107,14112],{},[143,14062,14063,10501],{},[119,14064,10500],{"href":10498,"rel":14065},[750],[143,14067,14068],{},[119,14069,13074],{"href":13072,"rel":14070},[750],[143,14072,14073],{},[119,14074,10508],{"href":10506,"rel":14075},[750],[143,14077,14078],{},[119,14079,10515],{"href":10513,"rel":14080},[750],[143,14082,14083],{},[119,14084,10522],{"href":10520,"rel":14085},[750],[143,14087,14088],{},[119,14089,10529],{"href":10527,"rel":14090},[750],[143,14092,14093],{},[119,14094,10536],{"href":10534,"rel":14095},[750],[143,14097,14098],{},[119,14099,10556],{"href":10554,"rel":14100},[750],[143,14102,14103],{},[119,14104,14106],{"href":13435,"rel":14105},[750],"functools.lru_cache（LRU 参考实现）",[143,14108,14109],{},[119,14110,10563],{"href":10561,"rel":14111},[750],[143,14113,14114],{},[119,14115,10570],{"href":13667,"rel":14116},[750],[790,14118,792],{},{"title":625,"searchDepth":732,"depth":732,"links":14120},[14121,14126,14127,14136,14137,14143,14144,14149,14150,14151,14152,14153],{"id":132,"depth":732,"text":133,"children":14122},[14123,14124,14125],{"id":137,"depth":34,"text":138},{"id":148,"depth":34,"text":149},{"id":157,"depth":34,"text":158},{"id":166,"depth":732,"text":167},{"id":230,"depth":732,"text":231,"children":14128},[14129,14130,14131,14132,14133,14134,14135],{"id":13149,"depth":34,"text":13150},{"id":13239,"depth":34,"text":13240},{"id":13264,"depth":34,"text":13265},{"id":13346,"depth":34,"text":13347},{"id":13416,"depth":34,"text":13417},{"id":13492,"depth":34,"text":13493},{"id":13652,"depth":34,"text":13653},{"id":324,"depth":732,"text":325},{"id":369,"depth":732,"text":370,"children":14138},[14139,14140,14141,14142],{"id":425,"depth":34,"text":426},{"id":369,"depth":34,"text":434},{"id":442,"depth":34,"text":443},{"id":451,"depth":34,"text":452},{"id":459,"depth":732,"text":460},{"id":474,"depth":732,"text":475,"children":14145},[14146,14147,14148],{"id":481,"depth":34,"text":138},{"id":493,"depth":34,"text":149},{"id":511,"depth":34,"text":158},{"id":519,"depth":732,"text":520},{"id":529,"depth":732,"text":530},{"id":538,"depth":732,"text":539},{"id":13959,"depth":732,"text":13959},{"id":741,"depth":732,"text":741},"2026-08-25","Week 1 学习计划：7 天约 14–16 小时，从对象模型与数据结构出发，目标独立设计并验证一个 Mini Key-Value Store。",{},"2026-08-25T13:49:05+08:00",{"title":13013,"description":14155},"week1-python-object-model-data-structures","writing\u002F2026-08-25-week1-python-object-model-data-structures",[837,10622,10623,14162,14163],"lru-cache","learning-plan","5-ULknXoUt0RByswve0hmnLb7NBN3R-g0_5YQCToxjI",{"id":14166,"title":14167,"body":14168,"category":14606,"date":14607,"description":14608,"extension":827,"hidden":828,"kind":834,"meta":14609,"navigation":30,"path":14610,"publishedAt":14611,"seo":14612,"series":834,"slug":14613,"stem":14614,"tags":14615,"unit":834,"updated":14607,"__hash__":14620},"writing\u002Fwriting\u002F2026-08-17-personal-investment-framework.md","个人财务规划与长期投资体系",{"type":112,"value":14169,"toc":14589},[14170,14173,14176,14180,14183,14203,14207,14233,14236,14240,14243,14246,14257,14260,14264,14267,14290,14293,14313,14317,14320,14323,14337,14340,14344,14347,14361,14364,14368,14371,14375,14378,14381,14384,14388,14391,14420,14423,14427,14430,14434,14437,14454,14457,14461,14464,14477,14480,14484,14490,14496,14502,14508,14514,14520,14523,14527,14533,14539,14542,14546,14586],[115,14171,14172],{},"我已经进入工作阶段，有相对稳定的工资现金流，有一笔利率较低的低息贷款，目前主要承担利息成本。没有必要因为低利率就优先大规模提前偿还——保留流动性和投资能力更重要。未来购房等重大资产大概率主要靠自己，所以个人长期现金流、储蓄能力和资产积累是基本盘。",[115,14174,14175],{},"投资方面有约两年交易经验，主要用ETF，小仓位配置个股，自认为已经形成一定风控意识。偏好ETF，重视风险控制，倾向三笔建仓，建仓速度相对较快，习惯设止损。对宏观、行业、估值和市场结构有较强兴趣，不想完全采用机械无脑定投。",[130,14177,14179],{"id":14178},"一财务方案","一、财务方案",[115,14181,14182],{},"核心思路是把部分现金转入券商账户，同时保留一部分高流动性现金作为生活和应急资金。",[140,14184,14185,14191,14197],{},[143,14186,14187,14190],{},[125,14188,14189],{},"券商账户","：定位为长期资本账户，权益仓位大致维持在60%～80%，中枢约70%，剩余资金留在券商作为等待机会和未来加仓的现金。",[143,14192,14193,14196],{},[125,14194,14195],{},"银行账户","：保持一笔随时可用的安全垫，覆盖房租、水电、日常消费和短期突发支出。",[143,14198,14199,14202],{},[125,14200,14201],{},"工资现金流","：工资到账后先覆盖生活成本，补充安全垫，超过安全垫需求的资金再逐步进入投资账户。因为工资持续产生现金流，不需要把当前所有资金一次性投入市场。",[130,14204,14206],{"id":14205},"二资产配置框架","二、资产配置框架",[140,14208,14209,14215,14221,14227],{},[143,14210,14211,14214],{},[125,14212,14213],{},"核心指数 约30%","——负责长期Beta",[143,14216,14217,14220],{},[125,14218,14219],{},"行业ETF 约30%","——体现主动判断",[143,14222,14223,14226],{},[125,14224,14225],{},"个股 约10%～20%","——有限Alpha探索，后续倾向控制在更小比例",[143,14228,14229,14232],{},[125,14230,14231],{},"现金 约20%～40%","——流动性和等待机会",[115,14234,14235],{},"整体权益仓位保持在60%～80%。核心资产负责长期Beta，行业ETF体现主动判断，个股承担有限的Alpha探索，现金承担流动性和等待机会的功能。",[130,14237,14239],{"id":14238},"三定投改造有纪律的分批建仓","三、定投改造：有纪律的分批建仓",[115,14241,14242],{},"我不习惯长期、机械、固定日期定投，更习惯判断行情后快速分三笔建仓并设止损。这里有一个核心冲突：短中期交易思维关注买点、止损和方向；长期投资关注长期资产质量、估值、仓位、回撤和时间。",[115,14244,14245],{},"结论是没有必要强迫自己变成传统意义上的无脑定投者。替代方案是把定投改造成有纪律的分批建仓：",[4640,14247,14248,14251,14254],{},[143,14249,14250],{},"第一笔建立基础仓",[143,14252,14253],{},"第二笔等待估值改善、市场回撤或其他客观条件",[143,14255,14256],{},"第三笔等待进一步风险释放并出现一定右侧确认",[115,14258,14259],{},"时间尺度从几天扩大到数周、数月甚至更长。可以择时，但择时主要用于控制仓位，而不是试图准确预测市场顶部和底部。",[130,14261,14263],{"id":14262},"四指数投资的真正难点","四、指数投资的真正难点",[115,14265,14266],{},"指数长期投资并非简单到完全不需要思考，而是将难点从选股转移到了估值、仓位、风险管理和行为纪律：",[140,14268,14269,14272,14275,14278,14281,14284,14287],{},[143,14270,14271],{},"估值判断",[143,14273,14274],{},"回撤承受能力",[143,14276,14277],{},"风险判断",[143,14279,14280],{},"仓位管理",[143,14282,14283],{},"长期持有",[143,14285,14286],{},"避免情绪化交易",[143,14288,14289],{},"避免因为短期波动改变长期策略",[115,14291,14292],{},"长期主义不是\"买入后十年完全不动\"，而是使用较长时间尺度评价资产，同时允许基于估值、资产质量、风险和个人财务状况进行合理调整。三个需要区分的概念：",[140,14294,14295,14301,14307],{},[143,14296,14297,14300],{},[125,14298,14299],{},"长期主义","：长期持有长期逻辑成立、风险可接受的资产",[143,14302,14303,14306],{},[125,14304,14305],{},"死拿","：无论估值、基本面和个人财务状况如何都不卖",[143,14308,14309,14312],{},[125,14310,14311],{},"投机","：依赖短期价格波动、频繁猜测方向、缺乏长期正期望和风险边界",[130,14314,14316],{"id":14315},"五沪深300","五、沪深300",[115,14318,14319],{},"最初对沪深300存在明显心理障碍：担心A股历史上的制度问题、内幕交易、制度变化风险，担心长期持有存在较长时间低回报甚至长期被套的可能，担心买沪深300等同于押注中国国运。",[115,14321,14322],{},"后来逐渐接受沪深300可以被视为中国大型上市公司Beta的重要代表，确实可以算作中国较优秀的核心资产之一，但不意味着任何价格都值得买。几个修正后的认识：",[140,14324,14325,14328,14331,14334],{},[143,14326,14327],{},"沪深300是中国大型核心资产的重要Beta工具，但不宜简单绝对化为\"中国最优秀资产\"",[143,14329,14330],{},"判断一个资产是否适合持有，必须结合估值、时间区间、回撤、盈利、分红和个人风险承受能力",[143,14332,14333],{},"不需要证明沪深300绝对优秀，只需要判断它是否值得在个人组合中占一定比例",[143,14335,14336],{},"核心指数仓位可以逐步建立，不要求一次性完成",[115,14338,14339],{},"历史上即使是优质指数也可能出现非常长的回撤和低回报阶段，\"长期持有\"不等于\"任何价格买入都能获得良好体验\"。认可资产不代表认可任何价格，长期看好不代表短期必须买入。",[130,14341,14343],{"id":14342},"六纳斯达克","六、纳斯达克",[115,14345,14346],{},"认可纳斯达克长期优质资产属性，但担心当前估值偏高，担心历史上互联网泡沫期间的极端回撤，尤其关注境内场内相关ETF可能出现的溢价问题：",[140,14348,14349,14352,14355,14358],{},[143,14350,14351],{},"优秀资产不等于任何价格都值得买",[143,14353,14354],{},"历史上的极端泡沫说明估值和买入价格非常重要",[143,14356,14357],{},"如果境内ETF存在明显场内溢价，实际买入价格可能与底层资产价值存在偏离",[143,14359,14360],{},"需要同时判断底层指数估值和投资工具本身的交易条件",[115,14362,14363],{},"当前不急于配置，等待估值、溢价和风险收益比变得更合理。",[130,14365,14367],{"id":14366},"七海外资产","七、海外资产",[115,14369,14370],{},"曾提出通过海外宽基降低单一中国市场风险，后来认为目前没有必要强行加入海外资产。认可中国资产与海外资产的大类逻辑确实不同，但暂时更希望先把自己真正理解的资产研究透。海外资产不是必须立即配置的任务，不能为了所谓分散化而强迫自己购买一个尚未形成明确判断的资产。如果未来个人资产规模扩大，全球资产配置仍然值得重新研究。",[130,14372,14374],{"id":14373},"八择时","八、择时",[115,14376,14377],{},"不能把长期投资建立在预测顶部和底部的能力之上。择时的合理定义是用于决定仓位大小，而不是决定是否永远持有或彻底退出。",[115,14379,14380],{},"可以参考的因素：估值分位、PE、PB、股息率、历史回撤、距离近期高点的回撤幅度、回撤持续时间、盈利增长、基本面是否发生结构性变化、趋势和右侧确认。",[115,14382,14383],{},"不建议的行为：试图精准抄底、因为等待最低点而长期完全不投资、因为短期上涨而追涨、因为短期下跌而机械止损长期核心指数。",[130,14385,14387],{"id":14386},"九最大回撤与持有体验","九、最大回撤与持有体验",[115,14389,14390],{},"研究指数不能只看长期年化收益，还必须看投资者真实持有过程：",[140,14392,14393,14396,14399,14402,14405,14408,14411,14414,14417],{},[143,14394,14395],{},"最大回撤",[143,14397,14398],{},"回撤持续时间",[143,14400,14401],{},"创新高所需时间",[143,14403,14404],{},"历史不同估值区间买入后的3年、5年、10年收益",[143,14406,14407],{},"盈利增速",[143,14409,14410],{},"股息率",[143,14412,14413],{},"估值分位",[143,14415,14416],{},"指数成分股变化",[143,14418,14419],{},"实际投资者持有体验",[115,14421,14422],{},"一个指数最终能够创新高，不代表投资者在中途一定能够舒服地长期持有。",[130,14424,14426],{"id":14425},"十核心指数与止损","十、核心指数与止损",[115,14428,14429],{},"核心指数不适合直接复制个股的紧密价格止损机制。指数本身可能出现较大的正常周期波动，过于紧密的止损容易造成\"下跌止损—反弹追涨—再次下跌\"的循环。更适合的方式是使用仓位管理、估值判断和长期基本面判断控制风险。不设机械价格止损不等于永远不能卖；如果长期逻辑、资产质量、个人财务状况或资金需求发生重大变化，仍然可以调整或退出。",[130,14431,14433],{"id":14432},"十一主动判断与仓位上限","十一、主动判断与仓位上限",[115,14435,14436],{},"我容易产生一些个人小巧思，认为自己可能通过判断市场、行业和个股获得优势。原则是允许自己聪明，但给聪明设置仓位上限：",[140,14438,14439,14442,14445,14448,14451],{},[143,14440,14441],{},"核心指数部分机械化",[143,14443,14444],{},"行业ETF部分允许主动判断",[143,14446,14447],{},"个股部分允许寻找Alpha",[143,14449,14450],{},"整体组合保留现金",[143,14452,14453],{},"不允许一次判断错误毁掉整个资产组合",[115,14455,14456],{},"理解市场不等于能够战胜市场，两年交易经验也不能证明长期Alpha能力已经被验证。",[130,14458,14460],{"id":14459},"十二主动投资与投机的区分","十二、主动投资与投机的区分",[115,14462,14463],{},"可以择时，可以机械死板，但绝对不能有投机心理。需要区分的是：",[140,14465,14466,14472],{},[143,14467,14468,14471],{},[125,14469,14470],{},"主动投资","：研究行业周期、研究企业盈利、研究估值、根据基本面配置行业ETF或个股",[143,14473,14474,14476],{},[125,14475,14311],{},"：主要依赖短期价格涨跌、频繁猜测方向、高频进出、没有明确正期望、依靠情绪和赌博式仓位",[115,14478,14479],{},"真正需要避免的不是主动判断，而是没有正期望、没有风险边界、依赖短期价格波动的赌博行为。",[130,14481,14483],{"id":14482},"十三投资框架","十三、投资框架",[115,14485,14486,14489],{},[125,14487,14488],{},"第一层·个人财务安全："," 保持生活现金安全垫，维持稳定工资现金流，低息贷款正常管理，避免为了投资牺牲基本生活流动性。",[115,14491,14492,14495],{},[125,14493,14494],{},"第二层·长期资本："," 建立一个独立的券商投资账户，将其视为未来长期资产，而不是短期必须赚钱的账户。",[115,14497,14498,14501],{},[125,14499,14500],{},"第三层·仓位："," 权益仓位保持在可承受区间，参考60%～80%，中枢约70%。上限避免市场狂热时无限追涨，下限避免短期恐慌时完全退出。",[115,14503,14504,14507],{},[125,14505,14506],{},"第四层·资产结构："," 核心指数约30%，行业ETF约30%，个股约10%，现金约30%。",[115,14509,14510,14513],{},[125,14511,14512],{},"第五层·行为纪律："," 可以择时但不依赖精准择时，可以主动判断但限制比例，可以分批建仓但不试图精准抄底，核心指数不用过紧止损，重大逻辑变化时可重新评估，不因短期波动改变长期配置原则。",[115,14515,14516,14519],{},[125,14517,14518],{},"第六层·长期主义："," 用5～10年的尺度评价资产，在可承受的风险范围内长期持有，允许基于估值、资产质量和个人财务状况进行调整。",[115,14521,14522],{},"六层框架形成一个完整循环：工资 → 储蓄 → 投资 → 权益上涨 → 部分兑现 → 改善生活 → 剩余继续投资。权益资产出现明显增值后，适当兑现一小部分用于改善生活——吃顿好的、按摩休闲、旅行、兴趣爱好，剩余资产继续长期复利。这不是短线止盈，而是长期资产配置体系中的财富兑现机制，同时实现两个目标：改善当下生活，避免为了积累财富长期压抑消费；降低风险暴露，把部分账面财富转化为现实财富。本金负责长期增值，收益负责适当改善生活。",[130,14524,14526],{"id":14525},"十四下一步研究","十四、下一步研究",[115,14528,14529,14532],{},[125,14530,14531],{},"沪深300："," 过去15～20年PE\u002FPB历史分位、股息率、盈利增长、最大回撤、回撤持续时间、创新高时间、不同估值分位买入后的3年\u002F5年\u002F10年收益、不同分批建仓策略的结果。",[115,14534,14535,14538],{},[125,14536,14537],{},"纳斯达克100："," 长期年化收益、互联网泡沫时期最大回撤、历史最大回撤、回撤恢复时间、PE和Forward PE历史分位、盈利增长、当前估值、境内相关ETF场内溢价、不同估值区间买入后的长期收益。",[115,14540,14541],{},"最终目的不是寻找\"现在是不是最低点\"，而是建立一套自己真正认可的规则：什么条件下值得建立第一笔仓位，什么时候加仓，什么时候停止加仓，什么时候降低仓位。",[130,14543,14545],{"id":14544},"十五核心原则","十五、核心原则",[4640,14547,14548,14551,14554,14557,14559,14562,14565,14568,14571,14574,14577,14580,14583],{},[143,14549,14550],{},"资产配置优先于预测市场",[143,14552,14553],{},"仓位管理优先于精准择时",[143,14555,14556],{},"长期主义不等于死拿",[143,14558,14351],{},[143,14560,14561],{},"可以主动判断，但不能让主动判断毁掉组合",[143,14563,14564],{},"可以择时，但不能依赖择时才能赚钱",[143,14566,14567],{},"可以机械，但不能赌博",[143,14569,14570],{},"可以聪明，但必须给聪明设置仓位上限",[143,14572,14573],{},"不需要证明自己一定正确，只需要保证错误不会毁掉自己的财富积累",[143,14575,14576],{},"不投资也是一种决策",[143,14578,14579],{},"等待本身也是投资能力",[143,14581,14582],{},"本金负责长期增值，收益负责适当改善生活",[143,14584,14585],{},"真正的长期投资不是预测未来，而是在不知道未来的情况下控制风险、保持暴露并让时间发挥作用",[115,14587,14588],{},"目前最适合自己的方向不是强迫自己成为无脑定投者，而是建立\"稳定现金流 + 安全垫 + 核心指数长期资本 + 有限主动配置 + 明确仓位上限 + 估值和风险管理 + 长期主义\"的个人投资系统。其中最重要的不是证明自己聪明，而是确保自己的错误不会毁掉长期财富积累。",{"title":625,"searchDepth":732,"depth":732,"links":14590},[14591,14592,14593,14594,14595,14596,14597,14598,14599,14600,14601,14602,14603,14604,14605],{"id":14178,"depth":732,"text":14179},{"id":14205,"depth":732,"text":14206},{"id":14238,"depth":732,"text":14239},{"id":14262,"depth":732,"text":14263},{"id":14315,"depth":732,"text":14316},{"id":14342,"depth":732,"text":14343},{"id":14366,"depth":732,"text":14367},{"id":14373,"depth":732,"text":14374},{"id":14386,"depth":732,"text":14387},{"id":14425,"depth":732,"text":14426},{"id":14432,"depth":732,"text":14433},{"id":14459,"depth":732,"text":14460},{"id":14482,"depth":732,"text":14483},{"id":14525,"depth":732,"text":14526},{"id":14544,"depth":732,"text":14545},"交易","2026-08-17","在稳定工资现金流和低息贷款背景下，建立一套兼顾安全性、长期财富积累和有限主动判断的个人投资框架。",{},"\u002Fwriting\u002F2026-08-17-personal-investment-framework","2026-08-17T00:30:10+08:00",{"title":14167,"description":14608},"personal-investment-framework","writing\u002F2026-08-17-personal-investment-framework",[14616,14617,14280,14618,14619],"资产配置","ETF","长期投资","风险控制","IWRVZNvihzkK6SoKYZen1vrDYTsfB84Z7I8AQRq8NMU",{"id":14622,"title":14623,"body":14624,"category":6797,"date":14824,"description":14825,"extension":827,"hidden":828,"kind":834,"meta":14826,"navigation":30,"path":14827,"publishedAt":14828,"seo":14829,"series":834,"slug":14830,"stem":14831,"tags":14832,"unit":834,"updated":14837,"__hash__":14838},"writing\u002Fwriting\u002F2026-08-13-want-everything-got-nothing.md","有很多想做的 但是没有行动力 也没有经费",{"type":112,"value":14625,"toc":14817},[14626,14629,14632,14635,14638,14641,14645,14648,14654,14660,14663,14666,14669,14672,14675,14678,14681,14685,14688,14691,14694,14697,14703,14706,14709,14713,14716,14723,14726,14732,14738,14744,14750,14753,14756,14782,14789,14793,14796,14799,14802,14805,14808,14814],[115,14627,14628],{},"看到别人去旅游、去不同的地方玩，会羡慕。但一想到自己没钱、又是一个人，就觉得不去也罢。",[115,14630,14631],{},"看到别人事业有成、在台上分享经验，会羡慕。但一想到自己的能力和职位，又觉得自卑，同时又忍不住想：我是不是也能去试试？",[115,14633,14634],{},"看到别人出国，会想自己也出去看看。还是没钱，放弃。",[115,14636,14637],{},"看到别人考研、读研、读博，也想过卷一卷、走学术。但没考上，现在已经在工作、在培训了。",[115,14639,14640],{},"最后我发现：自己不是没有欲望，而是欲望很多，却没有一个被选成第一优先级。每一个欲望冒出来，都被现实条件压了回去。最后剩下的感觉不是\"我不想要\"，而是——我好像什么都想要，但什么都没真正得到。",[130,14642,14644],{"id":14643},"一欲望很多但它们不是同一种东西","一、欲望很多，但它们不是同一种东西",[115,14646,14647],{},"把想做的事情列出来，其实是一张很长的清单。但仔细一看，这些欲望其实分成两种完全不同的东西。",[115,14649,14650,14653],{},[125,14651,14652],{},"一种是即时享受。"," 踢球、打羽毛球、和同学游泳、去足浴、打麻将、玩游戏、旅游——这些事拿起就能做，做完就开心，成本主要是钱和时间。它们的快乐来得直接，去得也快。",[115,14655,14656,14659],{},[125,14657,14658],{},"另一种是长期投入。"," 想在工作上做出成绩、想卷学历、想考研读博读研——这些不是\"去做一次\"就能获得体验的事。它们需要持续几个月甚至几年的投入，成本不只是钱和时间，还有机会成本、精力，以及放弃其他可能性的代价。",[115,14661,14662],{},"我之前把这两种欲望混在一起想，结果就是：既觉得什么都想做，又觉得什么都做不了。即时享受的事觉得\"没时间、没钱、没人陪\"；长期投入的事觉得\"风险太大、不确定值不值得\"。两种阻力叠在一起，最后什么都没动。",[115,14664,14665],{},"但它们需要的行动方式完全不同。即时享受的事，门槛低、风险小，真正的问题只是\"敢不敢迈出第一步\"。长期投入的事，门槛高、需要持续投入，真正的问题是\"有没有在积累选择权，让自己将来能选\"。",[115,14667,14668],{},"分清楚之后，很多事情就想通了。",[115,14670,14671],{},"即时享受的事有一个共同特点：边际效用递减。很久没做的时候跃跃欲试，觉得特别想；真的去做了，又没那么想继续；但做的时候，确实又很开心。一开始我怀疑这是不是说明\"我其实没那么想要\"。后来想明白了：边际效用递减不等于不想要。吃完一顿好饭不想马上再吃，不代表那顿饭不好吃。大脑对重复刺激会适应，这是正常现象。",[115,14673,14674],{},"我真正喜欢的，可能不是某一件永远不变的事，而是周期性地获得体验：今天踢球，下周游泳，过几天打游戏，周末去足浴或者打麻将，偶尔出去旅行一次。兴趣不需要固定成一个项目，也不需要现在就给自己贴上\"事业型\"\"享乐型\"\"学术型\"的标签——我还没充分体验过这些事，贴标签太早了。",[115,14676,14677],{},"而长期投入的事，不需要现在就选定一个方向。它们的关键不是\"今天就要决定走哪条路\"，而是\"现在就要开始积累，让自己将来有资格选\"。",[115,14679,14680],{},"这里还有一条容易踩的坑：不要把\"别人拥有\"直接解释成\"我也一定想拥有\"。看到别人的生活而羡慕，是正常的情绪反应；但羡慕不等于自己真正需要。区分的方法很简单——如果一件事连续在脑子里冒出来几次，那更可能是自己的真实欲望，而不只是被别人的朋友圈激活的一时冲动。",[130,14682,14684],{"id":14683},"二想做却不敢迈出第一步","二、想做，却不敢迈出第一步",[115,14686,14687],{},"即时享受类的事情，按理说门槛最低，但偏偏最难迈出第一步。明明想去游泳、去足浴、去谈恋爱，却迟迟不动。真正要行动的时候会害怕，一个人的时候这种感觉更明显。",[115,14689,14690],{},"这大概不是欲望不足，而是行动成本、陌生感、独处感，以及对失败和尴尬的预期在挡路。不需要要求自己突然变成一个很勇敢的人，只需要把第一步缩小到极低成本：",[115,14692,14693],{},"想游泳，不用要求自己\"培养一个游泳的爱好\"，只去一次、游二十分钟，不舒服就回来。想去足浴，不用要求自己\"开始享受生活\"，找一家店体验一次就行。想谈恋爱，不用要求自己\"找到女朋友\"，先认识一个新的异性，看看自己喜不喜欢恋爱这件事。",[115,14695,14696],{},"第一次行动的目的不是坚持，而是拿到真实数据——让大脑发现，现实通常没有想象中那么可怕。很多\"我到底喜不喜欢\"的问题，其实都可以通过一次低成本行动获得答案，而不是靠长期脑补。",[115,14698,14699,14700],{},"我后来意识到自己有一个循环：在脑子里想象生活→羡慕、兴奋→想到现实成本→害怕→不行动→继续羡慕。要打破的不是\"找到人生目标\"，而是这个循环本身。所以我给自己定了一条原则：",[125,14701,14702],{},"连续想了三次以上、风险又很低的事情，就实际去试一次。",[115,14704,14705],{},"这个原则主要针对即时享受类的事。长期投入的事不能靠\"想了几次就冲\"来决定，因为成本和风险完全不同。但即时享受的事——去游一次泳、去一次足浴、约人打一场球——失败了也就损失几十块钱和一个下午，没有任何长期后果。对这种事，想了三次还在想，就说明不是一时冲动，该去了。",[115,14707,14708],{},"一个人的问题也是现实。独立不等于所有事情都必须一个人完成。可以主动找朋友、同学、同事发起低成本的活动：约一场羽毛球、游一次泳、吃顿饭。有人陪着，很多第一步会容易很多。",[130,14710,14712],{"id":14711},"三把自己搭成一套生活系统","三、把自己搭成一套生活系统",[115,14714,14715],{},"想了一圈，我最后没有找到什么\"人生使命\"，反而想明白一件事：我不需要一个终极答案，我需要一套能持续运行、可以调整的生活系统。",[115,14717,14718,14719,14722],{},"自由可以理解成两部分：",[125,14720,14721],{},"当下的享受，加上未来的选择权。"," 年轻最大的资产就是时间，所以可以大胆做低成本、低风险的试错；但必须有一条长期能力积累线，还要有钱作为第二层保险。不要为了未来把现在全牺牲掉，也不要走另一个极端——把所有时间变成工作、学习和存钱，最后得到资源却没有生活。",[115,14724,14725],{},"这套系统我把它拆成四个账户：",[115,14727,14728,14731],{},[125,14729,14730],{},"生活账户。"," 工资的一部分用于正常生活、娱乐、运动、朋友和探索。想做就做，但在预算范围内。踢球、游泳、足浴、游戏、旅行、恋爱——这些即时享受都是生活本身，不是浪费时间。它们对应的是\"当下的享受\"那一半的自由。",[115,14733,14734,14737],{},[125,14735,14736],{},"安全账户。"," 固定储蓄，为失业、考研、换工作、突发事件留缓冲。储蓄的核心价值不是数字，而是\"可以说不\"的能力：可以离开不合适的工作，可以随时准备考研、转型或者应对突发情况。钱是第二层保险，第一层是能力。",[115,14739,14740,14743],{},[125,14741,14742],{},"能力账户。"," 每天或每周持续投入，形成至少一套真正能带走的能力。它可以来自现在的工作，也可以来自工作之外：数据、编程、业务理解、英语、学历，什么都行。这个账户对应的是那些\"长期投入\"类的欲望——想卷学历、想在工作上做出成绩、想考研读博，本质上都是在往这个账户里存东西。关键是无论未来留在国企、跳槽私企，还是回去读研，这套能力都能用上。如果方向还不确定，那就先提高储蓄率、降低风险，边走边看。",[115,14745,14746,14749],{},[125,14747,14748],{},"身体账户。"," 持续运动、睡眠、饮食。身体是未来几十年所有选择的基础设施，这个\"硬件\"得自己负责。",[115,14751,14752],{},"即时享受的事归生活账户管，低成本就去试；长期投入的事归能力账户和安全账户管，需要持续积累。两种欲望不再混在一起互相打架，而是各有各的位置。",[115,14754,14755],{},"配套的规则也不复杂：",[140,14757,14758,14761,14764,14767,14770,14773,14776,14779],{},[143,14759,14760],{},"低成本、低风险、连续想了几次的事，主动尝试一次。",[143,14762,14763],{},"短期兴趣可以随心变化，不要求一个爱好终身坚持。",[143,14765,14766],{},"长期能力和身体要有最低投入，不能完全看当天心情。",[143,14768,14769],{},"当前消费可以有，但不能为了即时欲望破坏未来选择权。",[143,14771,14772],{},"不要把\"现在做不到\"解释成\"我其实不想要\"。",[143,14774,14775],{},"不要把\"别人拥有\"直接解释成\"我也一定想拥有\"。",[143,14777,14778],{},"不要因为边际效用递减，就否定曾经带来快乐的事。",[143,14780,14781],{},"不追求极端自律，设置一个低但稳定、不能长期突破的最低投入。",[115,14783,14784,14785,14788],{},"原则可以概括成一句：",[125,14786,14787],{},"兴趣随心，底线固定，方向可以变。"," 身体每周有固定运动底线，具体形式随便换；能力每周固定投入时间，具体方向可以调整；财务每月固定存一部分，剩下的在预算内自由花。",[130,14790,14792],{"id":14791},"四关于未来不需要现在知道答案","四、关于未来：不需要现在知道答案",[115,14794,14795],{},"现在不需要决定留在现在的公司、去总部、跳私企、考研、读博、出国哪个是唯一正确答案。二十多岁，合理的状态不是\"已经知道我要成为什么人\"，而是\"我有能力继续探索，而且探索失败也不会把自己搞崩\"。",[115,14797,14798],{},"真正需要做的是在未来两三年持续积累工作经验、可迁移能力、储蓄和身体，让未来的问题从\"我有没有资格选择\"变成\"我想选择哪一个\"。",[115,14800,14801],{},"也不需要每天审判自己。半年复盘一次就够：钱有没有增加？能力有没有比半年前更强？身体状态有没有改善？过去半年有没有真正体验过一些东西？如果这些方面总体都在向前，说明人生轨道并没有问题。",[115,14803,14804],{},"最重要的目标可以是一个状态，而不是一个终点：把自己建设成一个有钱、有能力、有身体、有选择权、敢于行动的人。未来想留在国企、去总部、跳私企、考研、读博、出国，都可以在有选择权的基础上重新决定。",[130,14806,14807],{"id":14807},"写在最后",[115,14809,14810,14811],{},"如果非要给这套生活一个概括，大概是这句：",[125,14812,14813],{},"我愿意用一部分当下的自由，换取未来更大的自由；剩下的时间，好好享受现在。",[115,14815,14816],{},"现在的人生任务，不是找到唯一正确的人生，而是在认真积累的同时真正去生活：保持身体、持续学习、固定储蓄、勇敢试错。让自己越来越有能力、越来越有选择权——同时允许自己，享受当下。",{"title":625,"searchDepth":732,"depth":732,"links":14818},[14819,14820,14821,14822,14823],{"id":14643,"depth":732,"text":14644},{"id":14683,"depth":732,"text":14684},{"id":14711,"depth":732,"text":14712},{"id":14791,"depth":732,"text":14792},{"id":14807,"depth":732,"text":14807},"2026-08-13","从\"什么都想要\"到四个账户的生活系统：身体、能力、储蓄与当下的享受",{},"\u002Fwriting\u002F2026-08-13-want-everything-got-nothing","2026-08-13T23:25:16+08:00",{"title":14623,"description":14825},"want-everything-got-nothing","writing\u002F2026-08-13-want-everything-got-nothing",[14833,14834,14835,14836],"自我认知","生活系统","行动力","储蓄","2026-08-15","PMPrSEXNtdbY5gyAMANwqVMLNaWYmiTMN8tFovf_tIk",{"id":14840,"title":14841,"body":14842,"category":8345,"date":15085,"description":15086,"extension":827,"hidden":828,"kind":834,"meta":15087,"navigation":30,"path":15088,"publishedAt":15089,"seo":15090,"series":834,"slug":15091,"stem":15092,"tags":15093,"unit":834,"updated":14824,"__hash__":15099},"writing\u002Fwriting\u002F2026-08-04-steel-industry-profit-chain-analysis.md","上下游产业链的利润分配与长期趋势：从净利率对比说起",{"type":112,"value":14843,"toc":15073},[14844,14849,14853,14856,14859,14862,14865,14869,14872,14878,14884,14887,14891,14894,14897,14900,14903,14909,14913,14916,14919,14922,14926,14929,14935,14941,14944,14951,14955,14958,14965,14968,14975,14979,14982,14988,14994,15000,15003,15007,15010,15013,15016,15020,15023,15029,15032,15038,15044,15051,15055,15061,15067],[522,14845,14846],{},[115,14847,14848],{},"上汽净利率2.7%，头部钢企3.59%。一家造车的和一家炼钢的，利润率居然差不多。这个看似荒诞的对比，引出了一系列追问：钢厂的利润被谁拿走了？上游矿山为什么这么赚钱？头部央企的突围能不能改变格局？钢铁行业的未来到底是周期波动还是结构性下行？本文沿着这些问题逐层展开，试图给出一个理性的判断。",[130,14850,14852],{"id":14851},"一两条利润率曲线的交汇","一、两条利润率曲线的交汇",[115,14854,14855],{},"2025年上汽集团净利率2.70%，宝钢股份3.59%。十年前这个对比不可想象--整车厂曾是高利润的代名词，钢厂则是微利的苦生意。现在两者的利润率交汇了，但原因各不相同。",[115,14857,14858],{},"整车厂的利润率被三层力量压缩：价格战（新能源车降价均幅3万元）、上游成本侵蚀（碳酸锂涨132%、存储芯片从20元涨到近100元）、以及转型期的高强度研发投入。2026年上半年整车制造利润率仅1.5%，卖一辆10万元的车，整车厂到手约1500元。",[115,14860,14861],{},"钢厂的利润率则长期低迷。2026年上半年行业平均销售利润率0.86%，247家样本钢企中近三分之二亏损。一季度重点企业钢铁主业利润同比暴跌85.6%。",[115,14863,14864],{},"两者利润率交汇的真正含义是：**\"制造\"环节的利润正在被系统性挤压，利润沿产业链向上游资源端和技术端转移。** 电池厂（宁德时代利润率17%）拿走了电动车的利润，铁矿石巨头（利润率30-40%+）拿走了钢铁的利润。留在中间环节的企业，无论造车还是炼钢，都只能赚加工辛苦费。",[130,14866,14868],{"id":14867},"二钢厂的夹心困境","二、钢厂的\"夹心\"困境",[115,14870,14871],{},"钢厂的微利不是经营不善，而是产业链位置决定的。",[115,14873,14874,14877],{},[125,14875,14876],{},"上游：铁矿石80%以上依赖进口，被四大矿山（淡水河谷、力拓、必和必拓、FMG）垄断。"," 这四家控制着全球70%以上的铁矿石贸易量，开采成本仅15-25美元\u002F吨。国内钢厂在每年铁矿石谈判中几乎没有话语权。2026年上半年上游煤炭企业利润同比增长41.1%，而中游钢企利润下降了25%--利润在向上游集中。",[115,14879,14880,14883],{},[125,14881,14882],{},"下游：房地产需求持续萎缩（6月同比降25%），传统用钢领域需求疲软。"," 钢价涨不动，成本又传不下去。2026年上半年螺纹钢期货高低价差仅293元\u002F吨，波动幅度远小于往年。",[115,14885,14886],{},"上游拿走资源暴利，下游握着买方议价权，中间的钢厂两头受气。这个格局不是周期性的，而是结构性的--只要铁矿石供应格局不变、房地产需求不复苏，\"夹心\"状态就会持续。",[130,14888,14890],{"id":14889},"三需求见顶一个结构性的转折","三、需求见顶：一个结构性的转折",[115,14892,14893],{},"判断一个行业的长期趋势，需求比供给更重要。钢铁行业的需求端正在经历一个历史性转折。",[115,14895,14896],{},"中国是全球最大的钢铁消费国，粗钢产量占全球一半以上。历史上，中国钢铁需求的最大驱动力是房地产和基建。2020年中国粗钢产量达到约10.65亿吨的峰值，此后开始回落。世界钢铁协会数据显示，2026年上半年中国粗钢产量500.0 Mt，同比下降3.0%。",[115,14898,14899],{},"需求结构的变化更具决定性：2025年制造业用钢占比达到53%，首次超过建筑业。这不是周期性切换--房地产新开工面积从2020年峰值下滑超过60%，而人口结构（劳动年龄人口2012年见顶、总人口开始负增长）决定了这种下滑不可逆。制造业用钢（汽车、家电、新能源）虽然在增长，但体量上不足以完全填补房地产留下的缺口。",[115,14901,14902],{},"全球来看，2026年上半年全球粗钢产量931.5 Mt，同比下降0.7%。需求增量几乎全部来自印度（同比增长7.1%）和越南（同比增长26.9%），但这些新兴市场绝对体量仍小，无法抵消中国的结构性回落。",[115,14904,14905,14908],{},[125,14906,14907],{},"钢铁行业的总需求曲线已经从\"增长\"转为\"横盘至缓降\"。"," 在一个总需求不再增长的市场里，所有企业的竞争本质上都是存量博弈。",[130,14910,14912],{"id":14911},"四供给收缩政策在倒逼但能否对冲","四、供给收缩：政策在倒逼，但能否对冲？",[115,14914,14915],{},"面对需求回落，政策在主动压减产能。2026年版《钢铁行业产能置换实施办法》要求1.5:1减量置换，同时钢铁行业纳入全国碳市场，碳价逼近94元\u002F吨。",[115,14917,14918],{},"供给收缩能否对冲需求下滑？2016年供给侧改革曾短暂推高钢价和利润，但那次是\"行政限产+地产周期上行\"的双轮驱动。现在地产周期已经下行，单纯压产能的效果存疑。2026年中国钢材需求量预计8亿吨，粗钢产量预测9.35亿吨，供需缺口超过1.35亿吨。",[115,14920,14921],{},"更关键的是，产能收缩导致的价格回升，受益最大的是成本最低的龙头企业，而非全行业。行业整合加速，头部央企等龙头份额提升，但这是\"龙头拿走更大蛋糕\"的零和博弈，不是行业整体利润池的扩大。",[130,14923,14925],{"id":14924},"五尝试突围方向正确但不足以逆转趋势","五、尝试突围：方向正确，但不足以逆转趋势",[115,14927,14928],{},"宝钢正在做两件事来改变自己的产业链位置。",[115,14930,14931,14934],{},[125,14932,14933],{},"向上游走。"," 铁矿石自给率目前约36%，目标50%以上，核心抓手是几内亚西芒杜铁矿（品位65%+，总产能1.2亿吨\u002F年，2025年11月投产）。自给率超过50%是一个质变点：铁矿石涨价时自有矿山利润暴涨，对外采购需求减半，谈判筹码增加。",[115,14936,14937,14940],{},[125,14938,14939],{},"向高端走。"," 该企业2025年研发投入近200亿元，0.16mm超薄取向硅钢全球唯一量产（纯利润200元\u002F吨以上，普通钢材仅20-100元\u002F吨），新一代汽车高强钢在燃油车身中占比超八成。",[115,14942,14943],{},"但两件事都有局限。西芒杜面临政治、基建和运营风险，实际成本不确定；且这是该企业一家的自给率，行业整体进口依存度依然超80%。高端产品方向对，但取向硅钢总产能120万吨，对比5000万吨年产能占比仅3%左右--技术领先未能改变整体利润结构，5000万吨产能中绝大多数仍是微利的普通钢材。",[115,14945,14946,14947,14950],{},"雪球用户\"四七八二\"在某钢企个股分析中指出：该企业的取向硅钢全球产能500万吨中占120万吨且全为高端，无取向硅钢也专注于高牌号产品。这些产品的技术壁垒很高，但",[125,14948,14949],{},"壁垒高不等于利润厚，因为量太小。"," 从\"有高端产品\"到\"高端产品占比足以扭转利润率\"，中间需要一个量级跳跃。",[130,14952,14954],{"id":14953},"六四大矿山为什么不整合下游","六、四大矿山为什么不整合下游",[115,14956,14957],{},"理解了钢厂的困境，自然会问：四大矿山为什么不把下游也吃下来？",[115,14959,14960,14961,14964],{},"答案很简单：",[125,14962,14963],{},"不需要。"," 矿山核心业务回报率极高，钢铁制造是低利润重资产行业。把高回报的现金流投入低回报的领域，会拉低整体资本回报率。与其买钢厂，不如把钱投向回报率更高的矿产（铜、锂）。",[115,14966,14967],{},"2013年大宗商品价格回落后，巨头们从\"纵向一体化\"转向\"轻资产\"策略--控矿权、轻基建、提回报。不做股权收购，但与核心客户（如头部央企）合资开发项目，锁定长期销售渠道，不承担运营钢厂的风险。",[115,14969,14970,14971,14974],{},"四大矿山站在产业链利润顶端，没有必要向低利润环节扩张。这个事实本身就说明了钢铁产业链的利润分配格局：",[125,14972,14973],{},"上游吃肉，下游喝汤。"," 只要这个格局不变，钢厂的利润天花板就在那里。",[130,14976,14978],{"id":14977},"七全球格局三个世界","七、全球格局：三个世界",[115,14980,14981],{},"世界钢铁协会2026年6月数据显示，全球钢铁业呈现明显分化：",[115,14983,14984,14987],{},[125,14985,14986],{},"需求增长区（印度、东南亚）。"," 印度是全球钢铁需求最强劲的市场，2025财年消费量同比增长8%；越南上半年产量同比增长26.9%。印度JSW Steel每吨利润618元，受益于本土需求红利。",[115,14989,14990,14993],{},[125,14991,14992],{},"需求疲软区（欧洲、日韩）。"," 欧盟过去三年产量小幅下滑，能源成本高企；日韩趋势性收缩，制造业外流。日本制铁靠结构改革（高炉从15座减至10座）和定价权，每吨利润710元全球第一，但这是\"小而美\"模式，不可复制到中国头部钢企的体量。",[115,14995,14996,14999],{},[125,14997,14998],{},"贸易保护区（美国）。"," 靠50%关税壁垒维持高钢价（热轧价格是亚洲的两倍），纽柯钢铁利润大增。但这种保护主义策略以牺牲下游制造业为代价，且不可复制。",[115,15001,15002],{},"中国头部钢企每吨利润仅约221元，约为日本制铁的三分之一。规模全球领先，但\"大而不强\"的特征明显--产品仍以普通钢材为主，上游资源尚未完全掌握。",[130,15004,15006],{"id":15005},"八产业一线的印证","八、产业一线的印证",[115,15008,15009],{},"雪球用户黄建平在一家头部钢企做了8年控制模型工程师，前两年几乎天天加班，后面几年经常半夜被叫去处理问题。他接触过外包协力工--技能水平更低，工作更辛苦，收入也更低。",[115,15011,15012],{},"他的判断很直接：**\"如果我是这家企业的董事长，我也没有更好办法。\"** 行业竞争激烈、周期性强、动辄亏损，资本回报率比较低，利润和股价10多年徘徊。他说\"要靠发展解决问题，没有灵丹妙药\"。",[115,15014,15015],{},"这个判断来自产业内部人的体感，与宏观数据相互印证：钢厂的微利不是管理问题，而是产业链位置和行业基本面决定了天花板。黄建平后来离开那家企业，管理私募基金，跨行业投研20年，现在用 AI agent 搭建投研平台。他的人生选择本身就是一个隐喻--在趋势下行的行业里，个人最优策略是保持迁移能力，去寻找更好的赛道。",[130,15017,15019],{"id":15018},"九理性审视是周期底部还是结构性下行","九、理性审视：是周期底部还是结构性下行？",[115,15021,15022],{},"以上分析指向一个判断：钢铁行业的长期趋势是下行的。但必须对这个判断保持审慎，考虑反方证据：",[115,15024,15025,15028],{},[125,15026,15027],{},"周期波动的可能性。"," 钢铁是强周期行业，2026年Q1行业利润已同比改善，247家钢企盈利率从底部回升。库存周期、产能出清后的价格反弹都是真实存在的。如果将当前视为周期底部，那么未来可能出现利润修复。",[115,15030,15031],{},"但这里需要区分周期性波动和结构性趋势。周期性利润修复是在一个趋势线上的波动。每一次周期高点的利润率，大概率低于上一次--因为总需求曲线在缓降，产能过剩在持续，上游垄断格局未变。如果这个判断成立，那么\"周期底部\"的反弹高度会越来越低。",[115,15033,15034,15037],{},[125,15035,15036],{},"技术变革的可能性。"," 电炉炼钢比例提升可能打破铁矿石垄断。中国电炉钢比例目前约10-15%，远低于美国（70%+）。随着废钢积累量增长，这个方向值得期待。但这是一个5-10年的缓慢过程，短期内不足以改变成本结构。氢冶金等新技术也在探索中，但距离工业化量产尚远。",[115,15039,15040,15043],{},[125,15041,15042],{},"行业整合的可能性。"," 产能出清后龙头份额提升，头部央企等可能获得更强的定价权。但龙头利润率改善是\"份额逻辑\"而非\"行业逻辑\"--全行业利润池在缩小，龙头拿走更大比例，不代表行业整体在变好。",[115,15045,15046,15047,15050],{},"综合来看：",[125,15048,15049],{},"钢铁行业面临的是需求结构性回落+成本格局刚性+技术替代缓慢的三重压力。"," 短期内有周期性反弹的空间，但长期趋势线的斜率大概率是向下的。这个判断不是终局论--技术变革（电炉、氢冶金）和资源整合（西芒杜）可能在5-10年后改变行业格局。但在可见的未来，这是一个需要正视的现实。",[130,15052,15054],{"id":15053},"十对投资者和产业人的含义","十、对投资者和产业人的含义",[115,15056,15057,15060],{},[125,15058,15059],{},"对投资者："," 趋势下行的行业不是不能投，而是不能赌行业整体回暖。这家头部钢企市净率0.62，反映的是市场对行业基本面的悲观定价。如果这家央企能兑现资源整合和产品高端化的战略，存在估值修复的α空间。但这是\"行业β下行+个体α突出\"的投资逻辑，不是\"行业会好转\"的赌注。寻找的是能在下行中扩大份额、改变产业链位置的个体，而非等待行业周期回归的贝塔。",[115,15062,15063,15066],{},[125,15064,15065],{},"对产业中的人："," 黄建平从钢企工程师到跨界投资人的路径提供了一种参考。在趋势下行的行业里，个人的人力资本是最核心的资产。技能的可迁移性、跨行业学习能力、对产业链规律的认知--这些是不受行业周期约束的。他说\"别被专业、出身等限制了发展空间\"，这句话放在钢铁行业的语境下，有特殊的重量。",[115,15068,15069],{},[15070,15071,15072],"em",{},"本文基于一段 AI 辅助对话的思考路径整理改写，参考了世界钢铁协会数据及雪球用户黄建平等的公开文章。涉及的数据和判断请以权威来源为准，不构成投资建议。",{"title":625,"searchDepth":732,"depth":732,"links":15074},[15075,15076,15077,15078,15079,15080,15081,15082,15083,15084],{"id":14851,"depth":732,"text":14852},{"id":14867,"depth":732,"text":14868},{"id":14889,"depth":732,"text":14890},{"id":14911,"depth":732,"text":14912},{"id":14924,"depth":732,"text":14925},{"id":14953,"depth":732,"text":14954},{"id":14977,"depth":732,"text":14978},{"id":15005,"depth":732,"text":15006},{"id":15018,"depth":732,"text":15019},{"id":15053,"depth":732,"text":15054},"2026-08-04","上汽净利率2.7%，宝钢股份净利率3.59%。从这条对比出发，逐层追溯利润沿产业链向上集中的规律，分析钢铁行业需求见顶、成本刚性、技术替代缓慢三重压力，论证长期β下行的结构性原因。",{},"\u002Fwriting\u002F2026-08-04-steel-industry-profit-chain-analysis","2026-08-04T01:06:00+08:00",{"title":14841,"description":15086},"steel-industry-profit-chain-analysis","writing\u002F2026-08-04-steel-industry-profit-chain-analysis",[15094,15095,15096,15097,15098],"钢铁","产业链","央国企","铁矿石","行业分析","UuvtRSoECSuI_NZSCZ-YW281upjuEImA0mcQ3PAkJuQ",{"id":15101,"title":15102,"body":15103,"category":8345,"date":15431,"description":15432,"extension":827,"hidden":828,"kind":834,"meta":15433,"navigation":30,"path":15434,"publishedAt":15435,"seo":15436,"series":834,"slug":15437,"stem":15438,"tags":15439,"unit":834,"updated":14824,"__hash__":15445},"writing\u002Fwriting\u002F2026-08-03-yen-depreciation-cognition-drilldown.md","从日元贬值到行为金融：货币、利率与制度",{"type":112,"value":15104,"toc":15420},[15105,15110,15114,15117,15120,15123,15126,15133,15137,15140,15143,15146,15152,15155,15158,15164,15168,15171,15178,15192,15195,15198,15205,15209,15212,15215,15221,15224,15230,15236,15242,15249,15253,15256,15259,15262,15265,15271,15275,15278,15281,15288,15291,15298,15301,15307,15311,15314,15317,15320,15346,15352,15356,15359,15365,15371,15377,15383,15389,15396,15400,15403,15409,15412,15415],[522,15106,15107],{},[115,15108,15109],{},"一条\"日元贬值\"的新闻，可以被当成财经快讯听完即忘，也可以作为入口，一路下探到货币本质、利率定价、行为金融和国家制度设计。本文记录的是后者--一次通过连续追问凿穿金融概念表层的思考过程。",[130,15111,15113],{"id":15112},"一日元为什么跌表层是利差底层是人口","一、日元为什么跌：表层是利差，底层是人口",[115,15115,15116],{},"2026年7月底，日元跌到近40年最低点，一度突破1美元兑163日元。日本政府单日动用约528亿美元干预，美国也罕见地参与了联合干预--这是2011年以来首次。",[115,15118,15119],{},"直接原因很清楚：美日利差。美国联邦基金利率约3.5%，日本政策利率仅约1%，差了2.5个百分点。资金天然从低息货币流向高息货币--借日元换美元吃利差，这是经典的套息交易（carry trade）。卖日元的人多了，日元就跌。",[115,15121,15122],{},"但\"利差大\"只是把问题往后推了一步。为什么日本不敢加息？因为日本政府债务超过GDP的220%，大幅加息意味着利息支出吞噬财政空间。为什么美国能加息？因为2021-2023年通胀高企，美联储必须收紧。为什么日本通胀来了却不像美国那样加息？因为日本经济长期疲软，加息可能直接把经济打回通缩。",[115,15124,15125],{},"再往下：日本经济为什么疲软？人口老龄化、劳动力萎缩、产业竞争力被中韩追赶。这些是慢变量，不会因为一次干预或一次加息就改变。",[115,15127,15128,15129,15132],{},"所以日元贬值的根因，不在汇率层面，而在人口结构和产业竞争力层面。",[125,15130,15131],{},"金融现象的表层是数字，底层是人口、技术和制度。"," 这条规律不只适用于日元--理解任何资产的长期走势，最终都会落到这几个慢变量上。",[130,15134,15136],{"id":15135},"二汇率是什么从菜市场到欠条","二、汇率是什么：从\"菜市场\"到\"欠条\"",[115,15138,15139],{},"理解了\"为什么跌\"，下一个问题是\"这东西到底是什么\"。",[115,15141,15142],{},"一个有效的具象方式：把货币当成菜市场里的菜。美元是猪肉（全球硬通货），日元是卷心菜（量大、利息低）。\"1美元=163日元\"就是\"1斤猪肉换163斤卷心菜\"。美元加息了（大家都抢美元去存钱），猪肉涨价；或者日本央行大量印钞（卷心菜丰收），菜跌价。兑换比例就这么变了。",[115,15144,15145],{},"这个比喻抓住了供需关系，但有一个需要警惕的边界：它可能让人误以为货币是一种\"商品\"。实际上，货币不是商品，而是一张欠条。",[115,15147,15148,15149],{},"1971年美元与黄金脱钩后，全球货币都不再锚定实物。纸币背后没有金库，电子支付连纸都没有。它的价值来自一个更抽象的东西：",[125,15150,15151],{},"国家信用，具体表现为税收驱动的需求。",[115,15153,15154],{},"想象一家公司不发现金，只发\"内部饭票\"。饭票有价值，是因为公司规定交水电费、租宿舍只收饭票。你为了住宿舍，就必须用劳动去换饭票。国家版的故事一样：法律规定交税只能用本国货币，全国几亿人为了交税和还贷，必须去赚这种\"积分\"。只要国家机器在运转，这张纸就有强制性信用。",[115,15156,15157],{},"如果国家疯狂印饭票而食堂里的鸡没变多，每张饭票换到的鸡丝就变少了--这是对内贬值（通胀）。如果外面的人觉得这家公司未来不行了，不愿意用原价买它的饭票--这是对外贬值（汇率下跌）。",[115,15159,15160,15163],{},[125,15161,15162],{},"对内贬值看货币供给与商品供给的匹配，对外贬值看全球资本对该国未来的信心。两者可以独立发生。"," 日本的情况是：对内通胀终于有所抬头，但对外信心严重不足--因为利率太低，资本不愿意持有日元。",[130,15165,15167],{"id":15166},"三利率的底层不是央行在定是人口和技术在定","三、利率的底层：不是央行在定，是人口和技术在定",[115,15169,15170],{},"汇率短期跟着利率跑。但利率是谁定的？",[115,15172,15173,15174,15177],{},"不是央行拍的板。央行只是跟随一个更底层的\"水位线\"--经济学称之为",[125,15175,15176],{},"自然利率","（natural rate of interest），即既不刺激也不抑制经济的平衡点。这个水位由两个慢变量决定：",[140,15179,15180,15186],{},[143,15181,15182,15185],{},[125,15183,15184],{},"人口结构","：干活的人多不多。劳动力萎缩，经济增长动力不足，自然利率就低。日本是典型--老龄化严重，利率从高位一路降到零甚至负值。",[143,15187,15188,15191],{},[125,15189,15190],{},"生产率","：干活的效率高不高。技术进步快，单位产出提升，经济能承受更高的资金成本，自然利率就高。美国凭借AI和科技革命，至今仍能维持相对较高的利率。",[115,15193,15194],{},"央行在这个水位线上下做微调：通胀高了加息压制需求，经济冷了降息刺激需求。但央行的自由度有限。如果自然利率本身在下行（人口老化、增长放缓），央行长期只能跟着往下走。",[115,15196,15197],{},"这个框架对中国有直接意义。中国正在经历人口结构转型--劳动年龄人口在2012年前后见顶，此后持续下降。如果人口结构决定自然利率的方向，那么中国长期利率大概率会进入下行通道。但中国有一个日本当年没有的缓冲：科技产业升级。电动车、光伏、AI在提高单人产出，用机器替代人。这意味着利率下行的速度可能比日本慢，但方向很难逆转。",[115,15199,15200,15201,15204],{},"这个判断对个人的直接含义：",[125,15202,15203],{},"如果你的房贷利率是3%-4%，在未来利率可能降至1%-2%的环境下，它就是一个\"高息包袱\"。"," 除非你有收益率超过这个利率的投资渠道，否则提前还贷在数学上等价于锁定了一个确定的高收益。",[130,15206,15208],{"id":15207},"四套息交易逻辑越简单容错率越低","四、套息交易：逻辑越简单，容错率越低",[115,15210,15211],{},"套息交易的逻辑简单到一句话：借低息货币，买高息货币，吃利差。",[115,15213,15214],{},"和研究个股相比，它的\"变量数量\"少得多。研究一家公司要看财报、管理层、行业竞争、原材料价格、销售渠道，还要面对信息不对称。而套息交易的核心变量只有一个：两国国债收益率之差。只要这个差在，钱就会流，方向就确定。",[115,15216,15217,15218],{},"但金融市场有一个铁律：",[125,15219,15220],{},"越是逻辑直白的交易，赚起来越考验风控。",[115,15222,15223],{},"原因有三层：",[115,15225,15226,15229],{},[125,15227,15228],{},"杠杆放大波动。"," 套息交易的利差通常只有2-3个百分点，不加杠杆收益微薄。所以参与者几乎都用杠杆，10倍、50倍很常见。日元如果突然反向升值1%，10倍杠杆就是亏10%--吃两年的利差，一天亏完。波动再大就会穿仓，不光亏光本金，还倒欠券商。",[115,15231,15232,15235],{},[125,15233,15234],{},"逻辑透明导致踩踏。"," 所有人都知道\"借日元买美元赚利息\"，全球累积了数万亿美元的套利盘。一旦日本央行突然加息或出现黑天鹅事件，所有人同时平仓：卖掉美元资产，抢回日元还债。日元急速暴涨，套利者两边挨打--美元资产跌了，换回日元还的钱也变多了。这种踩踏在金融史上被称为\"寡妇制造者交易\"（widow-maker trade）。",[115,15237,15238,15241],{},[125,15239,15240],{},"赚的是息差，亏的是本金。"," 股票赚的是企业成长的钱，蛋糕在变大。套利赚的是\"定价偏差\"，市场平稳时像收租，但波动率一上来，那点利息根本不够覆盖汇率亏损。",[115,15243,15244,15245,15248],{},"简言之：",[125,15246,15247],{},"股票像种地--看天看地，收成慢，但地还在；汇率套利像走钢丝--路是直的，一眼看到头，但容错率极低。"," 简单不等于容易。正因为简单，它吸引的人更多，踩踏时也更惨烈。",[130,15250,15252],{"id":15251},"五汇率高不等于国力强","五、汇率高不等于国力强",[115,15254,15255],{},"一个常见的直觉误区：既然1美元能换163日元，那美元\"贵\"，说明美国比日本强。推而广之，哪个国家汇率最高，哪个国家国力最强。",[115,15257,15258],{},"这个推论不成立。科威特第纳尔1个能换约3.3美元，但没人会认为科威特国力是美国的3倍。英镑比美元\"贵\"，也不意味着英国比美国强。",[115,15260,15261],{},"汇率数字的高低，本质上是\"计量单位\"的差异。日元面值大，是因为日本历史上经历过严重通胀，货币单位被刻度得很大。这就像有的东西按克卖，有的按吨卖，不代表按克卖的东西更贵重。",[115,15263,15264],{},"评价国家强弱，看的是\"总购买力\"而非\"单价\"。按购买力平价（PPP）计算，中国的GDP已经超过美国，但人民币兑美元汇率才7点多。制造业大国--日本、德国、中国--没有一个是汇率最高的。高汇率国家往往是资源型经济体（科威特、挪威），产业结构单一，恰恰不是\"国力强\"的表现。",[115,15266,15267,15270],{},[125,15268,15269],{},"汇率反映的不是国力绝对值，而是全球资本对该国未来利息和通胀的预期。"," 对一个国家来说，汇率太高意味着出口商品变贵、竞争力下降。最有利的状态不是\"最高\"，而是\"稳定\"。",[130,15272,15274],{"id":15273},"六为什么人明知正确却选择错误","六、为什么人明知正确，却选择错误",[115,15276,15277],{},"所有长期逻辑都指向\"确定性的正确\"：定投指数、提前还贷、配置黄金、减少高息负债。但大多数散户依然选择追涨杀跌、加杠杆、炒短线。",[115,15279,15280],{},"为什么？",[115,15282,15283,15284,15287],{},"行为经济学给出的一个解释是",[125,15285,15286],{},"双曲贴现","（hyperbolic discounting）：人对\"近期的等待\"比\"远期的等待\"更不耐烦。现在给你100块还是明天给你110块？多数人选现在。365天后给你100块还是366天后给你110块？多数人愿意多等一天。同样是多等一天换10块，但\"现在\"这个时间节点会让人产生断崖式的厌恶。",[115,15289,15290],{},"双曲贴现的核心特征是\"时间不一致性\"：今天的自己和未来的自己偏好不同。今天你决定\"下个月开始存钱\"，但下个月到了，你又决定\"再等一个月\"。这种偏好反转，才是双曲贴现区别于普通\"时间偏好\"的关键。",[115,15292,15293,15294,15297],{},"但这个模型不能解释全部。还有一个更朴素的因素：",[125,15295,15296],{},"人的寿命有限，而\"长期正确\"需要的等待时间可能超过个人的时间预算。"," 一个40岁的人知道定投20年后会有可观回报，但60岁才兑现的收益，对他当下的房贷、孩子学费、父母医疗费毫无帮助。长期正确解决不了短期的现金流压力。",[115,15299,15300],{},"这不是不理性，而是一个约束条件下的理性选择。当流动性紧张时，\"长期最优\"让位于\"短期存活\"是合理的。问题出在：很多人即使不缺流动性，也依然无法执行长期策略。这才是双曲贴现真正起作用的领域--不是因为等不起，而是因为大脑天然不认\"20年后\"的账。",[115,15302,15303,15306],{},[125,15304,15305],{},"应对方式不是\"更自律\"，而是\"减少需要自律的场景\"。"," 自动扣款让钱在到手之前就被锁定，不给大脑的即时反应系统留出决策窗口。把\"20年后退休\"替换为\"给孩子存大学学费\"或\"55岁环游世界\"这样的具体画面，让远期目标变得可感知。这些不是心灵鸡汤，而是针对认知偏差的结构性对策。",[130,15308,15310],{"id":15309},"七制度作为人性的补丁","七、制度作为人性的补丁",[115,15312,15313],{},"如果个体的短视是结构性的，社会的应对也必须是结构性的。",[115,15315,15316],{},"中国的五险一金就是这样一个\"强制性纠偏系统\"。养老保险对抗\"等老了再存\"的拖延，医疗保险对抗\"我身体好\"的盲目，公积金对抗\"月光族\"的生存危机。国家知道你管不住未来的自己，所以在你年轻时强行没收你的\"短视权\"。",[115,15318,15319],{},"全球各国都在做类似的事，但路径不同：",[140,15321,15322,15328,15334,15340],{},[143,15323,15324,15327],{},[125,15325,15326],{},"新加坡","（中央公积金 CPF）：完全个人账户制，钱在自己名下，多存多得，回报率稳定在4%以上。本质是\"对自己负责\"。",[143,15329,15330,15333],{},[125,15331,15332],{},"德国、日本","（现收现付制）：年轻人缴费供养退休者，考验人口结构。本质是\"代际互助\"。",[143,15335,15336,15339],{},[125,15337,15338],{},"美国","（兜底制）：社保缴费比例仅12.4%（雇主雇员各6.2%），是主要国家中最低的，大头靠个人投资和401(k)。本质是\"基本兜底，自求多福\"。",[143,15341,15342,15345],{},[125,15343,15344],{},"中国","：混合体。社会统筹（现收现付）+ 个人账户（积累制），但个人账户长期空账运行--你缴的钱并没有真正存在你名下，而是被用来支付当代退休人员。",[115,15347,15348,15349],{},"这里有一个值得深思的矛盾。制度设计上写了\"你的钱在你账户里\"，实际上账户是空的。这与新加坡CPF的真金白银有本质区别。如果年轻人发现自己缴的钱并没有真正存下来，制度本身的信用就会受损，反而可能加剧短视行为--\"既然钱没存下来，不如现在就花掉\"。",[125,15350,15351],{},"制度对抗短视的有效性，最终取决于它能否维持\"代际契约\"的可信度。",[130,15353,15355],{"id":15354},"八普通人能做什么","八、普通人能做什么",[115,15357,15358],{},"在国内合规框架内，普通人想应对汇率波动，大致有这几条路：",[115,15360,15361,15364],{},[125,15362,15363],{},"QDII基金。"," 用人民币买基金，机构帮你换汇投资海外。人民币贬值时，海外资产换回人民币会多出一笔汇兑收益。门槛低，支付宝就能买。但约半数QDII限购，场内ETF溢价可能超20%，赎回到账需7-10天。",[115,15366,15367,15370],{},[125,15368,15369],{},"结构性存款。"," 保本浮动收益，收益挂钩汇率走势。判断对了拿高息，错了拿低息，但不亏本金。是境内最接近\"押注汇率方向\"的合规工具，通常1万人民币起。",[115,15372,15373,15376],{},[125,15374,15375],{},"黄金。"," 以美元计价，人民币贬值时国内金价通常上涨。适合配置5%-10%作为对冲。",[115,15378,15379,15382],{},[125,15380,15381],{},"外币定存。"," 直接换美元存定期，部分银行利率可达3%-3.6%。但致命风险是汇率反向：如果人民币升值幅度超过利差，利息会被完全吞噬甚至本金亏损。",[115,15384,15385,15388],{},[125,15386,15387],{},"港卡渠道。"," 持有香港银行卡可以接触更多产品（外币联系存款、跨境理财通、美元定存），但2026年开户门槛已大幅提高，且外汇保证金交易在内地仍属灰色地带。",[115,15390,15391,15392,15395],{},"核心原则：",[125,15393,15394],{},"用本来就要花的\"刚需外币\"去做，赚了是白捡，亏了也无所谓。"," 如果判断不了方向，最稳妥的\"套利\"是提前还掉高息负债--它等于一个保本保息、无汇率风险的确定收益。",[130,15397,15399],{"id":15398},"九金融的底层逻辑是时间","九、金融的底层逻辑是时间",[115,15401,15402],{},"回头看这条思考路径：从\"日元为什么跌\"到\"汇率是什么\"到\"货币是什么\"到\"利率谁定的\"到\"人为什么不按长期逻辑行动\"到\"国家如何对抗人性短视\"。每一步都是被\"为什么\"推着往下走的。",[115,15404,15405,15406],{},"这条路径揭示了一件事：",[125,15407,15408],{},"金融市场的底层逻辑只有两个字--\"时间\"。",[115,15410,15411],{},"汇率是两国未来的贴现。利率是人口和技术的影子价格。套息交易赚的是时间差，亏的是时间到了时的剧烈结算。双曲贴现是大脑对时间的非线性估值。五险一金是制度对个人时间偏好的强制纠正。",[115,15413,15414],{},"谁能忍受\"无聊\"和\"等待\"，谁就拿到了那笔被多数人放弃的确定性。这不是鸡汤，而是上述每一层逻辑共同指向的结论。",[115,15416,15417],{},[15070,15418,15419],{},"本文基于一段 AI 辅助对话的思考路径整理改写，涉及的数据和判断请以权威来源为准，不构成投资建议。",{"title":625,"searchDepth":732,"depth":732,"links":15421},[15422,15423,15424,15425,15426,15427,15428,15429,15430],{"id":15112,"depth":732,"text":15113},{"id":15135,"depth":732,"text":15136},{"id":15166,"depth":732,"text":15167},{"id":15207,"depth":732,"text":15208},{"id":15251,"depth":732,"text":15252},{"id":15273,"depth":732,"text":15274},{"id":15309,"depth":732,"text":15310},{"id":15354,"depth":732,"text":15355},{"id":15398,"depth":732,"text":15399},"2026-08-03","从一条日元贬值新闻出发，通过连续追问层层下探到货币本质、利率定价、行为金融和国家制度设计，展示金融概念表层与底层逻辑之间的认知路径。",{},"\u002Fwriting\u002F2026-08-03-yen-depreciation-cognition-drilldown","2026-08-03T22:38:00+08:00",{"title":15102,"description":15432},"yen-depreciation-cognition-drilldown","writing\u002F2026-08-03-yen-depreciation-cognition-drilldown",[15440,15441,15442,15443,15444,15286],"汇率","行为金融","货币","利率","套息交易","gJ3Au9UBjT_EEAz1wCHZCzAywJ-MpYBW_1phLVcvAJI",{"id":15447,"title":15448,"body":15449,"category":824,"date":15875,"description":15876,"extension":827,"hidden":828,"kind":834,"meta":15877,"navigation":30,"path":15878,"publishedAt":15879,"seo":15880,"series":834,"slug":15881,"stem":15882,"tags":15883,"unit":834,"updated":834,"__hash__":15891},"writing\u002Fwriting\u002F2026-07-29-etf-premium-discount-cost-of-carry.md","从ETF到商品期货：申赎机制、折溢价与持有成本模型",{"type":112,"value":15450,"toc":15854},[15451,15455,15458,15461,15475,15478,15481,15484,15537,15540,15543,15557,15567,15570,15584,15587,15591,15594,15601,15604,15627,15630,15633,15653,15656,15667,15670,15673,15677,15680,15683,15692,15695,15721,15727,15730,15735,15738,15742,15793,15796,15816,15819,15833,15836,15842,15845,15852],[130,15452,15454],{"id":15453},"etf与lof申赎机制决定折溢价","ETF与LOF：申赎机制决定折溢价",[135,15456,15457],{"id":15457},"底层机制差异",[115,15459,15460],{},"ETF和LOF虽然都是场内可交易的开放式基金，但申赎机制截然不同：",[140,15462,15463,15469],{},[143,15464,15465,15468],{},[125,15466,15467],{},"ETF：实物申赎，T+0瞬时套利。"," 机构投资者用一篮子股票换取基金份额，最小申赎单位通常为50万或100万份，门槛在百万级别。每日盘前基金公司公布PCF（Portfolio Composition File）清单，列明当日申赎对应的一篮子股票明细。",[143,15470,15471,15474],{},[125,15472,15473],{},"LOF：现金申赎，跨系统转托管需T+2日。"," 门槛低（1份起），普通投资者即可参与，但套利存在时滞和净值波动风险。",[115,15476,15477],{},"这个差异是两者折溢价收敛速度天差地别的根本原因。",[135,15479,15480],{"id":15480},"现金替代的四种标志",[115,15482,15483],{},"ETF实物申赎中，并非每只成分股都能完美交收。PCF清单对每只股票标注现金替代标志：",[264,15485,15486,15495],{},[267,15487,15488],{},[270,15489,15490,15493],{},[273,15491,15492],{},"标志",[273,15494,4971],{},[283,15496,15497,15507,15517,15527],{},[270,15498,15499,15504],{},[288,15500,15501],{},[125,15502,15503],{},"禁止",[288,15505,15506],{},"必须用股票实物交收",[270,15508,15509,15514],{},[288,15510,15511],{},[125,15512,15513],{},"允许",[288,15515,15516],{},"申购可现金替代，赎回必须给股票，多退少补",[270,15518,15519,15524],{},[288,15520,15521],{},[125,15522,15523],{},"必须",[288,15525,15526],{},"申赎均必须用现金，针对停牌或待剔除股票",[270,15528,15529,15534],{},[288,15530,15531],{},[125,15532,15533],{},"退补",[288,15535,15536],{},"必须现金替代，按基金公司实际买卖成本多退少补",[135,15538,15539],{"id":15539},"折溢价的形成",[115,15541,15542],{},"场内基金有两个价格：",[140,15544,15545,15551],{},[143,15546,15547,15550],{},[125,15548,15549],{},"IOPV（参考净值）","：实时计算的基金份额参考价值",[143,15552,15553,15556],{},[125,15554,15555],{},"市价","：二级市场交易价格",[115,15558,15559,15560,15563,15564,863],{},"当市价 > IOPV，即为",[125,15561,15562],{},"溢价","；反之则为",[125,15565,15566],{},"折价",[115,15568,15569],{},"套利机制是折溢价收敛的驱动力：",[140,15571,15572,15578],{},[143,15573,15574,15577],{},[125,15575,15576],{},"溢价套利","：T日买入一篮子股票 → 申购ETF份额 → 卖出ETF，全流程T+0完成",[143,15579,15580,15583],{},[125,15581,15582],{},"折价套利","：T日买入ETF → 赎回为一篮子股票 → 卖出股票，同样T+0",[115,15585,15586],{},"正因为ETF套利链条畅通，折溢价通常控制在1%以内。而LOF因T+2转托管时滞，套利者需承担两天净值波动风险，折溢价常达5%以上。",[130,15588,15590],{"id":15589},"跨境etf全现金替代与qdii额度约束","跨境ETF：全现金替代与QDII额度约束",[135,15592,15593],{"id":15593},"一个常见误区",[115,15595,15596,15597,15600],{},"\"我有了一篮子股票，如何申购跨境ETF？\"——这个问题本身存在误区。跨境ETF的成分股在境外，境内投资者无法持有和交收这些股票，因此",[125,15598,15599],{},"跨境ETF不能实物申购，只能全现金替代","。投资者交付现金，由基金公司代为买入境外成分股。",[135,15602,15603],{"id":15603},"现金申购流程",[4640,15605,15606,15609,15612,15615,15618,15621,15624],{},[143,15607,15608],{},"确认券商具备跨境ETF申赎资格",[143,15610,15611],{},"T日盘前查阅PCF清单（现金替代标志全为\"退补\"）",[143,15613,15614],{},"核对当日QDII额度与开放状态（可能限购，极端情况单日仅100元）",[143,15616,15617],{},"计算所需现金（现金替代款 + 预估现金差额），最小单位50万份",[143,15619,15620],{},"T日交易时段通过券商端提交委托",[143,15622,15623],{},"T+1日日终份额确认，T+2日可用",[143,15625,15626],{},"基金公司代买完成后，进行现金差额多退少补",[135,15628,15629],{"id":15629},"三层结构与溢价固化",[115,15631,15632],{},"跨境ETF的运作涉及三层主体：",[140,15634,15635,15641,15647],{},[143,15636,15637,15640],{},[125,15638,15639],{},"外汇局","：审批QDII额度（总量约1708亿美元）",[143,15642,15643,15646],{},[125,15644,15645],{},"基金公司","：在额度内换汇，代投资者买入境外成分股",[143,15648,15649,15652],{},[125,15650,15651],{},"境内投资者","：只能通过现金提交申购申请",[115,15654,15655],{},"当额度充裕时，套利机制正常运行。但当额度耗尽时：",[4640,15657,15658,15661,15664],{},[143,15659,15660],{},"基金公司暂停或限额申购（单日低至10元）",[143,15662,15663],{},"一级市场申购通道堵死，套利者无法创造新份额",[143,15665,15666],{},"二级市场资金只能抢存量份额，溢价飙升至10%-50%+",[115,15668,15669],{},"此外，现金申赎 + 代买模式下，跨时区汇率波动和境外开盘价差导致退补款不可预知，套利收益无法锁定，进一步抑制套利者进场意愿，固化高溢价。",[115,15671,15672],{},"典型场景：QDII ETF因外汇额度受限，一级市场申购通道关闭，套利链条断裂，溢价长期高达10%-40%。",[130,15674,15676],{"id":15675},"商品期货持有成本模型与期限结构","商品期货：持有成本模型与期限结构",[135,15678,15679],{"id":15679},"持有成本定价公式",[115,15681,15682],{},"商品期货的理论价格由持有成本模型决定：",[619,15684,15686],{"className":10361,"code":15685,"language":10363,"meta":625,"style":625},"F = S · e^((r + u − y) · T)\n",[60,15687,15688],{"__ignoreMap":625},[723,15689,15690],{"class":725,"line":726},[723,15691,15685],{},[115,15693,15694],{},"其中：",[140,15696,15697,15703,15709,15715],{},[143,15698,15699,15702],{},[125,15700,15701],{},"S","：现货价格",[143,15704,15705,15708],{},[125,15706,15707],{},"r","：无风险利率（资金占用成本）",[143,15710,15711,15714],{},[125,15712,15713],{},"u","：仓储保险成本率",[143,15716,15717,15720],{},[125,15718,15719],{},"y","：便利收益率（convenience yield）",[115,15722,15723,15726],{},[125,15724,15725],{},"便利收益","是持有实物库存的隐性收益——当供应紧张时，手上有货意味着生产不断线、客户不流失，这种\"随时可用\"的价值就是便利收益。它与库存水平成反比：库存越低，便利收益越高。",[135,15728,15729],{"id":15729},"基差及其收敛",[115,15731,15732],{},[125,15733,15734],{},"基差 = 现货价格(S) − 期货价格(F)",[115,15736,15737],{},"随着交割日临近，持仓成本（r+u−y）T逐渐衰减趋零，期货价格向现货价格收敛，基差必然趋近于零。否则将触发无风险套利：买低卖高，交割平仓。",[135,15739,15741],{"id":15740},"期限结构contango与backwardation","期限结构：Contango与Backwardation",[264,15743,15744,15759],{},[267,15745,15746],{},[270,15747,15748,15751,15754,15757],{},[273,15749,15750],{},"结构",[273,15752,15753],{},"形态",[273,15755,15756],{},"成因",[273,15758,4971],{},[283,15760,15761,15777],{},[270,15762,15763,15768,15771,15774],{},[288,15764,15765],{},[125,15766,15767],{},"Contango",[288,15769,15770],{},"S \u003C F近 \u003C F远",[288,15772,15773],{},"r+u > y，库存充足",[288,15775,15776],{},"常态结构，持有成本主导",[270,15778,15779,15784,15787,15790],{},[288,15780,15781],{},[125,15782,15783],{},"Backwardation",[288,15785,15786],{},"S > F近 > F远",[288,15788,15789],{},"r+u \u003C y，库存紧张",[288,15791,15792],{},"供应短缺，现货溢价",[135,15794,15795],{"id":15795},"品种实例",[140,15797,15798,15804,15810],{},[143,15799,15800,15803],{},[125,15801,15802],{},"黄金","：投资性商品，便利收益y≈0，恒为Contango结构",[143,15805,15806,15809],{},[125,15807,15808],{},"原油","：消费性商品，便利收益波动剧烈，常在Contango与Backwardation间切换。紧缺时深度Backwardation，过剩时转Contango",[143,15811,15812,15815],{},[125,15813,15814],{},"农产品","：受季节性收获周期影响，仓储成本u与便利收益y交替主导曲线形态",[135,15817,15818],{"id":15818},"套利操作",[140,15820,15821,15827],{},[143,15822,15823,15826],{},[125,15824,15825],{},"正向套利（Cash and Carry）","：当F实际 > F理论时，借钱买入现货 + 卖出期货，锁定差价",[143,15828,15829,15832],{},[125,15830,15831],{},"反向套利（Reverse Cash and Carry）","：当F实际 \u003C F理论时，卖空现货 + 买入期货。但对消费性商品，卖空现货难以操作",[135,15834,15835],{"id":15835},"远期与期货的凸性调整",[115,15837,15838,15839,863],{},"期货合约每日盯市（mark-to-market），而远期合约到期才结算。当标的资产价格与利率正相关时，期货价格略高于远期价格，这一差异称为",[125,15840,15841],{},"凸性调整",[130,15843,15844],{"id":15844},"串联",[115,15846,15847,15848,15851],{},"三个主题看似分散，实则共享一条逻辑主线：",[125,15849,15850],{},"机制决定效率，效率决定价格形态","。ETF的T+0实物申赎使折溢价瞬间收敛；跨境ETF的额度瓶颈使套利链条断裂、溢价固化；商品期货的持有成本模型则将仓储、利率、库存预期统一为一个数学框架，解释了曲线形态的成因。理解机制本身，比记住某个具体结论更重要。",[790,15853,792],{},{"title":625,"searchDepth":732,"depth":732,"links":15855},[15856,15861,15866,15874],{"id":15453,"depth":732,"text":15454,"children":15857},[15858,15859,15860],{"id":15457,"depth":34,"text":15457},{"id":15480,"depth":34,"text":15480},{"id":15539,"depth":34,"text":15539},{"id":15589,"depth":732,"text":15590,"children":15862},[15863,15864,15865],{"id":15593,"depth":34,"text":15593},{"id":15603,"depth":34,"text":15603},{"id":15629,"depth":34,"text":15629},{"id":15675,"depth":732,"text":15676,"children":15867},[15868,15869,15870,15871,15872,15873],{"id":15679,"depth":34,"text":15679},{"id":15729,"depth":34,"text":15729},{"id":15740,"depth":34,"text":15741},{"id":15795,"depth":34,"text":15795},{"id":15818,"depth":34,"text":15818},{"id":15835,"depth":34,"text":15835},{"id":15844,"depth":732,"text":15844},"2026-07-29","ETF实物申赎与LOF现金申赎的机制差异决定折溢价收敛速度，跨境ETF的QDII额度约束固化高溢价，商品期货持有成本模型统一解释期限结构形态。",{},"\u002Fwriting\u002F2026-07-29-etf-premium-discount-cost-of-carry","2026-07-29T10:51:17+08:00",{"title":15448,"description":15876},"etf-premium-discount-cost-of-carry","writing\u002F2026-07-29-etf-premium-discount-cost-of-carry",[14617,15884,15885,15886,15887,15888,15889,15890],"LOF","跨境ETF","QDII","商品期货","持有成本模型","期限结构","折溢价","j8ZREl2DLHpvRrQexWCwAs9Df03Dloc_cBM9d85BgaM",{"id":15893,"title":15894,"body":15895,"category":824,"date":19299,"description":19300,"extension":827,"hidden":828,"kind":834,"meta":19301,"navigation":30,"path":19302,"publishedAt":19303,"seo":19304,"series":834,"slug":19305,"stem":19306,"tags":19307,"unit":834,"updated":19312,"__hash__":19313},"writing\u002Fwriting\u002F2026-07-26-quilibra-complete-technical-handbook.md","Quilibra 个人主页完整技术手册：从页面架构到内容发布与生产部署",{"type":112,"value":15896,"toc":19227},[15897,15900,15903,15922,15926,15929,15932,15938,15948,15962,15965,15969,15972,15992,15995,15998,16002,16005,16099,16102,16108,16114,16130,16134,16137,16143,16146,16178,16192,16196,16202,16238,16256,16267,16273,16318,16325,16329,16332,16395,16414,16421,16456,16469,16473,16479,16514,16532,16566,16569,16584,16587,16591,16594,16617,16636,16655,16658,16662,16665,16693,16696,16700,16707,16760,16771,16774,16800,16803,16809,16815,16819,16830,16836,16843,16853,16876,16880,16890,16893,16912,16915,16955,16961,16965,16976,16979,16982,16996,17017,17023,17047,17058,17062,17073,17076,17103,17114,17117,17132,17136,17139,17155,17158,17164,17178,17185,17203,17207,17216,17223,17226,17240,17254,17261,17279,17283,17294,17300,17308,17314,17329,17348,17355,17359,17362,17443,17446,17452,17479,17502,17515,17525,17529,17538,17541,17563,17566,17574,17580,17586,17589,17596,17610,17614,17621,17624,17647,17666,17669,17673,17680,17686,17695,17708,17712,17715,17721,17724,17730,17733,17739,17742,17748,17754,17760,17766,17772,17804,17814,17820,17844,17847,17866,17870,17876,17882,17895,17912,17921,17931,17934,17940,17944,17980,18045,18051,18054,18060,18071,18082,18086,18150,18159,18162,18166,18169,18175,18182,18185,18227,18243,18254,18265,18269,18272,18276,18282,18313,18317,18323,18357,18361,18367,18420,18423,18426,18430,18433,18450,18457,18460,18463,18504,18509,18513,18516,18523,18540,18543,18547,18551,18560,18563,18572,18575,18583,18586,18603,18606,18619,18622,18629,18632,18646,18649,18658,18661,18667,18671,18675,18692,18695,18698,18722,18727,18731,18775,18786,18789,18861,18864,18867,18895,18906,18910,18914,18937,18941,18952,18955,18963,18966,18972,18976,18986,18990,18999,19003,19009,19013,19016,19022,19025,19031,19041,19045,19048,19051,19054,19057,19060,19066,19069,19073,19076,19138,19141,19161,19164,19166,19224],[115,15898,15899],{},"这是一份给自己看的项目说明书。它主要以 2026 年 9 月 4 日的实际代码和 VPS 配置为准，从浏览器里看到的页面一直追到内容文件、Git 分支、构建产物、静态服务器和版本化 release。以后忘了某段代码为什么存在、主页颜色为什么能保存、一次 CMS 保存如何自动变成线上新版本，或者 3000、3100、3110 几个端口分别在做什么，可以从这里重新建立完整认识。",[115,15901,15902],{},"本文记录的是一个具体版本的实现，不是一套永远不变的规范。最可靠的事实来源始终是仓库中的代码；本文的作用，是把分散在 Vue、CSS、YAML、脚本和运维文件里的设计意图串起来。",[522,15904,15905],{},[115,15906,15907,15908,15911,15912,15915,15916,15918,15919,863],{},"2026 年 9 月 4 日更新：8 月 3 日起网站移除长期 staging 分支、远程测试站和 ",[60,15909,15910],{},"promote"," 流程，全站改为 ",[60,15913,15914],{},"master"," 单分支：CMS 直接提交 ",[60,15917,15914],{},"，Webhook 自动构建部署，纯内容提交走快速构建。此后新增了文章列表状态与阅读进度恢复、移动端返回与回顶按钮、平滑标题锚点和文章表格样式；8 月 26 日曾试验文章 TOC 与自托管统计，当天整体回退；9 月 2 日仓库迁移至 ",[60,15920,15921],{},"XXXCH-siu\u002Fpersonal-site",[130,15923,15925],{"id":15924},"一先用一句话理解整个网站","一、先用一句话理解整个网站",[115,15927,15928],{},"Quilibra（名字也是AI起的捏） 是一个由 Nuxt 生成的静态个人网站：页面结构写在 Vue 中，主页设置写在 YAML 中，文章写在 Markdown 中；发布时把这些源文件编译成普通 HTML、CSS、JavaScript 和图片，再由一个很小的 Node 静态服务器提供给浏览器。",[115,15930,15931],{},"它的静态生成核心是：",[619,15933,15936],{"className":15934,"code":15935,"language":624,"meta":625},[622],"Vue \u002F CSS \u002F YAML \u002F Markdown\n           |\n           | Nuxt generate\n           v\n      .output\u002Fpublic\n           |\n           | 复制到带时间戳的 release\n           v\n  releases\u002F\u003Crelease-id>\n           |\n           | 原子切换 current 符号链接\n           v\n     current -> 当前 release\n           |\n           | Node 静态服务器 :3000\n           v\n        浏览器 \u002F 反向代理\n",[60,15937,15935],{"__ignoreMap":625},[115,15939,15940,15941,15943,15944,15947],{},"现在这套链路只有一个生产实例：",[60,15942,15914],{}," 构建到 ",[60,15945,15946],{},"~\u002F.local\u002Fshare\u002Fpersonal-site\u002Fcurrent","，由 3000 提供正式站。本地开发和 Playwright 测试会临时占用 3100、3101，但它们不常驻，也没有独立的 release。2026 年 8 月 3 日之前，这里曾有第二个 staging 实例和独立的测试域名，双环境简化后彻底删除。",[115,15949,15950,15951],{},"这里最重要的边界是：",[125,15952,15953,15954,15957,15958,15961],{},"Git 分支中的源码是长期事实，",[60,15955,15956],{},".output\u002Fpublic"," 是可重新生成的临时产物，",[60,15959,15960],{},"current"," 指向的 release 才是线上真正提供的版本。",[115,15963,15964],{},"因此，修改 Markdown 或 YAML 只改变源码；只有重新生成并切换 release，线上页面才会改变。网站没有生产数据库，也没有在访问文章时临时渲染 Markdown。",[130,15966,15968],{"id":15967},"二为什么选择静态优先","二、为什么选择静态优先",[115,15970,15971],{},"个人主页的读取远多于写入，而且文章、简介和服务入口都不需要按访客实时计算。静态生成正好适合这种负载：",[140,15973,15974,15977,15980,15983,15986,15989],{},[143,15975,15976],{},"访问时不查询数据库，也不运行 Vue 服务端渲染逻辑；",[143,15978,15979],{},"HTML 已经在发布阶段生成，首屏可以直接返回完整内容；",[143,15981,15982],{},"生产服务只需要读取文件，故障面比常驻 Nuxt 服务小；",[143,15984,15985],{},"每个 release 都是一份独立、完整、可回滚的目录；",[143,15987,15988],{},"Markdown 和 YAML 都在 Git 中，内容、配置和代码可以一起审查与恢复；",[143,15990,15991],{},"CMS 只是源码编辑器，不会成为网站运行时依赖。",[115,15993,15994],{},"代价也很明确：每次内容变化都要重新生成站点；文章越多，生成时间越长；需要登录、评论、实时状态或用户数据时，必须接入独立服务，而不能假装静态文件可以承担动态业务。",[115,15996,15997],{},"当前项目主动接受这个取舍，因为稳定、透明和容易备份比“保存后数据库立刻生效”更重要。",[130,15999,16001],{"id":16000},"三技术栈与各自职责","三、技术栈与各自职责",[115,16003,16004],{},"当前主要依赖如下：",[264,16006,16007,16017],{},[267,16008,16009],{},[270,16010,16011,16014],{},[273,16012,16013],{},"技术",[273,16015,16016],{},"当前职责",[283,16018,16019,16027,16035,16043,16051,16059,16067,16075,16083,16091],{},[270,16020,16021,16024],{},[288,16022,16023],{},"Nuxt 4",[288,16025,16026],{},"应用框架、路由、预渲染、页面头信息和构建产物",[270,16028,16029,16032],{},[288,16030,16031],{},"Vue 3",[288,16033,16034],{},"组件、响应式状态、生命周期和浏览器交互",[270,16036,16037,16040],{},[288,16038,16039],{},"TypeScript",[288,16041,16042],{},"页面、组件、配置、内容脚本和测试的静态类型检查",[270,16044,16045,16048],{},[288,16046,16047],{},"Nuxt Content 3",[288,16049,16050],{},"读取 YAML\u002FMarkdown、建立集合、查询文章并渲染正文",[270,16052,16053,16056],{},[288,16054,16055],{},"Zod",[288,16057,16058],{},"约束站点设置与文章字段",[270,16060,16061,16064],{},[288,16062,16063],{},"Lucide Vue",[288,16065,16066],{},"返回、搜索、服务、调色等界面图标",[270,16068,16069,16072],{},[288,16070,16071],{},"原生 CSS",[288,16073,16074],{},"全部视觉、响应式布局、悬浮反馈和字标动画",[270,16076,16077,16080],{},[288,16078,16079],{},"Sveltia CMS",[288,16081,16082],{},"在浏览器中编辑仓库里的 YAML、Markdown 和上传资源",[270,16084,16085,16088],{},[288,16086,16087],{},"Playwright",[288,16089,16090],{},"真实浏览器中的交互、布局、响应式和回归测试",[270,16092,16093,16096],{},[288,16094,16095],{},"Node.js",[288,16097,16098],{},"内容工具、同步与发布脚本、生产和测试静态服务器",[115,16100,16101],{},"没有引入 Tailwind、组件库、动画框架、运行时数据库或外部字体。页面使用系统无衬线字体和系统等宽字体回退，这减少了字体请求，也避免了字体加载完成时重新排版。",[115,16103,16104,16107],{},[60,16105,16106],{},"package.json"," 中最常用的入口是：",[619,16109,16112],{"className":16110,"code":16111,"language":624,"meta":625},[622],"npm run dev              本地临时 Nuxt 开发服务\nnpm run generate         生成静态站点\nnpm run typecheck        Nuxt\u002FVue TypeScript 检查\nnpm run content:validate 校验全部文章与站点设置\nnpm run content:write    受限 writer：新建、修订和删除文章\nnpm run test:content     内容校验器与受限 writer 的单元测试\nnpm run test:release     release、同步与 Webhook 的单元测试\nnpm run test:e2e         Playwright 浏览器测试\nnpm run verify           提交代码前的完整本地检查\nnpm run publish          发布未提交的受管内容\nnpm run deploy           部署已经提交且工作区干净的代码版本\nnpm run deploy:content   纯内容快速部署，由自动同步器调用\nnpm run sync             检查 GitHub master 并自动部署新提交\nnpm run rollback         切换回指定历史 release\n",[60,16113,16111],{"__ignoreMap":625},[115,16115,16116,1471,16119,16122,16123,16126,16127,16129],{},[60,16117,16118],{},"build",[60,16120,16121],{},"preview"," 仍是 Nuxt 的常规命令，但当前生产链路以 ",[60,16124,16125],{},"generate"," 和自有静态服务器为准。2026 年 8 月 3 日移除双环境时，",[60,16128,15910],{}," 命令随 staging 一起删除。",[130,16131,16133],{"id":16132},"四目录结构与所有权边界","四、目录结构与所有权边界",[115,16135,16136],{},"理解这个项目，先要知道“改什么就去哪里”。",[619,16138,16141],{"className":16139,"code":16140,"language":624,"meta":625},[622],"personal-site\u002F\n├── app\u002F\n│   ├── app.vue                         全站外壳、主题初始化、动态 favicon、返回与回顶控制\n│   ├── assets\u002Fcss\u002Fmain.css             全站视觉与响应式规则\n│   ├── components\u002F\n│   │   ├── ArtisticWordmark.vue        可编辑艺术字与重播控制\n│   │   ├── RgbAssembly.vue             RGB 滑杆、预设与重置\n│   │   ├── ServiceIcon.vue             服务图标名称到 Lucide 的映射\n│   │   └── ServiceStatusDot.vue        服务状态点\n│   ├── composables\u002F\n│   │   ├── useAccentTheme.ts           主题色状态、混色、对比色与持久化\n│   │   ├── useArticleReadingState.ts   文章阅读进度的会话级记忆\n│   │   ├── useSiteSettings.ts          并行读取并组合五份站点设置\n│   │   ├── useWordmarkAnimation.ts     跨组件字标重播信号\n│   │   └── useWritingListState.ts      文章列表筛选与滚动位置的会话级记忆\n│   ├── config\u002F                         文章分类、服务状态和图标枚举\n│   ├── pages\u002F                          首页、文章、服务、关于页面\n│   └── error.vue                       错误页面\n├── content\u002F\n│   ├── settings\u002F                       全站与四个页面的独立 YAML 配置\n│   └── writing\u002F*.md                    正式文章\n├── public\u002F\n│   ├── admin\u002F                          Sveltia CMS 入口与字段配置\n│   ├── brand\u002F                          字标位图资源\n│   ├── uploads\u002F                        文章上传资源\n│   └── favicon.svg                     静态 favicon 回退\n├── scripts\u002F\n│   ├── content\u002Fvalidate.ts             内容结构校验\n│   ├── content\u002Fwrite.ts                受限文章写入、更新与删除\n│   ├── release.ts                      测试、生成、提交、发布和回滚\n│   ├── sync.ts                         GitHub 拉取、代理、构建与部署\n│   ├── webhook.ts                      GitHub 签名校验、事件队列与部署触发\n│   └── static-server.mjs               生产静态文件服务器\n├── tests\u002F                              单元、发布和浏览器测试\n├── docs\u002F                               发布合约、内容管理、运维与 OpenClaw 说明\n├── ops\u002F\n│   ├── openclaw\u002F...\u002FSKILL.md           OpenClaw 文章发布 Skill\n│   ├── nginx\u002F                           正式域名、Webhook 位置和默认拒绝站点\n│   └── systemd\u002F                         静态、Webhook 与同步服务\n├── content.config.ts                   Nuxt Content 集合\n├── content.schema.ts                   站点设置 Schema\n├── nuxt.config.ts                      Nuxt 与静态预渲染配置\n└── playwright.config.ts                E2E 临时服务与浏览器配置\n",[60,16142,16140],{"__ignoreMap":625},[115,16144,16145],{},"可以把这些文件分成四层：",[4640,16147,16148,16154,16160,16169],{},[143,16149,16150,16153],{},[60,16151,16152],{},"content\u002F"," 是可频繁修改的数据层。",[143,16155,16156,16159],{},[60,16157,16158],{},"app\u002F"," 是页面表现和交互层。",[143,16161,16162,1471,16165,16168],{},[60,16163,16164],{},"scripts\u002F",[60,16166,16167],{},"ops\u002F"," 是发布和运行层。",[143,16170,16171,195,16174,16177],{},[60,16172,16173],{},".output\u002F",[60,16175,16176],{},".nuxt*"," 是构建缓存或产物，不是手工维护的源码。",[115,16179,16180,16181,16184,16185,4814,16188,16191],{},"CMS 默认只管理 ",[60,16182,16183],{},"content\u002Fsettings\u002F"," 中五份固定 YAML、",[60,16186,16187],{},"content\u002Fwriting\u002F",[60,16189,16190],{},"public\u002Fuploads\u002F","。Vue、CSS、CMS 配置、脚本或测试发生变化时，应当视为代码变更，走完整代码部署流程；设置目录中的未知文件也不会自动进入内容发布白名单。",[130,16193,16195],{"id":16194},"五nuxt-配置与构建数据库","五、Nuxt 配置与构建数据库",[115,16197,16198,16201],{},[60,16199,16200],{},"nuxt.config.ts"," 做了几件关键的事：",[140,16203,16204,16210,16216,16219,16225,16232,16235],{},[143,16205,16206,16207,4588],{},"加载 ",[60,16208,16209],{},"@nuxt\u002Fcontent",[143,16211,16212,16213,4588],{},"引入 Cormorant Garamond 字标字体和 ",[60,16214,16215],{},"app\u002Fassets\u002Fcss\u002Fmain.css",[143,16217,16218],{},"关闭开发工具；",[143,16220,16221,16222,4588],{},"设置中文页面语言、移动端 viewport 和浅色 ",[60,16223,16224],{},"color-scheme",[143,16226,16227,16228,16231],{},"使用 ",[60,16229,16230],{},"out-in"," 页面切换过渡；",[143,16233,16234],{},"允许 Nitro 爬取内部链接并预渲染，遇到错误时直接让构建失败；",[143,16236,16237],{},"把 Nuxt Content 的本地数据库放到与构建目录对应的位置。",[115,16239,16240,16241,4814,16244,16247,16248,16251,16252,16255],{},"Nuxt Content 3 在构建过程中会建立 SQLite 内容索引。开发环境默认使用 ",[60,16242,16243],{},".nuxt",[60,16245,16246],{},".data\u002Fcontent\u002Fcontents.sqlite","；release 构建传入 ",[60,16249,16250],{},"NUXT_BUILD_DIR=.nuxt-release","，E2E 使用 ",[60,16253,16254],{},".nuxt-e2e","。把三种构建目录分开，是为了避免开发服务器、正式生成和 Playwright 同时读写同一个内容缓存。",[115,16257,16258,16259,16262,16263,16266],{},"这里的 SQLite 是",[125,16260,16261],{},"构建时索引","，不是生产文章数据库。它帮助 Nuxt Content 查询 Markdown；静态页面生成完成后，生产服务器只负责输出构建结果。遇到 ",[60,16264,16265],{},"no such table: _content_writing"," 时，通常是本地 Nuxt Content 缓存被并发构建或中断破坏，不代表文章源文件丢失。",[115,16268,16269,16272],{},[60,16270,16271],{},"content.config.ts"," 定义了六个集合：",[140,16274,16275,16297],{},[143,16276,16277,195,16280,195,16283,195,16286,195,16289,16292,16293,16296],{},[60,16278,16279],{},"site",[60,16281,16282],{},"homeSettings",[60,16284,16285],{},"writingSettings",[60,16287,16288],{},"servicesSettings",[60,16290,16291],{},"aboutSettings","：五个 ",[60,16294,16295],{},"data"," 集合，各自读取同名职责的 YAML；",[143,16298,16299,181,16302,16305,16306,16309,16310,16313,16314,16317],{},[60,16300,16301],{},"writing",[60,16303,16304],{},"page"," 类型，读取 ",[60,16307,16308],{},"writing\u002F**\u002F*.md","，因此每篇文章会拥有可路由的 ",[60,16311,16312],{},"path"," 和可供 ",[60,16315,16316],{},"ContentRenderer"," 使用的正文。",[115,16319,16320,16321,16324],{},"E2E 测试通过 ",[60,16322,16323],{},"NUXT_CONTENT_CWD=tests\u002Ffixtures\u002Fcontent"," 把文章集合切换到固定 fixture，测试不会依赖真实文章的标题和数量，也不会改动生产内容。",[130,16326,16328],{"id":16327},"六站点设置五份-yaml-如何保持页面边界","六、站点设置：五份 YAML 如何保持页面边界",[115,16330,16331],{},"站点设置按后台入口拆成五份文件：",[264,16333,16334,16343],{},[267,16335,16336],{},[270,16337,16338,16340],{},[273,16339,5244],{},[273,16341,16342],{},"管理内容",[283,16344,16345,16355,16365,16375,16385],{},[270,16346,16347,16352],{},[288,16348,16349],{},[60,16350,16351],{},"site.yml",[288,16353,16354],{},"站点名称、浏览器标题、SEO、位置、默认主题色、备案和署名",[270,16356,16357,16362],{},[288,16358,16359],{},[60,16360,16361],{},"home.yml",[288,16363,16364],{},"首页介绍、标题、最近文章、服务矩阵、RGB 模块和底部入口",[270,16366,16367,16372],{},[288,16368,16369],{},[60,16370,16371],{},"writing.yml",[288,16373,16374],{},"文章列表页介绍、SEO 和空状态",[270,16376,16377,16382],{},[288,16378,16379],{},[60,16380,16381],{},"services.yml",[288,16383,16384],{},"服务列表、状态、图标、主页显示和访问说明",[270,16386,16387,16392],{},[288,16388,16389],{},[60,16390,16391],{},"about.yml",[288,16393,16394],{},"关于页介绍、当前状态、关注主题和联系文案",[115,16396,16397,16398,16401,16402,16405,16406,16409,16410,16413],{},"拆分不只是 CMS 菜单分组。如果多个表单只声明同一 YAML 的部分字段，保存其中一个表单可能覆盖另一个表单未声明的字段；因此每个入口必须拥有独立源文件。",[60,16399,16400],{},"useSiteSettings()"," 使用固定异步数据键 ",[60,16403,16404],{},"site-settings","，通过 ",[60,16407,16408],{},"Promise.all"," 并行查询五个 data collection，再返回结构化的 ",[60,16411,16412],{},"site\u002Fhome\u002Fwriting\u002Fservices\u002Fabout"," 对象。任何一份缺失都会立即抛出 500，而不是让页面带着部分设置运行。",[115,16415,16416,16417,16420],{},"这些配置不是任意 YAML。",[60,16418,16419],{},"content.schema.ts"," 为每个文件提供独立严格 Schema，例如：",[140,16422,16423,16429,16432,16435,16444,16447,16453],{},[143,16424,16425,16426,4588],{},"默认强调色必须是 ",[60,16427,16428],{},"#RRGGBB",[143,16430,16431],{},"最新文章数量只能是 1 到 3；",[143,16433,16434],{},"服务数量只能是 1 到 12；",[143,16436,16437,16438,195,16440,195,16442,4588],{},"服务状态只能是 ",[60,16439,59],{},[60,16441,71],{},[60,16443,65],{},[143,16445,16446],{},"图标只能使用代码中已有的枚举；",[143,16448,16449,16450,16452],{},"服务链接只能是 ",[60,16451,10236],{}," 或完整的 HTTPS URL；",[143,16454,16455],{},"对象使用严格模式，拼错字段不会被静默忽略。",[115,16457,16458,16461,16462,16464,16465,16468],{},[60,16459,16460],{},"public\u002Fadmin\u002Fconfig.yml"," 的表单选项、",[60,16463,16419],{}," 的运行时约束、",[60,16466,16467],{},"app\u002Fconfig\u002Fsite.ts"," 的 TypeScript 枚举应该同步修改。只改 CMS 下拉框，不改 Schema，保存后会校验失败；只改 Schema，不改组件映射，界面可能没有正确图标。",[130,16470,16472],{"id":16471},"七全站外壳与水合边界","七、全站外壳与水合边界",[115,16474,16475,16478],{},[60,16476,16477],{},"app\u002Fapp.vue"," 是每个路由外面的共同外壳。它负责：",[140,16480,16481,16484,16487,16490,16496,16499,16502,16508,16511],{},[143,16482,16483],{},"在页面加载时取得站点设置；",[143,16485,16486],{},"判断当前是否为主页或文章详情页；",[143,16488,16489],{},"初始化和恢复主题色；",[143,16491,16492,16493,4588],{},"把主题 CSS 变量挂到 ",[60,16494,16495],{},".site-shell",[143,16497,16498],{},"为非主页显示上下文相关的“返回”按钮与主题预设切换按钮；",[143,16500,16501],{},"为非主页在滚动超过阈值时显示“回到顶部”按钮；",[143,16503,16504,16505,4588],{},"设置标题模板、站点描述、Open Graph 信息和 ",[60,16506,16507],{},"theme-color",[143,16509,16510],{},"根据当前强调色实时生成 data URL favicon；",[143,16512,16513],{},"显示 Nuxt 页面加载进度条。",[115,16515,16516,16519,16520,16523,16524,16527,16528,16531],{},[60,16517,16518],{},"hydrated"," 初始为 ",[60,16521,16522],{},"false","，在 ",[60,16525,16526],{},"onMounted"," 完成主题恢复和本地监听注册后变成 ",[60,16529,16530],{},"true","，最终反映为：",[619,16533,16537],{"className":16534,"code":16535,"language":16536,"meta":625,"style":625},"language-html shiki shiki-themes github-light github-dark","\u003Cdiv class=\"site-shell\" data-hydrated=\"true\">\n","html",[60,16538,16539],{"__ignoreMap":625},[723,16540,16541,16543,16547,16550,16552,16555,16558,16560,16563],{"class":725,"line":726},[723,16542,10305],{"class":5381},[723,16544,16546],{"class":16545},"s9eBZ","div",[723,16548,16549],{"class":5691}," class",[723,16551,13230],{"class":5381},[723,16553,16554],{"class":5394},"\"site-shell\"",[723,16556,16557],{"class":5691}," data-hydrated",[723,16559,13230],{"class":5381},[723,16561,16562],{"class":5394},"\"true\"",[723,16564,16565],{"class":5381},">\n",[115,16567,16568],{},"这个属性既是测试可观察的“客户端已经接管页面”标志，也是字标动画的播放闸门。CSS 动画先建立，但在页面完成水合前保持暂停，防止服务端 HTML 刚显示就消耗掉一段动画，等 JavaScript 接管时只剩最终帧。",[115,16570,16571,16572,16575,16576,16579,16580,16583],{},"非主页的全局返回入口是上下文相关的：在文章详情页显示“文章”并返回 ",[60,16573,16574],{},"\u002Fwriting","，在其他页面显示“返回”并回到 ",[60,16577,16578],{},"\u002F","，图标和 ",[60,16581,16582],{},"aria-label"," 补充方向与完整语义。它与文章内部的标题锚点属于不同层级：前者是全局导航，后者是页面内跳转。",[115,16585,16586],{},"滚动超过 360 像素（视口宽度不超过 720 像素时是 200 像素）后，非主页会出现一个“回到顶部”按钮，点击后平滑滚回顶部，并在系统开启减少动态效果时退回瞬间跳转；路由切换时它立即隐藏，避免新页面继承旧滚动状态。",[130,16588,16590],{"id":16589},"八主页是一张单屏工作台","八、主页是一张单屏工作台",[115,16592,16593],{},"主页不是按多个营销区块向下滚动，而是一张固定在一个视口高度内的工作台。核心区域包括：",[140,16595,16596,16599,16602,16605,16608,16611,16614],{},[143,16597,16598],{},"顶部站点标识、上海时区时钟、指针坐标和主题按钮；",[143,16600,16601],{},"左上介绍、普通标题前缀与 Quilibra 字标；",[143,16603,16604],{},"右上服务矩阵；",[143,16606,16607],{},"左下最近三篇文章；",[143,16609,16610],{},"右下 RGB 调色器；",[143,16612,16613],{},"底部中央文章、服务、关于三个入口；",[143,16615,16616],{},"两侧备案与署名。",[115,16618,16619,16620,16623,16624,16627,16628,16631,16632,16635],{},"首页在 ",[60,16621,16622],{},"setup"," 阶段并行取得站点设置和最近文章。文章查询按 ",[60,16625,16626],{},"date DESC"," 排序，再按 YAML 中的 ",[60,16629,16630],{},"limit"," 截取。服务矩阵先筛选 ",[60,16633,16634],{},"showOnHome: true","，按 YAML 顺序最多取四个；服务页仍然展示完整列表。",[115,16637,16638,16639,16642,16643,16646,16647,16650,16651,16654],{},"顶部时钟通过 ",[60,16640,16641],{},"Intl.DateTimeFormat"," 固定使用 ",[60,16644,16645],{},"Asia\u002FShanghai","，每 30 秒更新一次。指针坐标不是每次 ",[60,16648,16649],{},"pointermove"," 都直接改 DOM：事件只保存最新坐标，然后在下一帧 ",[60,16652,16653],{},"requestAnimationFrame"," 中统一计算。元素边界在挂载与窗口缩放时缓存，触摸指针被忽略。这样可以减少高频事件中的布局读取和响应式更新。",[115,16656,16657],{},"工作台没有为了装饰引入 canvas 或 WebGL。网格、面板、条纹、阴影和入场效果全部由 CSS 完成，浏览器可以直接合成绝大多数变换。",[130,16659,16661],{"id":16660},"九响应式布局为何不只是按比例缩小","九、响应式布局为何不只是“按比例缩小”",[115,16663,16664],{},"主页有大量绝对定位元素，因此响应式的目标不是把桌面页面机械缩成手机版，而是维护清晰的空间约束：",[140,16666,16667,16674,16677,16680,16683,16690],{},[143,16668,16669,16670,16673],{},"桌面端通过 ",[60,16671,16672],{},"clamp()","、视口高度和稳定的面板高度控制上下关系；",[143,16675,16676],{},"最近文章与 RGB 面板在宽桌面上对齐顶部和底部；",[143,16678,16679],{},"小屏笔记本缩小间距和模块尺寸，仍避免重叠；",[143,16681,16682],{},"768 像素附近的紧凑布局隐藏无法舒适操作的 RGB 面板；",[143,16684,16685,16686,16689],{},"手机端重新排列主页元素，并使用 ",[60,16687,16688],{},"overflow: clip"," 防止装饰或过渡产生横向滚动；",[143,16691,16692],{},"固定格式组件使用明确的 grid 列、最小宽度和高度，标题悬浮时不会重新计算可用宽度。",[115,16694,16695],{},"Playwright 会在 768×1024、1024×768、1440×900 和 3440×1440 上读取真实元素几何信息，验证介绍不压住服务矩阵、上方面板不压住底部导航、RGB 与最近文章对齐、页面没有横向溢出。这里测试的是关系，而不是容易因一个像素改动就失效的整页截图。",[130,16697,16699],{"id":16698},"十主题色从一个-rgb-值扩展成整套界面","十、主题色从一个 RGB 值扩展成整套界面",[115,16701,16702,16703,16706],{},"主题逻辑集中在 ",[60,16704,16705],{},"useAccentTheme.ts","。唯一的核心状态是：",[619,16708,16712],{"className":16709,"code":16710,"language":16711,"meta":625,"style":625},"language-ts shiki shiki-themes github-light github-dark","interface RgbColor {\n  r: number\n  g: number\n  b: number\n}\n","ts",[60,16713,16714,16726,16738,16747,16756],{"__ignoreMap":625},[723,16715,16716,16720,16723],{"class":725,"line":726},[723,16717,16719],{"class":16718},"szBVR","interface",[723,16721,16722],{"class":5691}," RgbColor",[723,16724,16725],{"class":5381}," {\n",[723,16727,16728,16732,16735],{"class":725,"line":732},[723,16729,16731],{"class":16730},"s4XuR","  r",[723,16733,16734],{"class":16718},":",[723,16736,16737],{"class":5387}," number\n",[723,16739,16740,16743,16745],{"class":725,"line":34},[723,16741,16742],{"class":16730},"  g",[723,16744,16734],{"class":16718},[723,16746,16737],{"class":5387},[723,16748,16749,16752,16754],{"class":725,"line":1068},[723,16750,16751],{"class":16730},"  b",[723,16753,16734],{"class":16718},[723,16755,16737],{"class":5387},[723,16757,16758],{"class":725,"line":1074},[723,16759,5425],{"class":5381},[115,16761,16762,16763,16766,16767,16770],{},"三个内置预设是红、绿、蓝。站点设置里的 ",[60,16764,16765],{},"theme.defaultAccent"," 可以是任意合法十六进制颜色，并不要求与预设一致。当前颜色存放在 Nuxt ",[60,16768,16769],{},"useState"," 中，因此同一客户端的页面切换不会创建互相冲突的主题实例。",[115,16772,16773],{},"主题计算分为几步：",[4640,16775,16776,16779,16788,16791,16797],{},[143,16777,16778],{},"把每个通道四舍五入并限制在 0 到 255。",[143,16780,16781,16782,16785,16786,863],{},"生成 CSS ",[60,16783,16784],{},"rgb(r g b)"," 与大写 ",[60,16787,16428],{},[143,16789,16790],{},"把强调色分别与白色、画布色和黑色混合，得到 soft、faint、canvas、grid、strong 等衍生色。",[143,16792,16793,16794,863],{},"计算 sRGB 相对亮度，对比黑白文字的对比度，自动选择更清晰的 ",[60,16795,16796],{},"--accent-ink",[143,16798,16799],{},"把结果以 CSS 变量挂到站点外壳，按钮、悬浮底色、网格、字标和 favicon 共同消费这些变量。",[115,16801,16802],{},"主题变量的意义大致如下：",[619,16804,16807],{"className":16805,"code":16806,"language":624,"meta":625},[622],"--accent          原始强调色\n--accent-ink      强调色背景上的黑或白文字\n--accent-soft     大面积悬浮反馈\n--accent-faint    更浅的面板背景\n--accent-canvas   轻微带主题倾向的页面画布\n--accent-grid     网格与线条色\n--accent-strong   向黑色混合后的强调色\n",[60,16808,16806],{"__ignoreMap":625},[115,16810,16811,16812,16814],{},"这样做比在 CSS 中到处硬编码“红色按钮、浅红背景、深红边框”更可靠。换成极亮或极暗的自定义颜色时，文字对比度仍会自动调整。E2E 会直接把颜色调到纯白和纯黑，验证 ",[60,16813,16796],{}," 分别变成深色和白色。",[130,16816,16818],{"id":16817},"十一主题持久化与旧黄色迁移","十一、主题持久化与旧黄色迁移",[115,16820,16821,16822,16825,16826,16829],{},"客户端挂载时，",[60,16823,16824],{},"restoreSavedAccent()"," 从 ",[60,16827,16828],{},"localStorage"," 读取：",[619,16831,16834],{"className":16832,"code":16833,"language":624,"meta":625},[622],"site-accent-rgb   完整 RGB 对象\nsite-accent       red \u002F green \u002F blue \u002F custom 等名称\n",[60,16835,16833],{"__ignoreMap":625},[115,16837,16838,16839,16842],{},"恢复后，深度监听 ",[60,16840,16841],{},"accentRgb","，后续每次变化都会重新保存。由于服务端无法读取浏览器存储，静态 HTML 首先使用 YAML 中的默认颜色；客户端挂载后再恢复访客上次的选择。",[115,16844,16845,16846,1471,16849,16852],{},"旧版本曾使用黄色默认主题。为了避免已经访问过网站的浏览器永远被旧缓存锁在黄色，恢复逻辑专门识别 ",[60,16847,16848],{},"{255,255,2}",[60,16850,16851],{},"yellow"," 的组合，删除这两个旧值并回到当前 YAML 默认色。这是一次小型数据迁移：不是清空所有人的自定义配色，只清除能够明确识别的旧默认值。",[115,16854,16855,16856,16859,16860,16863,16864,16867,16868,16871,16872,16875],{},"动态 favicon 也跟随主题和站点名称。",[60,16857,16858],{},"app.vue"," 在内存中生成一个包含主题背景色与 ",[60,16861,16862],{},"site.name"," 首字符的 SVG，再编码为 ",[60,16865,16866],{},"data:image\u002Fsvg+xml","。因此 CMS 修改站点名称并重新构建后，标签页字母会同步改变；图标底色、字色和 ",[60,16869,16870],{},"meta[name=theme-color]"," 则继续与 RGB 面板同步。",[60,16873,16874],{},"public\u002Ffavicon.svg"," 只作为静态回退。",[130,16877,16879],{"id":16878},"十二rgb-调色器的交互细节","十二、RGB 调色器的交互细节",[115,16881,16882,16885,16886,16889],{},[60,16883,16884],{},"RgbAssembly.vue"," 不是独立维护另一份颜色，它调用同一个 ",[60,16887,16888],{},"useAccentTheme()","，因此滑杆、预设按钮、页面按钮和 favicon 始终共享状态。",[115,16891,16892],{},"三个 range input 分别控制 R、G、B：",[140,16894,16895,16900,16903,16906,16909],{},[143,16896,16897,16899],{},[60,16898,244],{}," 事件实时更新颜色，拖动时页面连续渐变；",[143,16901,16902],{},"每条轨道通过 CSS 变量计算已填充百分比；",[143,16904,16905],{},"输出区显示十六进制、十进制通道值，并采用自动对比文字；",[143,16907,16908],{},"点击 RED、GREEN、BLUE 立即加载预设；",[143,16910,16911],{},"重置按钮恢复 YAML 中的默认颜色，而不是写死某个预设。",[115,16913,16914],{},"字标动画不能在滑杆的每一个像素变化时重播，否则会不断重建动画并显得卡顿。组件因此区分“预览变化”和“提交变化”：",[140,16916,16917,16924,16930,16940,16947],{},[143,16918,16919,16920,16923],{},"指针按下后标记 ",[60,16921,16922],{},"pointerActive","，并捕获当前 pointer；",[143,16925,16926,16927,16929],{},"拖动中的 ",[60,16928,244],{}," 只改变颜色；",[143,16931,16932,16935,16936,16939],{},[60,16933,16934],{},"pointerup"," 或 ",[60,16937,16938],{},"pointercancel"," 才请求重播一次字标；",[143,16941,16942,16943,16946],{},"键盘调整 range 时由 ",[60,16944,16945],{},"change"," 请求重播；",[143,16948,16949,16950,4814,16952,16954],{},"一个零延时抑制标志防止同一次鼠标提交同时触发 ",[60,16951,16934],{},[60,16953,16945],{},"，造成双播。",[115,16956,16957,16958],{},"预设与重置属于离散操作，所以点击时立即换色并重播。这个模型可以概括为：",[125,16959,16960],{},"连续操作实时预览、松手提交动画；离散操作立即提交。",[130,16962,16964],{"id":16963},"十三quilibra-字标是可编辑的艺术字","十三、Quilibra 字标是可编辑的艺术字",[115,16966,16967,16968,16971,16972,16975],{},"当前字标不再依赖固定图片，而是由 ",[60,16969,16970],{},"content\u002Fsettings\u002Fhome.yml"," 中的 ",[60,16973,16974],{},"headline.emphasis"," 生成真实文本。CMS 的“首页 \u002F 艺术字文字”可以直接修改内容，Schema 将长度限制为 20 个字符；组件还会按照中英文字符宽度自动缩小过长文字，避免挤出首屏。",[115,16977,16978],{},"字形使用随构建产物一起发布的 Cormorant Garamond 600 Italic。页面只引入拉丁字符子集，既保留高对比衬线斜体的艺术感，也不依赖 Google Fonts 等站外服务；无法覆盖的字符会回退到本机衬线字体。",[115,16980,16981],{},"动画仍然采用“RGB 套色从偏移到合拢”，没有模拟笔尖逐笔书写。字标由四层相同文本组成：",[140,16983,16984,16987,16990,16993],{},[143,16985,16986],{},"红层从左下附近偏移进入；",[143,16988,16989],{},"绿层从上方偏移进入；",[143,16991,16992],{},"蓝层从右下附近偏移进入；",[143,16994,16995],{},"最终层使用当前主题色并带极轻的阴影。",[115,16997,16998,16999,17002,17003,195,17006,4814,17009,17012,17013,17016],{},"前三层使用 ",[60,17000,17001],{},"mix-blend-mode: multiply","、动态模糊、透明度和不同位移。动画前段保留可见错版，中段快速靠近，后段降低彩色层透明度并让最终主题层聚焦。四层都只改变 ",[60,17004,17005],{},"opacity",[60,17007,17008],{},"transform",[60,17010,17011],{},"filter","，播放时临时声明 ",[60,17014,17015],{},"will-change","，结束后不长期占用合成资源。",[115,17018,17019,17022],{},[60,17020,17021],{},"ArtisticWordmark.vue"," 的加载控制解决了“首次打开没有动画”和字体中途替换的问题：",[4640,17024,17025,17031,17038,17041,17044],{},[143,17026,17027,17030],{},[60,17028,17029],{},"@fontsource\u002Fcormorant-garamond"," 把字体文件交给 Vite 打包并生成带哈希的静态资源。",[143,17032,17033,17034,17037],{},"组件调用 ",[60,17035,17036],{},"document.fonts.load()","，等待艺术字体真正可用于绘制。",[143,17039,17040],{},"只有字体就绪、组件仍存活、页面可见且请求仍是最新时才开始播放。",[143,17042,17043],{},"字体加载失败时继续使用衬线回退字体，不阻断内容显示。",[143,17045,17046],{},"开启减少动态效果时直接展示最终状态。",[115,17048,17049,17050,17053,17054,17057],{},"每次重播都会增加 ",[60,17051,17052],{},"animationRun","，并把它放进四层的 Vue ",[60,17055,17056],{},"key","。这会让浏览器得到新的动画元素，可靠地从第 0 帧开始，而不是依赖移除 class 后强制读取布局。",[130,17059,17061],{"id":17060},"十四字标在什么时候重播","十四、字标在什么时候重播",[115,17063,17064,17065,17068,17069,17072],{},"全局 ",[60,17066,17067],{},"useWordmarkAnimation()"," 只维护一个数字信号。任何组件调用 ",[60,17070,17071],{},"requestReplay()","，数字加一；字标组件 watch 到变化后重播。这个很小的事件通道避免 RGB 组件直接引用字标 DOM。",[115,17074,17075],{},"当前重播场景包括：",[140,17077,17078,17081,17084,17087,17090,17093,17096],{},[143,17079,17080],{},"首次进入主页并且艺术字体加载完成；",[143,17082,17083],{},"点击主题预设或重置；",[143,17085,17086],{},"RGB 滑杆松手或键盘提交；",[143,17088,17089],{},"从文章、服务、关于页面返回主页，主页组件重新挂载；",[143,17091,17092],{},"浏览器标签页从隐藏变为可见；",[143,17094,17095],{},"窗口重新获得焦点；",[143,17097,17098,17099,17102],{},"浏览器的 ",[60,17100,17101],{},"pageshow","，包括可能来自往返缓存的恢复。",[115,17104,17105,195,17108,4814,17111,17113],{},[60,17106,17107],{},"visibilitychange",[60,17109,17110],{},"focus",[60,17112,17101],{}," 可能在同一次切回中连续到达。组件使用一个 animation frame 合并激活请求，并设置 200ms 的最小间隔，防止一次切回连续播两三遍。页面变为隐藏时会取消待播放帧、使旧异步请求失效并结束当前播放。",[115,17115,17116],{},"这里还有两个无障碍与容错原则：",[140,17118,17119,17125],{},[143,17120,17121,17124],{},[60,17122,17123],{},"prefers-reduced-motion: reduce"," 时不播放套色，直接显示最终字标；",[143,17126,17127,17128,17131],{},"可读文字仍存在于 ",[60,17129,17130],{},"sr-only"," 中，视觉 mask 不承担标题语义。",[130,17133,17135],{"id":17134},"十五服务矩阵图标与状态点","十五、服务矩阵、图标与状态点",[115,17137,17138],{},"每个服务在 YAML 中有六个字段：名称、说明、链接、状态、图标、是否显示在主页。",[115,17140,17141,17144,17145,1471,17147,17150,17151,17154],{},[60,17142,17143],{},"ServiceIcon.vue"," 把业务名称映射到 Lucide 图标。NAS 使用独立的 ",[60,17146,72],{},[60,17148,17149],{},"HardDrive","，不会复用影音播放图标。未知值虽然有 ",[60,17152,17153],{},"Server"," 回退，但正常内容会在 Schema 阶段被拒绝，因此回退只是组件级防御。",[115,17156,17157],{},"状态共有三种：",[619,17159,17162],{"className":17160,"code":17161,"language":624,"meta":625},[622],"online   在线\nprivate  仅限本人\nsoon     准备中\n",[60,17163,17161],{"__ignoreMap":625},[115,17165,17166,17169,17170,17173,17174,17177],{},[60,17167,17168],{},"ServiceStatusDot.vue"," 把状态写入 ",[60,17171,17172],{},"data-state","，CSS 用属性选择器决定颜色，",[60,17175,17176],{},"title"," 提供文字说明。主页和服务页都复用这个组件，因此 CMS 修改 YAML 状态后，重新构建的页面会同步变化，不需要分别维护配色。",[115,17179,17180,17181,17184],{},"状态点表达的是",[125,17182,17183],{},"人工配置的展示状态","，不是实时健康检查。它不会主动请求 Code、Photos、NAS 或 Status，也不会自动从监控系统同步。如果未来需要实时状态，应由独立状态 API 提供可信结果，再决定是在客户端请求还是构建前抓取；不能仅把绿色小点误解为自动监控。",[115,17186,17187,17188,17190,17191,17194,17195,17198,17199,17202],{},"链接为 ",[60,17189,10236],{}," 时，主页服务节点转到 ",[60,17192,17193],{},"\u002Fservices","，服务页显示为不可点击行；完整 HTTPS 链接则在新标签页打开，并带 ",[60,17196,17197],{},"noopener noreferrer","。Schema 禁止 ",[60,17200,17201],{},"javascript:"," 和非 HTTPS 外部地址，避免 CMS 字段直接变成危险链接。",[130,17204,17206],{"id":17205},"十六文章列表筛选与搜索","十六、文章列表、筛选与搜索",[115,17208,17209,17211,17212,17215],{},[60,17210,16574],{}," 在构建阶段只查询 ",[60,17213,17214],{},"hidden = false"," 的文章，并按日期倒序排列。首页最近文章使用同样条件，浏览器端再对已经加载的公开数组做筛选，不需要为每个关键词请求服务器。",[115,17217,17218,17219,17222],{},"分类来自共享常量：随笔、学习、交易、观察。列表额外添加“全部”。搜索会把标题、摘要和标签拼成小写字符串，再做简单的 ",[60,17220,17221],{},"includes"," 匹配。这适合当前文章规模，特点是实现透明、中文可用、没有索引服务；但它不是分词搜索，也不会搜索正文。",[115,17224,17225],{},"文章行显示日期、标题、摘要、分类和箭头。悬浮一篇时：",[140,17227,17228,17231,17234,17237],{},[143,17229,17230],{},"只给当前行增加浅主题色背景；",[143,17232,17233],{},"日期与正文整体平移 8px；",[143,17235,17236],{},"箭头向右上轻移；",[143,17238,17239],{},"其他文章保持完全不变。",[115,17241,17242,17243,195,17246,17249,17250,17253],{},"标题容器的 grid 列不会在 hover 时改变，所以长标题不会因为悬浮突然获得更小宽度而换行。主页“最近写下”的标题使用 ",[60,17244,17245],{},"minmax(0,1fr)",[60,17247,17248],{},"overflow:hidden","、省略号和 ",[60,17251,17252],{},"white-space:nowrap","，确保固定高度面板不被长标题撑开。",[115,17255,17256,17257,17260],{},"搜索结果区使用 ",[60,17258,17259],{},"aria-live=\"polite\"","，筛选后的篇数和空状态可以被辅助技术感知。",[115,17262,17263,17264,17267,17268,17270,17271,17274,17275,17278],{},"从文章详情返回列表时，列表状态会被恢复。",[60,17265,17266],{},"useWritingListState()"," 用一个小的 Nuxt ",[60,17269,16769],{}," 保存当前分类、搜索词和滚动位置：进入文章时 ",[60,17272,17273],{},"capture()"," 记录这三项并标记 ",[60,17276,17277],{},"restorePending","，返回列表时读取并消费这份状态，恢复筛选结果和滚动位置；浏览器后退同样恢复。直接打开一篇文章（例如从外部链接进入）没有可恢复的上下文，列表从顶部开始。恢复只发生在同一会话内存中，不写入 localStorage。",[130,17280,17282],{"id":17281},"十七文章详情与相邻导航","十七、文章详情与相邻导航",[115,17284,17285,17286,17289,17290,17293],{},"动态路由文件是 ",[60,17287,17288],{},"app\u002Fpages\u002Fwriting\u002F[...slug].vue","。它使用当前 ",[60,17291,17292],{},"route.path"," 查询对应文章；找不到时立即抛出带中文说明的 404。",[115,17295,17296,17297,17299],{},"详情查询本身也要求 ",[60,17298,17214],{},"，所以隐藏文章不只是从列表消失，直接访问原 URL 也会得到真正的 404。详情页同时取得按日期倒序排列的全部公开文章，用当前文章的数组位置计算：",[140,17301,17302,17305],{},[143,17303,17304],{},"数组中下一项是时间上更旧的“上一篇”；",[143,17306,17307],{},"数组中上一项是时间上更新的“下一篇”。",[115,17309,17310,17311,17313],{},"页面展示分类、发布日期、可选的更新日期、标题、摘要、标签与正文。正文由 Nuxt Content 的 ",[60,17312,16316],{}," 生成。SEO 标题使用全站模板变成“文章标题 · Quilibra”，描述来自 frontmatter，Open Graph 类型设为 article。",[115,17315,17316,17317,17320,17321,17324,17325,17328],{},"详情页会记住每篇文章的阅读位置。",[60,17318,17319],{},"useArticleReadingState()"," 把“路径到滚动位置”的映射存在 ",[60,17322,17323],{},"sessionStorage"," 中，离开文章时捕获当前位置，回到文章时用 ",[60,17326,17327],{},"behavior: 'instant'"," 一步恢复，不经过平滑滚动，避免看到一次从顶部滑下来的动画；恢复值会被限制在当前可滚动的最大范围内，内容变化后也不会定位到不存在的区域。同一会话内在多篇文章间切换，各自的位置互不覆盖。",[115,17330,17331,17332,17335,17336,17339,17340,17343,17344,17347],{},"文章内的标题锚点在允许动画的系统中平滑滚动：CSS 在 ",[60,17333,17334],{},"prefers-reduced-motion: no-preference"," 时给 ",[60,17337,17338],{},"html:has(.article-prose)"," 设置 ",[60,17341,17342],{},"scroll-behavior: smooth","，标题统一留出 16 像素的 ",[60,17345,17346],{},"scroll-margin-top","；减少动态效果时保持瞬间跳转。",[115,17349,17350,17351,17354],{},"正文样式集中在 ",[60,17352,17353],{},".article-prose","：限制阅读宽度、提高行高、分级处理标题、列表、引用、代码、链接、图片和带边框的表格（表格在 2026 年 8 月底加上了完整的边框、内边距和表头底色，长表格在窄屏下可横向阅读）。文章页面可以自由使用 Markdown，不需要为每篇文章编写 Vue 模板。",[130,17356,17358],{"id":17357},"十八文章-frontmatter-合约","十八、文章 frontmatter 合约",[115,17360,17361],{},"一篇新文章最小结构如下：",[619,17363,17367],{"className":17364,"code":17365,"language":17366,"meta":625,"style":625},"language-yaml shiki shiki-themes github-light github-dark","---\ntitle: 清楚的文章标题\ndescription: 一句话说明文章实际记录了什么。\ndate: 2026-07-26\nhidden: false\ncategory: 学习\ntags:\n  - Nuxt\n---\n","yaml",[60,17368,17369,17374,17383,17393,17403,17413,17423,17431,17439],{"__ignoreMap":625},[723,17370,17371],{"class":725,"line":726},[723,17372,17373],{"class":5691},"---\n",[723,17375,17376,17378,17380],{"class":725,"line":732},[723,17377,17176],{"class":16545},[723,17379,5391],{"class":5381},[723,17381,17382],{"class":5394},"清楚的文章标题\n",[723,17384,17385,17388,17390],{"class":725,"line":34},[723,17386,17387],{"class":16545},"description",[723,17389,5391],{"class":5381},[723,17391,17392],{"class":5394},"一句话说明文章实际记录了什么。\n",[723,17394,17395,17398,17400],{"class":725,"line":1068},[723,17396,17397],{"class":16545},"date",[723,17399,5391],{"class":5381},[723,17401,17402],{"class":5387},"2026-07-26\n",[723,17404,17405,17408,17410],{"class":725,"line":1074},[723,17406,17407],{"class":16545},"hidden",[723,17409,5391],{"class":5381},[723,17411,17412],{"class":5387},"false\n",[723,17414,17415,17418,17420],{"class":725,"line":1080},[723,17416,17417],{"class":16545},"category",[723,17419,5391],{"class":5381},[723,17421,17422],{"class":5394},"学习\n",[723,17424,17425,17428],{"class":725,"line":1086},[723,17426,17427],{"class":16545},"tags",[723,17429,17430],{"class":5381},":\n",[723,17432,17433,17436],{"class":725,"line":1092},[723,17434,17435],{"class":5381},"  - ",[723,17437,17438],{"class":5394},"Nuxt\n",[723,17440,17441],{"class":725,"line":1098},[723,17442,17373],{"class":5691},[115,17444,17445],{},"文件名必须是：",[619,17447,17450],{"className":17448,"code":17449,"language":624,"meta":625},[622],"YYYY-MM-DD-english-kebab.md\n",[60,17451,17449],{"__ignoreMap":625},[115,17453,17454,17455,195,17457,195,17459,195,17461,195,17464,195,17467,195,17469,195,17471,863,17473,17475,17476,17478],{},"允许字段只有 ",[60,17456,17176],{},[60,17458,17387],{},[60,17460,17397],{},[60,17462,17463],{},"updated",[60,17465,17466],{},"slug",[60,17468,17407],{},[60,17470,17417],{},[60,17472,17427],{},[60,17474,17407],{}," 只能是布尔值，省略时按 ",[60,17477,16522],{}," 处理。发布新文章时：",[140,17480,17481,17486,17492,17497],{},[143,17482,17483,17485],{},[60,17484,17397],{}," 必须是中国时区当天；",[143,17487,17488,17489,17491],{},"文件名日期必须与 ",[60,17490,17397],{}," 一致；",[143,17493,17494,17495,4588],{},"不能包含 ",[60,17496,17463],{},[143,17498,17499,17501],{},[60,17500,17466],{}," 如果存在，必须与文件名后半段一致。",[115,17503,17504,17505,17507,17508,17510,17511,17514],{},"修订文章时保留原文件名和 ",[60,17506,17397],{},"，把 ",[60,17509,17463],{}," 设置为中国时区当天。writer 默认保留现有隐藏状态，只有修订内容明确给出 ",[60,17512,17513],{},"hidden: false"," 才会恢复。正文结构是自由的，但不能为空。若使用二级标题“来源与延伸阅读”，该节必须至少有一个 HTTP 或 HTTPS 链接。",[115,17516,17517,17520,17521,17524],{},[60,17518,17519],{},"scripts\u002Fcontent\u002Fvalidate.ts"," 使用 ",[60,17522,17523],{},"gray-matter"," 和 YAML 解析 frontmatter，再用 Markdown AST 检查正文与来源节。使用结构化解析而不是正则扫描全文，可以区分标题、链接与普通文本，并给出确定的错误。",[130,17526,17528],{"id":17527},"十九sveltia-cms-到底是什么","十九、Sveltia CMS 到底是什么",[115,17530,17531,17534,17535,17537],{},[60,17532,17533],{},"\u002Fadmin\u002Findex.html"," 只加载 Sveltia CMS 的前端脚本，",[60,17536,16460],{}," 描述后台字段。它不是另一个网站后端，也不存文章副本。",[115,17539,17540],{},"后台能维护：",[140,17542,17543,17546,17549,17552,17555,17558],{},[143,17544,17545],{},"站点、首页与栏目文案；",[143,17547,17548],{},"默认强调色和主页模块开关；",[143,17550,17551],{},"服务的增删、顺序、URL、状态、图标和主页显示；",[143,17553,17554],{},"文章的新建、搜索、筛选、修改和删除；",[143,17556,17557],{},"文章的隐藏和恢复；",[143,17559,17560,17562],{},[60,17561,16190],{}," 中的文章资源。",[115,17564,17565],{},"CMS 有两种工作方式：",[4640,17567,17568,17571],{},[143,17569,17570],{},"本地仓库模式：Chromium 通过 File System Access API 直接编辑本机选择的仓库。",[143,17572,17573],{},"GitHub 模式：用户经 GitHub 登录后，CMS 根据配置向私人仓库提交内容。",[115,17575,17576,17577,17579],{},"无论哪种模式，“保存”都只意味着源文件或远程仓库出现改动。它不会直接覆盖某个静态目录。远程 CMS 当前固定提交私人仓库的 ",[60,17578,15914],{}," 分支，完整链路是：",[619,17581,17584],{"className":17582,"code":17583,"language":624,"meta":625},[622],"quilibra.cn\u002Fadmin\n        |\n        | GitHub OAuth 保存\n        v\nGitHub master\n        |\n        | GitHub push Webhook，HMAC-SHA256 签名\n        v\nVPS 事件队列 -> master 同步 -> 快速或完整构建 -> production release -> 127.0.0.1:3000\n",[60,17585,17583],{"__ignoreMap":625},[115,17587,17588],{},"因此，在 CMS 打开“隐藏文章”并保存，正式站会在同步器构建完成后自动切换到新 release；不再有 staging 预览和 promote 晋级。2026 年 8 月 3 日之前，这条链路中间还有 staging 分支、测试域名和人工晋级，CMS 保存只先出现在测试站；简化后保存即上线，代价是发布前的内容自查变得更加重要。",[115,17590,17591,17592,17595],{},"GitHub OAuth 只控制“谁能通过 CMS 读写私人仓库”。本项目不使用 GitHub Actions 部署，也不开放公网 SSH；GitHub 只向现有 HTTPS 精确路径发送带签名的 push 通知，VPS 收到后再通过 Sing-box HTTP 代理主动拉取 master。Webhook secret 只在 VPS 和 GitHub 仓库设置中保存。私人仓库存放网站源码、文章、公开图片和不含密钥的配置；OAuth secret、访问令牌、Webhook secret、部署私钥、OpenClaw 会话、cookie 和 ",[60,17593,17594],{},".env"," 不进入仓库。",[115,17597,17598,17599,17602,17603,17606,17607,17609],{},"远程后台统一通过 ",[60,17600,17601],{},"https:\u002F\u002Fquilibra.cn\u002Fadmin\u002Findex.html"," 使用。这个路径随静态站点公开发布，页面本身带 ",[60,17604,17605],{},"noindex"," 且不含任何秘密；能不能登录、能不能保存，完全由 GitHub OAuth 与私人仓库权限决定。2026 年 8 月 3 日之前它曾躲在带 Basic Auth 的测试域名后面，双环境简化时这层入口遮罩被一并移除。后台路径和 ",[60,17608,17605],{}," 都不是访问控制。",[130,17611,17613],{"id":17612},"二十受限-writer-为什么存在","二十、受限 writer 为什么存在",[115,17615,17616,17617,17620],{},"普通编辑器可以修改仓库中的任何文件，但自动化发布工具不应该拥有同样宽的写入自由。",[60,17618,17619],{},"scripts\u002Fcontent\u002Fwrite.ts"," 把 OpenClaw 的写入范围压缩为三个明确操作：create、update、delete。",[115,17622,17623],{},"新建流程只接受草稿目录中的一个普通 Markdown 文件名和一个无日期 slug。writer 会检查：",[140,17625,17626,17629,17632,17635,17638,17641,17644],{},[143,17627,17628],{},"草稿名不能包含目录或路径穿越；",[143,17630,17631],{},"草稿必须是普通文件，不能是符号链接；",[143,17633,17634],{},"草稿不超过 1 MiB；",[143,17636,17637],{},"slug 必须是小写英文、数字和连字符；",[143,17639,17640],{},"frontmatter 与正文必须通过完整校验；",[143,17642,17643],{},"日期必须是中国时区当天；",[143,17645,17646],{},"目标文章不能已经存在。",[115,17648,17649,17650,17653,17654,17657,17658,17661,17662,17665],{},"写入时先以 ",[60,17651,17652],{},"wx"," 模式建立权限为 ",[60,17655,17656],{},"0640"," 的随机临时文件并 ",[60,17659,17660],{},"fsync","，再用硬链接创建最终文件，利用文件系统的 ",[60,17663,17664],{},"EEXIST"," 保证不会覆盖已有新文章。更新时先验证现有文件和发布日期，再用 rename 原子替换。删除只接受精确的完整 dated slug，并拒绝目录、符号链接和不存在的目标。",[115,17667,17668],{},"这不是为了让日常写作变复杂，而是为了让“从聊天自动发布”仍有可审计的硬边界。Skill 中的文字规则可能被模型误解，writer 的路径、日期和文件类型检查则由操作系统与代码强制执行。",[130,17670,17672],{"id":17671},"二十一openclaw-发布-skill-的实际状态机","二十一、OpenClaw 发布 Skill 的实际状态机",[115,17674,17675,17676,17679],{},"仓库只维护一个 ",[60,17677,17678],{},"personal-site-publisher"," Skill。它先读取文章合约，再按以下过程工作：",[619,17681,17684],{"className":17682,"code":17683,"language":624,"meta":625},[622],"链接或素材\n   |\n   v\n确定标题、摘要、日期、slug、分类、标签\n   |\n   v\n在私聊中返回完整草稿，不写仓库\n   |\n   +-- 修改：... --> 修改内存草稿\n   +-- 放弃 ------> 清除内存草稿\n   +-- 发布 ------> 保存到受限草稿目录\n                         |\n                         v\n            content:write + content:validate\n                         |\n                         v\n              提交并推送 GitHub master\n                         |\n                         v\n        Webhook 自动构建部署，返回文章 URL 和 commit\n",[60,17685,17683],{"__ignoreMap":625},[115,17687,17688,17691,17692,863],{},[60,17689,17690],{},"直接发布"," 只跳过人工预览，不跳过 writer、内容校验和静态生成。更新文章必须保留 dated URL；删除必须先展示目标信息，再收到完全匹配的 ",[60,17693,17694],{},"确认删除 \u003Cdated-slug>",[115,17696,17697,17698,195,17701,16935,17704,17707],{},"发布 Skill 还规定：只接受所有者私聊中的发布命令；把网页、对话和搜索结果视为不可信内容；不把 API key、cookie、账号信息、私人对话细节和精确私人资产写入文章；不能用任意编辑器、",[60,17699,17700],{},"rm",[60,17702,17703],{},"git add",[60,17705,17706],{},"git commit"," 绕过 writer 和 release 脚本。",[130,17709,17711],{"id":17710},"二十二本地发布自动同步与内容快速通道","二十二、本地发布、自动同步与内容快速通道",[115,17713,17714],{},"当前有四个相关入口，名字接近但权限完全不同。",[135,17716,17718],{"id":17717},"npm-run-publish",[60,17719,17720],{},"npm run publish",[115,17722,17723],{},"用于受信本地流程中尚未提交的内容改动，例如 OpenClaw Skill。它只允许以下路径：",[619,17725,17728],{"className":17726,"code":17727,"language":624,"meta":625},[622],"content\u002Fsettings\u002Fsite.yml\ncontent\u002Fsettings\u002Fhome.yml\ncontent\u002Fsettings\u002Fwriting.yml\ncontent\u002Fsettings\u002Fservices.yml\ncontent\u002Fsettings\u002Fabout.yml\ncontent\u002Fwriting\u002F**\npublic\u002Fuploads\u002F**\n",[60,17729,17727],{"__ignoreMap":625},[115,17731,17732],{},"它验证内容、生成静态站、自动创建本地 Git commit、复制 release 并切换本地默认生产目录。若工作区混入 Vue、CSS、脚本、CMS 配置或文档改动，会拒绝执行。它是受限本地发布入口；线上更新仍然依赖把提交推送到 GitHub 后由 Webhook 部署。",[135,17734,17736],{"id":17735},"npm-run-deploy",[60,17737,17738],{},"npm run deploy",[115,17740,17741],{},"用于已经提交的代码版本。它要求工作区完全干净，不创建 commit，并依次运行：",[619,17743,17746],{"className":17744,"code":17745,"language":624,"meta":625},[622],"类型检查\n内容与 writer 单元测试\nrelease\u002Fsync\u002Fwebhook 单元测试\n静态生成\n浏览器 E2E\n创建 release\n原子切换 current\n清理过旧 release\n",[60,17747,17745],{"__ignoreMap":625},[135,17749,17751],{"id":17750},"npm-run-deploycontent",[60,17752,17753],{},"npm run deploy:content",[115,17755,17756,17757,863],{},"自动同步器判定提交只包含受管内容时使用的快速路径。它仍执行内容校验和静态生成，但跳过类型检查、单元测试和 E2E，让纯文章或站点设置的提交几十秒内上线；任何代码文件变化都会让同步器退回完整 ",[60,17758,17759],{},"deploy",[135,17761,17763],{"id":17762},"npm-run-sync",[60,17764,17765],{},"npm run sync",[115,17767,17768,17771],{},[60,17769,17770],{},"scripts\u002Fsync.ts"," 是 master 自动同步器的唯一实现，由 systemd oneshot 服务在收到 push 事件后运行。每轮会：",[4640,17773,17774,17777,17780,17790,17795,17801],{},[143,17775,17776],{},"从 Sing-box 配置读取带认证的本机 HTTP 代理；",[143,17778,17779],{},"重试 GitHub fetch，只接受能够快进的远端历史；",[143,17781,17782,17783,17786,17787,4588],{},"根据 ",[60,17784,17785],{},"package-lock.json"," 哈希决定是否运行 ",[60,17788,17789],{},"npm ci",[143,17791,17792,17793,4588],{},"比较上次已部署提交与目标提交：只有受管内容变化时执行快速内容部署，否则执行完整 ",[60,17794,17738],{},[143,17796,17797,17798,4588],{},"只有部署成功才原子写入 ",[60,17799,17800],{},"deployed-commit",[143,17802,17803],{},"失败时保留旧 release，由持久化 Webhook 事件延迟重试。",[115,17805,17806,17807,17809,17810,17813],{},"它不再区分 staging：仓库、",[60,17808,17800],{}," 和 release 目录都只有一份。构建通过共享的 ",[60,17811,17812],{},"flock"," 文件串行执行，防止 2 GB 内存的 VPS 同时跑多份 Nuxt 构建和 Playwright。",[135,17815,17817],{"id":17816},"npm-run-webhook",[60,17818,17819],{},"npm run webhook",[115,17821,17822,17825,17826,17829,17830,17833,17834,17836,17837,17840,17841,863],{},[60,17823,17824],{},"scripts\u002Fwebhook.ts"," 只监听 ",[60,17827,17828],{},"127.0.0.1:3110","，Nginx 把 ",[60,17831,17832],{},"https:\u002F\u002Fquilibra.cn\u002F_hooks\u002Fgithub"," 的精确 POST 路由转给它。接收器先验证 HMAC-SHA256 签名、仓库名、push 类型和 ",[60,17835,15914],{}," 分支，再把事件以权限 ",[60,17838,17839],{},"0600"," 写入用户状态目录并返回 ",[60,17842,17843],{},"202",[115,17845,17846],{},"每个 GitHub delivery 都有独立队列文件。若构建期间出现第二次 push，第一轮完成后会再启动一轮同步；进程重启时也会恢复未完成事件。失败使用延迟重试，不需要周期性轮询 GitHub。Webhook 请求只能映射到固定的同步 unit，不能携带或执行任意命令。",[115,17848,17849,17850,17852,17853,17856,17857,17859,17860,17862,17863,17865],{},"因此，修改文章现在只有两条合法路径：远程 CMS 保存到 ",[60,17851,15914],{},"，或本地受信流程用 writer\u002F",[60,17854,17855],{},"publish"," 提交；两者最终都由同一个 Webhook 部署。修改 Vue、CSS 或脚本并提交后，同步器自动执行完整 ",[60,17858,17759],{},"。不要用 ",[60,17861,17855],{}," 偷渡代码，也不要让 ",[60,17864,17759],{}," 带着未提交文件上线。",[130,17867,17869],{"id":17868},"二十三静态-release-如何做到切换时不中断","二十三、静态 release 如何做到切换时不中断",[115,17871,17872,17875],{},[60,17873,17874],{},"scripts\u002Frelease.ts"," 用 UTC 时间生成 release 名称，例如：",[619,17877,17880],{"className":17878,"code":17879,"language":624,"meta":625},[622],"20260726T144914-134Z\n",[60,17881,17879],{"__ignoreMap":625},[115,17883,17884,17885,17887,17888,17891,17892,17894],{},"生成结束后，把 ",[60,17886,15956],{}," 递归复制到 ",[60,17889,17890],{},"releases\u002F\u003Cid>","。激活时不是先删 ",[60,17893,15960],{}," 再新建，而是：",[4640,17896,17897,17900,17903],{},[143,17898,17899],{},"创建一个带进程号的临时符号链接；",[143,17901,17902],{},"让临时链接指向新 release；",[143,17904,17905,17906,17909,17910,863],{},"用 ",[60,17907,17908],{},"rename"," 把临时链接原子替换成 ",[60,17911,15960],{},[115,17913,17914,17915,17917,17918,17920],{},"对访问者来说，某个请求要么读取旧 release，要么读取新 release，不会遇到 ",[60,17916,15960],{}," 暂时不存在的中间状态。静态 Node 进程也不需要重启，因为它每次请求都从 ",[60,17919,15960],{}," 路径解析文件。",[115,17922,17923,17926,17927,17930],{},[60,17924,17925],{},"PERSONAL_SITE_DATA_ROOT"," 决定 release 根目录。生产不设置时使用 ",[60,17928,17929],{},"~\u002F.local\u002Fshare\u002Fpersonal-site","；双环境简化后，staging 数据根目录已经删除，线上只有这一份 release 历史。",[115,17932,17933],{},"脚本默认保留最近五个 release，并额外保证当前激活版本不被删掉。由于当前版本有可能是手工回滚到的较旧目录，保留“当前版本”比单纯保留时间最新的五个更重要。",[115,17935,17936,17937,17939],{},"回滚只调用同一个原子激活函数，把 ",[60,17938,15960],{}," 指回已存在的 release。它不修改 Git、不重新构建、不删除较新的版本，也不重启生产服务。",[130,17941,17943],{"id":17942},"二十四静态服务器nginx-与-https","二十四、静态服务器、Nginx 与 HTTPS",[115,17945,17946,17949,17950,195,17953,195,17956,4814,17959,17962,17963,17965,17966,195,17969,195,17972,17975,17976,17979],{},[60,17947,17948],{},"scripts\u002Fstatic-server.mjs"," 使用 Node 自带的 ",[60,17951,17952],{},"http",[60,17954,17955],{},"fs",[60,17957,17958],{},"stream",[60,17960,17961],{},"zlib","。监听地址、端口和 ",[60,17964,15960],{}," 路径分别由 ",[60,17967,17968],{},"PERSONAL_SITE_HOST",[60,17970,17971],{},"PERSONAL_SITE_PORT",[60,17973,17974],{},"PERSONAL_SITE_ROOT"," 控制。systemd unit 把 host 设为 ",[60,17977,17978],{},"127.0.0.1","，所以 3000 不直接接受公网连接。它没有框架中间件，职责非常窄：",[140,17981,17982,17985,17998,18004,18011,18014,18017,18020,18023,18026,18033,18039,18042],{},[143,17983,17984],{},"只接受 GET 和 HEAD，其他方法返回 405；",[143,17986,17987,17988,1471,17991,17994,17995,17997],{},"解码 URL 后用 ",[60,17989,17990],{},"resolve",[60,17992,17993],{},"relative"," 检查目标仍在 ",[60,17996,15960],{}," 内，阻止目录穿越；",[143,17999,18000,18001,4588],{},"请求目录时返回该目录的 ",[60,18002,18003],{},"index.html",[143,18005,18006,18007,18010],{},"找不到文件时返回预生成的 ",[60,18008,18009],{},"404.html"," 和 404 状态；",[143,18012,18013],{},"根据扩展名发送正确的 Content-Type；",[143,18015,18016],{},"对大于等于 1 KiB 的文本资源协商 Brotli 或 gzip；",[143,18018,18019],{},"Brotli 使用质量 4，gzip 使用级别 6，在 CPU 与体积间取中间值；",[143,18021,18022],{},"使用读取流和 pipeline，不把大文件一次性读进内存；",[143,18024,18025],{},"根据文件大小和修改时间生成弱 ETag；",[143,18027,18028,18029,18032],{},"命中 ",[60,18030,18031],{},"If-None-Match"," 时返回 304；",[143,18034,18035,18038],{},[60,18036,18037],{},"_nuxt\u002F"," 哈希资源缓存一年并标记 immutable；",[143,18040,18041],{},"HTML、图片等非哈希路径使用 no-cache，确保 release 切换后会重新验证；",[143,18043,18044],{},"收到 SIGINT 或 SIGTERM 时停止接受新连接并正常退出。",[115,18046,18047,18048,18050],{},"如果 ",[60,18049,15960],{}," 不可用，服务器返回 503，而不是把文件系统错误暴露给访问者。若响应流已经开始后出错，则销毁连接，避免发送一半文件后再拼接错误文本。",[115,18052,18053],{},"它不会处理 TLS、OAuth 或反向代理。公网入口由 Nginx 负责：",[619,18055,18058],{"className":18056,"code":18057,"language":624,"meta":625},[622],"quilibra.cn \u002F www.quilibra.cn -> HTTPS -> 127.0.0.1:3000\nquilibra.cn\u002F_hooks\u002Fgithub     -> 精确 location -> 127.0.0.1:3110\n公网 IP或未知 Host             -> 默认站点拒绝\n",[60,18059,18057],{"__ignoreMap":625},[115,18061,18062,18063,18066,18067,18070],{},"根域名和 ",[60,18064,18065],{},"www"," 共用正式站，证书由 Certbot 申请并续期；HTTP 到 HTTPS 的跳转和证书续期都由 Certbot 管理。GitHub Webhook 是 Nginx 上的一个精确 location，只放行 POST，",[60,18068,18069],{},"client_max_body_size"," 限制为 1m。同一台 Nginx 还反代了个别与网站无关的自建子域，它们不属于这个仓库的发布链路，也不使用网站域名证书之外的资源。腾讯云安全组只需要开放 80 和 443，3000、3110、8081 不对公网放行。",[115,18072,18073,18074,18077,18078,18081],{},"页脚备案号来自全站设置 ",[60,18075,18076],{},"content\u002Fsettings\u002Fsite.yml","，当前 ICP 号链接到 ",[60,18079,18080],{},"https:\u002F\u002Fbeian.miit.gov.cn\u002F","。备案展示、DNS、TLS 证书和反向代理是四个不同层次：备案号不会自动配置 HTTPS，证书也不会自动创建 DNS 记录。",[130,18083,18085],{"id":18084},"二十五3000310031013110-分别是什么","二十五、3000、3100、3101、3110 分别是什么",[264,18087,18088,18101],{},[267,18089,18090],{},[270,18091,18092,18095,18098],{},[273,18093,18094],{},"端口",[273,18096,18097],{},"用途",[273,18099,18100],{},"生命周期",[283,18102,18103,18114,18129,18140],{},[270,18104,18105,18108,18111],{},[288,18106,18107],{},"3000",[288,18109,18110],{},"当前生产静态 release",[288,18112,18113],{},"systemd 长期运行",[270,18115,18116,18119,18122],{},[288,18117,18118],{},"3100",[288,18120,18121],{},"本地开发约定端口",[288,18123,18124,18125,18128],{},"仅手工运行 ",[60,18126,18127],{},"npm run dev -- --port 3100"," 时存在",[270,18130,18131,18134,18137],{},[288,18132,18133],{},"3101",[288,18135,18136],{},"Playwright 隔离测试服务",[288,18138,18139],{},"测试自动启动并结束",[270,18141,18142,18145,18148],{},[288,18143,18144],{},"3110",[288,18146,18147],{},"签名 Webhook 接收与事件排队",[288,18149,18113],{},[115,18151,18152,18153,18155,18156,18158],{},"3100 曾经运行一个常驻的 staging 静态服务，双环境简化后它退化为纯粹的本地开发端口；需要热更新时在开发机或 VPS 临时运行 ",[60,18154,18127],{},"，不要让它占据生产心智。3101 使用独立内容 fixture 和 ",[60,18157,16254],{},"，不能拿它当日常预览端口。3110 只监听回环地址，由 Nginx 的精确 location 转发 GitHub 的 POST。",[115,18160,18161],{},"生产 3000 和事件驱动的同步流程可以共存，因为每次构建都通过共享锁串行执行，不会互相踩踏。",[130,18163,18165],{"id":18164},"二十六systemd-用户服务","二十六、systemd 用户服务",[115,18167,18168],{},"当前与网站相关的 unit 有四个：",[619,18170,18173],{"className":18171,"code":18172,"language":624,"meta":625},[622],"personal-site.service                 3000 生产静态服务\npersonal-site-webhook.service         3110 签名 Webhook 接收与事件排队\npersonal-site-sync.service            master 单次同步与部署\npersonal-site-goatcounter.service     8081 自托管统计服务（实验回退后仍在运行）\n",[60,18174,18172],{"__ignoreMap":625},[115,18176,18177,18178,18181],{},"前三个来自仓库的 ",[60,18179,18180],{},"ops\u002Fsystemd\u002F","；第四个是服务器上独立部署的 GoatCounter 进程，不属于网站发布链路。2026 年 8 月 26 日的统计实验回退后，站点不再加载任何统计脚本，这个服务目前只是遗留基础设施。",[115,18183,18184],{},"静态服务的关键设置包括：",[140,18186,18187,18193,18199,18205,18211,18220],{},[143,18188,18189,18192],{},[60,18190,18191],{},"Type=simple","：Node 进程本身就是主进程；",[143,18194,18195,18198],{},[60,18196,18197],{},"After\u002FWants=network-online.target","：网络就绪后启动；",[143,18200,18201,18204],{},[60,18202,18203],{},"Restart=on-failure"," 与 5 秒延迟：异常退出后自动恢复；",[143,18206,18207,18210],{},[60,18208,18209],{},"KillSignal=SIGINT","：与静态服务器的优雅关闭逻辑对应；",[143,18212,18213,195,18216,18219],{},[60,18214,18215],{},"NoNewPrivileges=true",[60,18217,18218],{},"PrivateTmp=true","：缩小运行时权限；",[143,18221,18222,18223,18226],{},"安装到用户级 ",[60,18224,18225],{},"default.target","，不要求整个服务以 root 运行。",[115,18228,18229,18230,18232,18233,18236,18237,1471,18240,18242],{},"Webhook 服务是 ",[60,18231,18191],{}," 常驻轻量进程，只监听回环地址；同步服务是 ",[60,18234,18235],{},"Type=oneshot","，开机不自启，完全由 Webhook 事件触发，并拥有独立的 ",[60,18238,18239],{},"sync-repo",[60,18241,17800],{},"。仓库没有周期性 GitHub timer；有事件时才 fetch，依赖锁变化时才安装依赖，纯内容提交使用快速构建。Webhook 与同步服务的环境覆盖（例如校验的仓库名）通过 systemd drop-in 目录维护。",[115,18244,18245,18246,18249,18250,18253],{},"unit 当前把 Node 可执行文件写成 NVM 下的明确路径。升级 Node 或切换 NVM 版本后，shell 中的 ",[60,18247,18248],{},"node"," 可能已经变化，但 systemd 仍引用旧路径，这是需要主动检查的运维陷阱。修改 unit 或 drop-in 后必须 ",[60,18251,18252],{},"daemon-reload","，而普通文章或页面 release 不需要重启静态服务。",[115,18255,18256,18257,18260,18261,18264],{},"用户级服务要在 VPS 重启且用户未登录时自动启动，需要为运行网站的 Linux 用户启用 linger。管理它时应使用相同用户的 ",[60,18258,18259],{},"systemctl --user","，不应使用 ",[60,18262,18263],{},"sudo systemctl --user"," 去连接 root 的用户总线。",[130,18266,18268],{"id":18267},"二十七测试体系究竟覆盖什么","二十七、测试体系究竟覆盖什么",[115,18270,18271],{},"测试按风险边界分成三组。",[135,18273,18275],{"id":18274},"内容和-writer-单元测试","内容和 writer 单元测试",[115,18277,18278,18281],{},[60,18279,18280],{},"test:content"," 检查：",[140,18283,18284,18287,18290,18293,18296,18301,18304,18307,18310],{},[143,18285,18286],{},"合法文章和四种分类可以通过；",[143,18288,18289],{},"非法日期、分类、文件名、重复标签和空正文会失败；",[143,18291,18292],{},"来源节必须包含公共 HTTP(S) 链接；",[143,18294,18295],{},"CMS 可选 slug 必须与文件名一致；",[143,18297,18298,18300],{},[60,18299,17407],{}," 只能是布尔值，writer 更新时默认保留已有隐藏状态；",[143,18302,18303],{},"站点 YAML 满足 Schema；",[143,18305,18306],{},"writer 拒绝覆盖、穿越、符号链接和错误修订日期；",[143,18308,18309],{},"更新保留发布日期；",[143,18311,18312],{},"删除只接受精确存在的 dated slug。",[135,18314,18316],{"id":18315},"release同步与-webhook-单元测试","release、同步与 Webhook 单元测试",[115,18318,18319,18322],{},[60,18320,18321],{},"test:release"," 在临时目录中检查：",[140,18324,18325,18330,18333,18336,18339,18342,18348,18351,18354],{},[143,18326,18327,18329],{},[60,18328,15960],{}," 能原子切换；",[143,18331,18332],{},"清理历史版本时保留激活 release；",[143,18334,18335],{},"release 名称不能路径穿越；",[143,18337,18338],{},"受管内容路径不会扩大到应用代码；",[143,18340,18341],{},"自动同步失败后不写成功状态，后续重试仍会部署；",[143,18343,18344,18345,18347],{},"Webhook 拒绝无效签名、其他仓库和 ",[60,18346,15914],{}," 之外的分支；",[143,18349,18350],{},"构建期间到达的第二个 push 会保留并追加同步；",[143,18352,18353],{},"纯内容提交走快速构建，代码提交走完整构建；",[143,18355,18356],{},"子命令失败时错误信息保留退出码。",[135,18358,18360],{"id":18359},"playwright-浏览器测试","Playwright 浏览器测试",[115,18362,18363,18366],{},[60,18364,18365],{},"test:e2e"," 在 Chromium 中检查：",[140,18368,18369,18372,18375,18378,18381,18384,18387,18390,18393,18396,18399,18402,18405,18408,18411,18414,18417],{},[143,18370,18371],{},"首页成功水合且没有控制台错误；",[143,18373,18374],{},"动态 favicon、theme-color 和 RGB 输出同步；",[143,18376,18377],{},"自定义主题在纯黑纯白下仍可读；",[143,18379,18380],{},"旧黄色缓存会迁移；",[143,18382,18383],{},"mask 没加载完时字标不偷跑，加载后正常播放；",[143,18385,18386],{},"标签页激活、窗口 focus、换色、滑杆松手和返回主页会重播；",[143,18388,18389],{},"拖动期间不会反复播放；",[143,18391,18392],{},"服务状态、NAS 图标、最近文章、文章详情和更新日期正确；",[143,18394,18395],{},"隐藏文章不出现在公开查询中，原 URL 返回 404；",[143,18397,18398],{},"文章悬浮只影响当前行，长标题宽度不跳变；",[143,18400,18401],{},"从文章返回恢复列表的分类、搜索词和滚动位置，浏览器后退同样恢复，直接进入文章时列表从顶部开始；",[143,18403,18404],{},"文章阅读位置按路径保存并瞬时恢复；",[143,18406,18407],{},"文章标题锚点在允许动画的系统中平滑滚动；",[143,18409,18410],{},"触屏设备有按压反馈且不残留 hover 状态，触摸屏笔记本能正确区分触控与触控板 hover；",[143,18412,18413],{},"移动端返回按钮指向正确目标，“回到顶部”按钮按滚动阈值出现；",[143,18415,18416],{},"手机页面无横向溢出，RGB 在小屏隐藏；",[143,18418,18419],{},"多种桌面尺寸下关键模块互不重叠。",[115,18421,18422],{},"Playwright 配置允许一次重试，等待时间 10 秒，临时 Nuxt 服务启动最长等待 120 秒。响应式几何测试启用 reduced motion，避免入场动画中的瞬时位置影响布局判断。",[115,18424,18425],{},"测试不是越多越好。这里保留的是曾经真实出错、或者一旦回归就很难靠类型系统发现的行为；字体文件数量、每个 CSS 颜色和每一段正文不需要分别写测试。",[130,18427,18429],{"id":18428},"二十八当前性能画像与做过的优化","二十八、当前性能画像与做过的优化",[115,18431,18432],{},"以下数字来自 2026 年 7 月版本的一次本机测量：",[140,18434,18435,18438,18441,18444,18447],{},[143,18436,18437],{},"主 CSS 约 31.1 KB，gzip 后约 6.9 KB；",[143,18439,18440],{},"外部或本地字体文件由 98 个减少到 0；",[143,18442,18443],{},"首页一次传输约 252 KB；",[143,18445,18446],{},"模拟 4G 和 4 倍 CPU 降速时，LCP 约 0.75 秒，完整 load 约 1.37 秒；",[143,18448,18449],{},"CLS 为 0。",[115,18451,18452,18453,18456],{},"VPS 空闲状态下，生产静态服务与 Webhook 接收器等常驻 Node 进程合计约几十 MB；统计实验遗留的 GoatCounter 进程也很轻。完整 Nuxt + Playwright 部署的峰值接近 1 GB，但同步构建通过共享锁串行执行；内容快速构建不启动 Playwright。磁盘主要消耗在工作仓库、同步仓库和各自的 ",[60,18454,18455],{},"node_modules"," 上；这是用磁盘换取工作区、缓存和发布故障互不污染。",[115,18458,18459],{},"这些数字是环境相关快照，不是永久承诺。浏览器缓存、网络、CPU、文章数量和依赖升级都会改变结果。",[115,18461,18462],{},"当前主要性能措施包括：",[140,18464,18465,18468,18471,18474,18481,18486,18489,18492,18495,18498,18501],{},[143,18466,18467],{},"静态预渲染，HTML 首次响应包含页面内容；",[143,18469,18470],{},"移除大批字体资源，改用系统字体；",[143,18472,18473],{},"只预加载首屏真正依赖的 30 KB 字标 mask；",[143,18475,18476,18477,18480],{},"字标等待 ",[60,18478,18479],{},"decode()","，避免资源未准备好时动画丢失；",[143,18482,18483,18485],{},[60,18484,16653],{}," 合并高频指针更新；",[143,18487,18488],{},"缓存元素边界，避免每次 pointermove 读取布局；",[143,18490,18491],{},"连续调色只改颜色，松手才重播动画；",[143,18493,18494],{},"用 transform\u002Fopacity 处理大多数动效；",[143,18496,18497],{},"生产服务器流式响应并支持 Brotli、gzip、ETag 和长期哈希缓存；",[143,18499,18500],{},"固定组件尺寸，避免动态文本造成布局偏移；",[143,18502,18503],{},"分离本地开发、生产同步和 E2E 的工作目录与 Nuxt Content 数据库。",[115,18505,18506,18508],{},[60,18507,15956],{}," 整体约 5.4 MB，其中包括 Nuxt Content 的客户端 SQLite\u002FWASM 相关资源；这不等于每个访客首屏都会下载整个目录。分析性能时应看浏览器实际请求和传输体积，不应把产物目录大小直接当作首页流量。",[130,18510,18512],{"id":18511},"二十九为什么首次交互偶尔会过一会儿才正常","二十九、为什么首次交互偶尔会“过一会儿才正常”",[115,18514,18515],{},"这个问题曾同时表现为字标不播放和 RGB 滑杆暂时失灵，根因方向不是滑杆 CSS 本身，而是客户端水合尚未完成或 Nuxt Dev 仍在首次编译。静态 HTML 可以先显示，但 Vue 事件只有在 JavaScript 加载、执行并成功水合后才生效。",[115,18517,18518,18519,18522],{},"判断方法是观察 ",[60,18520,18521],{},".site-shell[data-hydrated=\"true\"]","。如果页面已经显示但该属性迟迟没有变成 true，应检查：",[140,18524,18525,18528,18531,18534,18537],{},[143,18526,18527],{},"浏览器控制台是否有 JavaScript 错误；",[143,18529,18530],{},"本地 Nuxt Dev 是否还在首次编译；",[143,18532,18533],{},"本地内容 SQLite 缓存是否损坏；",[143,18535,18536],{},"大型依赖或资源是否阻塞客户端入口；",[143,18538,18539],{},"当前访问的是临时 Nuxt Dev，还是已经生成的 3000 静态站。",[115,18541,18542],{},"目前通过移除字体、减轻入口资源、预加载 mask、分离内容数据库和明确动画水合闸门改善了这个过程。所有常驻端口提供的都是静态 release，不存在边访问边编译的线上环境。",[130,18544,18546],{"id":18545},"三十常见修改应该改哪里","三十、常见修改应该改哪里",[135,18548,18550],{"id":18549},"修改个人介绍栏目文案或服务","修改个人介绍、栏目文案或服务",[115,18552,18553,18554,18556,18557,18559],{},"优先通过 CMS 中对应的页面入口，或编辑 ",[60,18555,16183],{}," 下对应 YAML；服务列表位于 ",[60,18558,16381],{},"。若新增服务状态或图标类型，还要同步修改 Schema、配置枚举、CMS 选项、组件映射、CSS 和测试。",[135,18561,18562],{"id":18562},"修改默认颜色",[115,18564,18565,18566,18568,18569,18571],{},"修改 ",[60,18567,16765],{},"。它可以是任意 ",[60,18570,16428],{},"。已有访客若保存过自定义颜色，会继续看到自己的选择；要做全体迁移，需要像旧黄色迁移一样明确识别并处理旧值，不能无条件清空 localStorage。",[135,18573,18574],{"id":18574},"修改红绿蓝预设",[115,18576,18565,18577,5653,18579,18582],{},[60,18578,16705],{},[60,18580,18581],{},"accentPresets","。RGB 面板和非主页调色按钮都复用它。修改后应测试纯黑纯白对比、localStorage 恢复和字标重播。",[135,18584,18585],{"id":18585},"修改艺术字",[115,18587,18588,18589,5653,18591,18593,18594,18596,18597,18599,18600,863],{},"在 CMS 中打开“站点设置 \u002F 首页”，修改“艺术字文字”即可。它最终写入 ",[60,18590,16361],{},[60,18592,16974],{},"，保存到 ",[60,18595,15914],{}," 后由 Webhook 自动重新构建上线。字形由 ",[60,18598,16200],{}," 引入，字体栈、字号和四层颜色位于 ",[60,18601,18602],{},"main.css",[135,18604,18605],{"id":18605},"修改字标动画",[115,18607,18608,18609,18611,18612,18615,18616,18618],{},"播放条件在 ",[60,18610,17021],{},"，跨组件触发在 ",[60,18613,18614],{},"useWordmarkAnimation.ts","，视觉关键帧在 ",[60,18617,18602],{},"。不要把三者混成一个大组件：字体状态、事件状态和视觉时间线是不同责任。",[135,18620,18621],{"id":18621},"修改文章分类",[115,18623,18624,18625,18628],{},"至少同步 ",[60,18626,18627],{},"app\u002Fconfig\u002Farticles.ts","、Nuxt Content Schema、CMS 选项、校验器测试与界面测试。文章中的旧分类是否迁移，需要单独决定。",[135,18630,18631],{"id":18631},"修改文章样式",[115,18633,18634,18635,18638,18639,18641,18642,18645],{},"文章列表看 ",[60,18636,18637],{},".article-row","，正文看 ",[60,18640,17353],{},"（表格边框也在这里），详情头部看 ",[60,18643,18644],{},".article-hero","。长标题、手机宽度、hover 前后几何和其他文章 opacity 是必须复查的回归点。",[135,18647,18648],{"id":18648},"修改全局返回与回顶按钮",[115,18650,18651,18652,18654,18655,18657],{},"返回目标的判断和阈值在 ",[60,18653,16858],{},"，视觉与过渡在 ",[60,18656,18602],{},"。修改后要同时覆盖文章页、普通内页和移动端三类场景。",[135,18659,18660],{"id":18660},"修改生产服务",[115,18662,18663,18664,18666],{},"静态响应逻辑改 ",[60,18665,17948],{},"；监听端口、release 根目录、Node 路径和重启策略改对应 systemd unit。同步仓库、代理和部署锁改 sync 脚本与 drop-in 环境变量。只有静态服务器或 unit 变化需要重启\u002F重载，普通 release 切换不需要。",[130,18668,18670],{"id":18669},"三十一日常发布配方","三十一、日常发布配方",[135,18672,18674],{"id":18673},"通过远程-cms-修改内容","通过远程 CMS 修改内容",[4640,18676,18677,18683,18689],{},[143,18678,18679,18680,18682],{},"打开 ",[60,18681,17601],{}," 并用 GitHub 登录。",[143,18684,18685,18686,18688],{},"保存后 CMS 直接向 ",[60,18687,15914],{}," 提交，Webhook 触发同步：纯内容提交走快速构建，几十秒内上线。",[143,18690,18691],{},"检查正式站对应的页面和 URL。",[115,18693,18694],{},"保存和上线之间没有人工晋级步骤，提交前的内容自查是唯一的安全垫。隐藏文章也遵循同一流程。",[135,18696,18697],{"id":18697},"本地受信流程直接发布内容",[619,18699,18701],{"className":5682,"code":18700,"language":5684,"meta":625,"style":625},"npm run content:validate\nnpm run publish\n",[60,18702,18703,18713],{"__ignoreMap":625},[723,18704,18705,18708,18710],{"class":725,"line":726},[723,18706,18707],{"class":5691},"npm",[723,18709,5695],{"class":5394},[723,18711,18712],{"class":5394}," content:validate\n",[723,18714,18715,18717,18719],{"class":725,"line":732},[723,18716,18707],{"class":5691},[723,18718,5695],{"class":5394},[723,18720,18721],{"class":5394}," publish\n",[115,18723,18724,18726],{},[60,18725,17855],{}," 成功后会输出 commit、release 和 current 路径。它用于 OpenClaw 等本地受限流程，会验证内容、生成静态站、提交并切换本地默认生产目录；把提交推送到 GitHub 后，Webhook 负责线上部署。若工作区混入 Vue、CSS、脚本、CMS 配置或文档改动，它会拒绝执行。",[135,18728,18730],{"id":18729},"发布-vuecss脚本或配置代码","发布 Vue、CSS、脚本或配置代码",[619,18732,18734],{"className":5682,"code":18733,"language":5684,"meta":625,"style":625},"npm run verify\ngit diff --check\n# 明确检查并提交本次文件\ngit push origin master\n",[60,18735,18736,18745,18756,18762],{"__ignoreMap":625},[723,18737,18738,18740,18742],{"class":725,"line":726},[723,18739,18707],{"class":5691},[723,18741,5695],{"class":5394},[723,18743,18744],{"class":5394}," verify\n",[723,18746,18747,18750,18753],{"class":725,"line":732},[723,18748,18749],{"class":5691},"git",[723,18751,18752],{"class":5394}," diff",[723,18754,18755],{"class":5387}," --check\n",[723,18757,18758],{"class":725,"line":34},[723,18759,18761],{"class":18760},"sJ8bj","# 明确检查并提交本次文件\n",[723,18763,18764,18766,18769,18772],{"class":725,"line":1068},[723,18765,18749],{"class":5691},[723,18767,18768],{"class":5394}," push",[723,18770,18771],{"class":5394}," origin",[723,18773,18774],{"class":5394}," master\n",[115,18776,18777,18780,18781,18783,18784,863],{},[60,18778,18779],{},"verify"," 会依次运行内容校验、类型检查、单元测试、静态生成和 E2E；master Webhook 之后触发同步器执行完整 ",[60,18782,17738],{},"。需要在 VPS 立即部署当前干净提交时，也可以手工运行 ",[60,18785,17738],{},[135,18787,18788],{"id":18788},"查看当前生产状态",[619,18790,18792],{"className":5682,"code":18791,"language":5684,"meta":625,"style":625},"systemctl --user is-active personal-site.service\nsystemctl --user is-active personal-site-webhook.service\nreadlink -f ~\u002F.local\u002Fshare\u002Fpersonal-site\u002Fcurrent\ncat ~\u002F.local\u002Fstate\u002Fpersonal-site-sync\u002Fdeployed-commit\ncurl -fsS -o \u002Fdev\u002Fnull -w '%{http_code} %{content_type}\\n' http:\u002F\u002F127.0.0.1:3000\u002F\n",[60,18793,18794,18808,18819,18830,18838],{"__ignoreMap":625},[723,18795,18796,18799,18802,18805],{"class":725,"line":726},[723,18797,18798],{"class":5691},"systemctl",[723,18800,18801],{"class":5387}," --user",[723,18803,18804],{"class":5394}," is-active",[723,18806,18807],{"class":5394}," personal-site.service\n",[723,18809,18810,18812,18814,18816],{"class":725,"line":732},[723,18811,18798],{"class":5691},[723,18813,18801],{"class":5387},[723,18815,18804],{"class":5394},[723,18817,18818],{"class":5394}," personal-site-webhook.service\n",[723,18820,18821,18824,18827],{"class":725,"line":34},[723,18822,18823],{"class":5691},"readlink",[723,18825,18826],{"class":5387}," -f",[723,18828,18829],{"class":5394}," ~\u002F.local\u002Fshare\u002Fpersonal-site\u002Fcurrent\n",[723,18831,18832,18835],{"class":725,"line":1068},[723,18833,18834],{"class":5691},"cat",[723,18836,18837],{"class":5394}," ~\u002F.local\u002Fstate\u002Fpersonal-site-sync\u002Fdeployed-commit\n",[723,18839,18840,18843,18846,18849,18852,18855,18858],{"class":725,"line":1074},[723,18841,18842],{"class":5691},"curl",[723,18844,18845],{"class":5387}," -fsS",[723,18847,18848],{"class":5387}," -o",[723,18850,18851],{"class":5394}," \u002Fdev\u002Fnull",[723,18853,18854],{"class":5387}," -w",[723,18856,18857],{"class":5394}," '%{http_code} %{content_type}\\n'",[723,18859,18860],{"class":5394}," http:\u002F\u002F127.0.0.1:3000\u002F\n",[135,18862,18863],{"id":18863},"回滚",[115,18865,18866],{},"先列出确实存在的 release，再执行：",[619,18868,18870],{"className":5682,"code":18869,"language":5684,"meta":625,"style":625},"npm run rollback -- \u003Cexact-release-id>\n",[60,18871,18872],{"__ignoreMap":625},[723,18873,18874,18876,18878,18881,18884,18887,18890,18893],{"class":725,"line":726},[723,18875,18707],{"class":5691},[723,18877,5695],{"class":5394},[723,18879,18880],{"class":5394}," rollback",[723,18882,18883],{"class":5387}," --",[723,18885,18886],{"class":16718}," \u003C",[723,18888,18889],{"class":5394},"exact-release-i",[723,18891,18892],{"class":5381},"d",[723,18894,16565],{"class":16718},[115,18896,18897,18898,18900,18901,18903,18904,863],{},"回滚后再次检查 ",[60,18899,15960],{}," 和 3000 返回值。不要手动删除 ",[60,18902,15960],{},"，也不要把它指向仓库的 ",[60,18905,15956],{},[130,18907,18909],{"id":18908},"三十二故障排查顺序","三十二、故障排查顺序",[135,18911,18913],{"id":18912},"cms-保存后正式站没变化","CMS 保存后正式站没变化",[115,18915,18916,18917,18919,18920,18922,18923,195,18926,18929,18930,18933,18934,18936],{},"先确认提交是否进入 GitHub ",[60,18918,15914],{},"，并在 GitHub Recent Deliveries 中确认 Webhook 返回 ",[60,18921,17843],{},"；再检查 ",[60,18924,18925],{},"personal-site-webhook.service",[60,18927,18928],{},"personal-site-sync.service"," 日志、",[60,18931,18932],{},"~\u002F.local\u002Fstate\u002Fpersonal-site-sync\u002Fdeployed-commit"," 和生产 ",[60,18935,15960],{},"。构建失败不会覆盖旧 release；事件文件会保留并延迟重试，也可以修复后手工启动同步服务。",[135,18938,18940],{"id":18939},"rgb-不能拖按钮不能点动画不播放","RGB 不能拖、按钮不能点、动画不播放",[115,18942,18943,18944,18947,18948,18951],{},"先看 ",[60,18945,18946],{},"data-hydrated","，再看控制台错误和网络请求。若只发生在临时 Nuxt Dev 的第一次编译，等待完成后重载；若持续发生，停止那个临时开发进程，运行 ",[60,18949,18950],{},"npx nuxt cleanup"," 后重启开发端口。常驻 3000 是静态服务器，不使用这份开发缓存。",[135,18953,18954],{"id":18954},"切回标签页没有动画",[115,18956,18957,18958,195,18960,18962],{},"检查浏览器是否启用 reduced motion、mask 图片是否加载失败，以及 ",[60,18959,17107],{},[60,18961,17110],{}," 是否触发。一次切回只播一次是正常防抖，不应去掉 200ms 合并后让三个事件连续触发。",[135,18964,18965],{"id":18965},"换色先闪回旧颜色",[115,18967,18968,18969,18971],{},"检查静态默认色、Nuxt ",[60,18970,16769],{}," 初始值和 localStorage 恢复顺序。服务端 HTML 无法知道客户端保存颜色，完全避免首帧差异需要在页面渲染前执行极小的内联主题恢复脚本；当前实现选择保持静态生成简单，在水合时恢复。",[135,18973,18975],{"id":18974},"服务状态点不随-cms-变化","服务状态点不随 CMS 变化",[115,18977,18978,18979,18982,18983,18985],{},"确认 YAML 中 ",[60,18980,18981],{},"state"," 已保存、值属于三个枚举。CMS 保存后需要等同步构建完成才会看到变化；若 DOM 属性已变而颜色没变，检查 CSS 状态选择器；若属性没变，检查 ",[60,18984,15960],{}," 是否仍是旧 release。",[135,18987,18989],{"id":18988},"_3000-无法访问","3000 无法访问",[115,18991,18992,18993,18995,18996,18998],{},"检查用户级 systemd 状态、日志、Node 固定路径、端口占用和 ",[60,18994,15960],{}," 链接。服务器进程存在但 ",[60,18997,15960],{}," 失效时会返回 503。不要在未确认进程归属前直接杀端口进程。真正常驻的只有 3000 和 3110；3100、3101 都按需临时启动，不为它们单独排障。",[135,19000,19002],{"id":19001},"管理页面提示只允许-https","管理页面提示只允许 HTTPS",[115,19004,19005,19006,19008],{},"这通常来自浏览器安全上下文要求，而不是 Git remote 配错。Git remote 只决定仓库同步地址，不会给 HTTP 页面增加 HTTPS。远程后台应使用已经配置可信证书的 ",[60,19007,17601],{},"；本地仓库模式则从 localhost 打开。",[130,19010,19012],{"id":19011},"三十三安全与备份边界","三十三、安全与备份边界",[115,19014,19015],{},"至少要区分三类数据：",[619,19017,19020],{"className":19018,"code":19019,"language":624,"meta":625},[622],"Git 仓库       源码、文章、站点设置、公开静态资源\nOpenClaw 目录  Skill、草稿、会话和本地配置\nrelease 目录   生产当前及历史静态构建结果\n",[60,19021,19019],{"__ignoreMap":625},[115,19023,19024],{},"Git 提交是版本历史，不是异地备份。私人远程仓库可以备份源码与文章，但 OpenClaw 配置和 release 数据需要单独备份到受控存储。",[115,19026,19027,19028,19030],{},"永远不要提交或写入公开文章的内容包括：API key、OAuth secret、部署私钥、QQ token、cookie、扫码状态、",[60,19029,17594],{},"、私人服务密码、内部地址清单和不适合公开的资产信息。",[115,19032,19033,19034,19037,19038,19040],{},"CMS 的 ",[60,19035,19036],{},"\u002Fadmin\u002F"," 路径和 ",[60,19039,17605],{}," 只能减少搜索引擎收录，不能阻止陌生人访问。当前后台没有 Basic Auth，保护来自可信 HTTPS 下的 GitHub OAuth 登录与私人仓库权限；生产上游只监听回环地址，公网 IP 与未知 Host 由默认 Nginx 站点拒绝（HTTP 返回 444，HTTPS 直接拒绝握手）。真正的保护来自这些边界和最小权限凭据，而不是隐藏 URL。",[130,19042,19044],{"id":19043},"三十四维护这套代码时应坚持的原则","三十四、维护这套代码时应坚持的原则",[115,19046,19047],{},"第一，内容、表现和发布边界分开。能在 YAML 解决的文案不要硬编码进 Vue；能由共享组件表达的状态不要在两个页面复制；生产切换不要混进页面代码。",[115,19049,19050],{},"第二，把浏览器状态当成显式状态。主题色、资源是否解码、页面是否可见、指针是否仍按下、水合是否完成、列表与阅读位置是否待恢复，都应有清楚的变量和生命周期，而不是靠延时猜测。",[115,19052,19053],{},"第三，动画必须有静态最终状态。资源失败、JavaScript 失败或用户减少动态效果时，标题仍要可读，布局仍要成立。",[115,19055,19056],{},"第四，自动化写入必须比人工编辑权限更窄。Skill 负责工作流，writer 负责硬边界，validator 负责内容合约，release 负责上线，四者不能互相冒充。",[115,19058,19059],{},"第五，生产部署应该是“生成一个完整新版本，然后切换”，而不是在访问者正在读取的目录里逐个覆盖文件。原子 release 是这个小网站最值得长期保留的工程设计之一。",[115,19061,19062,19063,19065],{},"第六，发布路径必须单一且有校验。CMS 和本地流程都汇入 ",[60,19064,15914],{},"，由同一个签名 Webhook 部署；内容快速构建与代码完整构建的差别由脚本判定，而不是由人临场决定。环境越简单，越要靠校验和原子切换守住质量。",[115,19067,19068],{},"第七，测试真实的关系和历史 bug。主题对比、滑杆松手、标签页重播、长标题 hover、隐藏文章 404、列表与阅读位置恢复、锚点平滑滚动、服务状态同步和多视口不重叠，都来自实际使用中的问题，比为了提高数字而堆大量脆弱测试更有价值。",[130,19070,19072],{"id":19071},"三十五重新上手时的最短阅读路径","三十五、重新上手时的最短阅读路径",[115,19074,19075],{},"如果未来很久没有维护这个项目，按下面顺序读，可以最快恢复上下文：",[4640,19077,19078,19084,19092,19101,19111,19116,19128,19135],{},[143,19079,19080,19081,19083],{},"看 ",[60,19082,16183],{}," 的五份 YAML，知道全站和各页面当前展示什么。",[143,19085,19080,19086,4814,19089,19091],{},[60,19087,19088],{},"app\u002Fpages\u002Findex.vue",[60,19090,18602],{}," 的主页部分，理解首屏结构。",[143,19093,19080,19094,195,19096,195,19098,19100],{},[60,19095,16705],{},[60,19097,16884],{},[60,19099,17021],{},"，理解最复杂的交互。",[143,19102,19103,19104,195,19107,19110],{},"看两个 writing 页面和 ",[60,19105,19106],{},"useWritingListState.ts",[60,19108,19109],{},"useArticleReadingState.ts","，理解文章查询、渲染与状态恢复。",[143,19112,19080,19113,19115],{},[60,19114,16419],{},"、validator 和 writer，理解内容边界。",[143,19117,19080,19118,195,19121,195,19124,19127],{},[60,19119,19120],{},"release.ts",[60,19122,19123],{},"sync.ts",[60,19125,19126],{},"webhook.ts"," 和 systemd unit，理解 master 直发的自动部署链路。",[143,19129,19130,19131,19134],{},"看 Nginx 模板与 ",[60,19132,19133],{},"docs\u002F"," 运维文档，理解域名、HTTPS 和默认拒绝站点。",[143,19136,19137],{},"最后看测试，把“哪些行为不能再坏”快速过一遍。",[115,19139,19140],{},"只要能重新回答下面六个问题，就已经掌握了这个项目：",[140,19142,19143,19146,19149,19152,19155,19158],{},[143,19144,19145],{},"当前页面内容的唯一来源在哪里？",[143,19147,19148],{},"浏览器中的主题色如何从 RGB 状态传到 CSS、字标和 favicon？",[143,19150,19151],{},"CMS 保存之后，一次生产 release 是如何自动发生的？",[143,19153,19154],{},"publish、deploy、deploy:content 和 sync 的权限、检查与 Git 行为有什么不同？",[143,19156,19157],{},"一个新 release 如何在不重启 3000 的情况下接管生产请求？",[143,19159,19160],{},"文章列表状态和阅读位置是如何在会话内恢复的？",[115,19162,19163],{},"这些问题就是 Quilibra 当前技术架构的骨架。",[130,19165,741],{"id":741},[140,19167,19168,19175,19182,19189,19196,19203,19210,19217],{},[143,19169,19170],{},[119,19171,19174],{"href":19172,"rel":19173},"https:\u002F\u002Fnuxt.com\u002Fdocs",[750],"Nuxt 文档",[143,19176,19177],{},[119,19178,19181],{"href":19179,"rel":19180},"https:\u002F\u002Fcontent.nuxt.com\u002F",[750],"Nuxt Content 文档",[143,19183,19184],{},[119,19185,19188],{"href":19186,"rel":19187},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fextras\u002Fcomposition-api-faq.html",[750],"Vue Composition API",[143,19190,19191],{},[119,19192,19195],{"href":19193,"rel":19194},"https:\u002F\u002Fplaywright.dev\u002Fdocs\u002Fintro",[750],"Playwright 文档",[143,19197,19198],{},[119,19199,19202],{"href":19200,"rel":19201},"https:\u002F\u002Fgithub.com\u002Fsveltia\u002Fsveltia-cms",[750],"Sveltia CMS 文档",[143,19204,19205],{},[119,19206,19209],{"href":19207,"rel":19208},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FCSS\u002Fmask",[750],"MDN：CSS mask",[143,19211,19212],{},[119,19213,19216],{"href":19214,"rel":19215},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FPage_Visibility_API",[750],"MDN：Page Visibility API",[143,19218,19219],{},[119,19220,19223],{"href":19221,"rel":19222},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FCaching",[750],"MDN：HTTP 缓存",[790,19225,19226],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":625,"searchDepth":732,"depth":732,"links":19228},[19229,19230,19231,19232,19233,19234,19235,19236,19237,19238,19239,19240,19241,19242,19243,19244,19245,19246,19247,19248,19249,19250,19257,19258,19259,19260,19261,19266,19267,19268,19279,19286,19295,19296,19297,19298],{"id":15924,"depth":732,"text":15925},{"id":15967,"depth":732,"text":15968},{"id":16000,"depth":732,"text":16001},{"id":16132,"depth":732,"text":16133},{"id":16194,"depth":732,"text":16195},{"id":16327,"depth":732,"text":16328},{"id":16471,"depth":732,"text":16472},{"id":16589,"depth":732,"text":16590},{"id":16660,"depth":732,"text":16661},{"id":16698,"depth":732,"text":16699},{"id":16817,"depth":732,"text":16818},{"id":16878,"depth":732,"text":16879},{"id":16963,"depth":732,"text":16964},{"id":17060,"depth":732,"text":17061},{"id":17134,"depth":732,"text":17135},{"id":17205,"depth":732,"text":17206},{"id":17281,"depth":732,"text":17282},{"id":17357,"depth":732,"text":17358},{"id":17527,"depth":732,"text":17528},{"id":17612,"depth":732,"text":17613},{"id":17671,"depth":732,"text":17672},{"id":17710,"depth":732,"text":17711,"children":19251},[19252,19253,19254,19255,19256],{"id":17717,"depth":34,"text":17720},{"id":17735,"depth":34,"text":17738},{"id":17750,"depth":34,"text":17753},{"id":17762,"depth":34,"text":17765},{"id":17816,"depth":34,"text":17819},{"id":17868,"depth":732,"text":17869},{"id":17942,"depth":732,"text":17943},{"id":18084,"depth":732,"text":18085},{"id":18164,"depth":732,"text":18165},{"id":18267,"depth":732,"text":18268,"children":19262},[19263,19264,19265],{"id":18274,"depth":34,"text":18275},{"id":18315,"depth":34,"text":18316},{"id":18359,"depth":34,"text":18360},{"id":18428,"depth":732,"text":18429},{"id":18511,"depth":732,"text":18512},{"id":18545,"depth":732,"text":18546,"children":19269},[19270,19271,19272,19273,19274,19275,19276,19277,19278],{"id":18549,"depth":34,"text":18550},{"id":18562,"depth":34,"text":18562},{"id":18574,"depth":34,"text":18574},{"id":18585,"depth":34,"text":18585},{"id":18605,"depth":34,"text":18605},{"id":18621,"depth":34,"text":18621},{"id":18631,"depth":34,"text":18631},{"id":18648,"depth":34,"text":18648},{"id":18660,"depth":34,"text":18660},{"id":18669,"depth":732,"text":18670,"children":19280},[19281,19282,19283,19284,19285],{"id":18673,"depth":34,"text":18674},{"id":18697,"depth":34,"text":18697},{"id":18729,"depth":34,"text":18730},{"id":18788,"depth":34,"text":18788},{"id":18863,"depth":34,"text":18863},{"id":18908,"depth":732,"text":18909,"children":19287},[19288,19289,19290,19291,19292,19293,19294],{"id":18912,"depth":34,"text":18913},{"id":18939,"depth":34,"text":18940},{"id":18954,"depth":34,"text":18954},{"id":18965,"depth":34,"text":18965},{"id":18974,"depth":34,"text":18975},{"id":18988,"depth":34,"text":18989},{"id":19001,"depth":34,"text":19002},{"id":19011,"depth":732,"text":19012},{"id":19043,"depth":732,"text":19044},{"id":19071,"depth":732,"text":19072},{"id":741,"depth":732,"text":741},"2026-07-26","记录 Quilibra 个人主页的技术选型、页面架构、内容系统、主题动画、master 单分支自动发布、HTTPS、测试体系和维护方法。",{},"\u002Fwriting\u002F2026-07-26-quilibra-complete-technical-handbook","2026-07-26T23:46:23+08:00",{"title":15894,"description":19300},"quilibra-complete-technical-handbook","writing\u002F2026-07-26-quilibra-complete-technical-handbook",[19308,19309,16039,19310,16087,16079,19311],"Nuxt","Vue","Nuxt Content","静态部署","2026-09-04","HzIhtT-51G4G8strCzY8f_5tUQqCBlSY7hKJqm6uh7o",{"id":19315,"title":19316,"body":19317,"category":14606,"date":19299,"description":19476,"extension":827,"hidden":828,"kind":834,"meta":19477,"navigation":30,"path":19478,"publishedAt":19303,"seo":19479,"series":834,"slug":19480,"stem":19481,"tags":19482,"unit":834,"updated":14824,"__hash__":19487},"writing\u002Fwriting\u002F2026-07-26-trading-cognition-and-compound-reality.md","做交易的动机：从量化门槛到复利现实",{"type":112,"value":19318,"toc":19465},[19319,19322,19326,19329,19332,19336,19339,19342,19345,19348,19351,19354,19357,19361,19364,19384,19387,19407,19410,19413,19427,19430,19433,19436,19439,19442,19446,19449,19452,19456,19459,19462],[115,19320,19321],{},"刷到一个清华博士的视频，说想赚钱就去量化。但这对于普通人的门槛是不是太高？就算跨过去了，市场容量真的够赚到钱吗？带着这些问题和 AI 聊了一圈，从量化门槛一路聊到\"你到底为什么做交易\"，把思路理了一遍。",[130,19323,19325],{"id":19324},"机构量化-vs-系统化交易","机构量化 vs 系统化交易",[115,19327,19328],{},"机构量化（幻方、九坤级别）的门槛确实很高：博士学历、海量 tick 数据、专用算力、上亿起步资金，而且赛道内卷、alpha 衰减快、容量有限。",[115,19330,19331],{},"但普通人能做的是\"系统化交易\"而非机构量化。20 日均线 + 严格止损 + 行业 ETF，这套逻辑本质就是量化思维。真正的问题不是门槛，是把博士生的参照系错误地套用在自己身上。这类建议对清北学生可操作，对大多数人是废话，因为它省略了关键前提：清北学生的起点已经过滤掉 90% 的竞争。",[130,19333,19335],{"id":19334},"只是为了赚钱可能是对自己的贬低","\"只是为了赚钱\"可能是对自己的贬低",[115,19337,19338],{},"我说做交易只是为了赚钱。但仔细想想，我对系统和机制本身一直有探究欲--止损逻辑、去中心化的交易所架构、自己项目的技术选型会追求优雅和性能。真正纯粹为钱的人不会纠结止损后策略逻辑对不对。",[115,19340,19341],{},"如果账户明天清零，还会不会继续研究这套系统？答案可能是会的。因为做交易挣不挣钱是对我认知是否正确的检验，猜对了奖励，猜错了亏钱。我在市场里的目的是验证认知，想知道自己是怎样的人。",[115,19343,19344],{},"核心驱动不是钱（AI说的 我持怀疑态度），是\"认知闭环\"--市场是不会说谎的裁判，钱只是计分板。",[130,19346,19347],{"id":19347},"一个需要警惕的特质",[115,19349,19350],{},"对\"是否正确\"极度敏感的人，容易把坚持系统等同于坚持自我，把止损等同于承认\"我是错的\"。如果哪天开始扛单不是因为信号而是无法接受\"我看错了\"，这个特质就从优势变成风险。",[115,19352,19353],{},"关键区分：把\"验证认知\"和\"验证自我价值\"分开。市场说这笔交易错了，不等于整个判断体系错了。我相信市场大部分时间是对的，应该是我往市场靠去检验自己，而不是市场错的时候坚持自己。但空仓时说这话容易，真正重仓浮亏 5%、系统说止损但脑子里说\"只是短期波动\"的那一刻，这套认知还在不在才是真考验。",[115,19355,19356],{},"止损系统的价值不只是风控，更是强制执行那些理性认同但情绪会动摇的信念。",[130,19358,19360],{"id":19359},"_10-万到几百万数学可行执行淘汰大多数人","10 万到几百万：数学可行，执行淘汰大多数人",[115,19362,19363],{},"市场容量直接排除--A 股日均成交额 8000 亿到 1.2 万亿，几十万到几百万对市场影响约等于零。真实难度分三段：",[140,19365,19366,19372,19378],{},[143,19367,19368,19371],{},[125,19369,19370],{},"10 万到 100 万","：拼策略稳定性和纪律一致性",[143,19373,19374,19377],{},[125,19375,19376],{},"100 万到 500 万","：拼复利需要时间，以及账户变大后心态是否变化（100 万亏 10% 是 10 万，感受和 10 万亏 10% 完全不同）",[143,19379,19380,19383],{},[125,19381,19382],{},"500 万以上","：拼市场风格切换时系统能否不变形",[115,19385,19386],{},"参照现有系统严格执行，年化 15%-30% 的历史区间成立。三种情景的复利推算：",[140,19388,19389,19395,19401],{},[143,19390,19391,19394],{},[125,19392,19393],{},"保守 15%","：10 万到 100 万约 17 年，到 500 万约 39 年",[143,19396,19397,19400],{},[125,19398,19399],{},"中性 20%","：10 万到 100 万约 13 年，到 500 万约 21 年",[143,19402,19403,19406],{},[125,19404,19405],{},"乐观 25%","：10 万到 100 万约 11 年，到 500 万约 15 年",[115,19408,19409],{},"数学上可行，但\"10 年严格执行\"淘汰的人比策略本身多。年化均值背后有巨大的方差，最大风险不是策略本身，而是震荡市连续止损后大多数人会改变规则。",[130,19411,19412],{"id":19412},"人群中能走到哪",[140,19414,19415,19418,19421,19424],{},[143,19416,19417],{},"能长期跑赢通胀：前 20%",[143,19419,19420],{},"年化 15%+ 持续 5 年以上：前 5%",[143,19422,19423],{},"系统化策略 10 年不变形：前 2-3%",[143,19425,19426],{},"10 万做到 500 万：前 1-2%",[115,19428,19429],{},"这个门槛不是天才门槛，而是纪律和时间门槛。输给认知的少，输给自己的多。",[130,19431,19432],{"id":19432},"交易不是生活的全部",[115,19434,19435],{},"正常认真上班拿现金流，有自己的爱好爱好，偶尔玩玩游戏，继续交易探索市场和世界。每个部分各司其职：工作管现金流，运动游戏管切换放松，交易是认知战场。",[115,19437,19438],{},"这个心态本身是优势。很多人把交易当生活全部，盯盘过度、情绪放大、决策变差。有工作提供现金流、有运动游戏切换状态，交易在这个结构里心理压力更小，压力小反而更容易执行系统。",[115,19440,19441],{},"但有一个前提：执行密度。想清楚和做到之间有 gap，关键不是今天想通了，而是明天还在做。",[130,19443,19445],{"id":19444},"一个佩服的-up-主","一个佩服的 UP 主",[115,19447,19448],{},"关注过一个 UP 主，他的操作和认知确实让人佩服：做过信息差套利，也在暴跌日开过期权多单护盘，事后看判断也大多是对的。",[115,19450,19451],{},"他的宏观判断和产业理解思路值得学，具体操作和套利思路也有参考价值。没有实盘记录这点确实无法验证，但判断以及操作确实很牛逼。",[130,19453,19455],{"id":19454},"a-股打新现状","A 股打新现状",[115,19457,19458],{},"查了一下 A 股打新的数据：2024 年新股首日平均涨幅 253%，单签平均收益约 2 万元；2025 年至 6 月，45 只新股无一破发。但主板网上中签率仅 0.03% 左右，参与人数超 1100 万户，普通人中签概率极低，更像是彩票。网下机构打新年化约 4.5%，但门槛是亿级资金账户。包括今年以来的一些大肉签（比如沐曦股份 摩尔线程 最近的长鑫存储）上市当日涨幅都很夸张，随便中几签都是几万块。可惜科创板门槛高啊。（我什么时候才能有50w）",[130,19460,19461],{"id":19461},"止损永远是最重要的",[115,19463,19464],{},"按照均线止损太死板，套到很多个股上买点太高了。但不管用什么方式入场，止损永远是第一位的。买点可以慢慢校准，进场理由可以有很多种，但止损必须进场前定好，而且不能因为浮亏就移动它。止损一旦守不住，什么策略都白搭。在市场里还是先活下来比较重要。",{"title":625,"searchDepth":732,"depth":732,"links":19466},[19467,19468,19469,19470,19471,19472,19473,19474,19475],{"id":19324,"depth":732,"text":19325},{"id":19334,"depth":732,"text":19335},{"id":19347,"depth":732,"text":19347},{"id":19359,"depth":732,"text":19360},{"id":19412,"depth":732,"text":19412},{"id":19432,"depth":732,"text":19432},{"id":19444,"depth":732,"text":19445},{"id":19454,"depth":732,"text":19455},{"id":19461,"depth":732,"text":19461},"从清华博士的建议想要赚钱就去做量化出发，梳理普通人做系统化交易的现实路径、人群比例和自我认知驱动。",{},"\u002Fwriting\u002F2026-07-26-trading-cognition-and-compound-reality",{"title":19316,"description":19476},"trading-cognition-and-compound-reality","writing\u002F2026-07-26-trading-cognition-and-compound-reality",[14606,19483,19484,19485,19486],"量化","认知","复利","系统化交易","kkeAPkWMCa4zdSvwkAlnHZlUbvyzQ-z5pFmE31fCl0o",{"id":19489,"title":19490,"body":19491,"category":14606,"date":19299,"description":19672,"extension":827,"hidden":828,"kind":834,"meta":19673,"navigation":30,"path":19674,"publishedAt":19675,"seo":19676,"series":834,"slug":19677,"stem":19678,"tags":19679,"unit":834,"updated":14824,"__hash__":19686},"writing\u002Fwriting\u002F2026-07-26-btc-synthetic-futures-arbitrage.md","BTC期权合成期货套利：定价偏差与跨平台对冲",{"type":112,"value":19492,"toc":19663},[19493,19496,19500,19503,19506,19510,19516,19524,19527,19530,19533,19539,19545,19548,19552,19555,19558,19562,19569,19575,19578,19581,19595,19600,19608,19614,19619,19633,19638,19650,19653,19657,19660],[115,19494,19495],{},"看到一套量化交易系统看板：年化 197%、极低回撤、多平仓少持仓。核心策略是利用 Deribit 期权市场合成的虚拟期货价格与 Binance 永续合约价格之间的定价偏差进行对冲套利。当时对着看板追问了 AI 几轮，才把这套合成期货套利的逻辑理清楚。这篇文章是给自己留的复盘笔记。",[130,19497,19499],{"id":19498},"deribit-是什么","Deribit 是什么",[115,19501,19502],{},"Deribit 是目前加密货币期权交易量最大的交易所，总部在巴拿马，2016 年上线。BTC 和 ETH 期权的主流流动性都在这里，行权价、到期日覆盖范围广，机构参与度高。它的期权以币本位结算（BTC 期权用 BTC 结算），也支持永续合约和交割期货。",[115,19504,19505],{},"正因为 Deribit 在期权市场的深度优势，它的期权定价成为合成期货策略的基础端。另一端选 Binance 永续合约，是因为 Binance 流动性最好、API 稳定、支持开空，适合做对冲腿。",[130,19507,19509],{"id":19508},"核心概念合成期货","核心概念：合成期货",[115,19511,19512,19513,863],{},"期权平价公式告诉我们：",[125,19514,19515],{},"买入 Call + 卖出 Put = 合成期货多头",[140,19517,19518,19521],{},[143,19519,19520],{},"买入 Call（看涨期权）：涨了赚，跌了不亏",[143,19522,19523],{},"卖出 Put（看跌期权）：涨了没事，跌了亏",[115,19525,19526],{},"两者组合后的盈亏轨迹，与直接持有 BTC 现货或期货完全一致——涨了赚、跌了亏。这就是\"合成期货\"。",[130,19528,19529],{"id":19529},"两种套利方向",[115,19531,19532],{},"关键在于：Deribit 期权合成的期货价格，和 Binance 永续合约的价格，经常不一致。",[115,19534,19535,19538],{},[125,19536,19537],{},"转换套利（Conversion \u002F SF+BS）","：当合成期货价格 > Binance 期货价格时，在 Deribit 卖出合成期货（卖出 Call + 买入 Put），同时在 Binance 买入永续合约。",[115,19540,19541,19544],{},[125,19542,19543],{},"反转套利（Reversal \u002F BF+SS）","：当合成期货价格 \u003C Binance 期货价格时，在 Deribit 买入合成期货（买入 Call + 卖出 Put），同时在 Binance 卖出永续合约。",[115,19546,19547],{},"本质上就是\"哪边便宜哪边买，哪边贵了哪边卖\"。",[130,19549,19551],{"id":19550},"对冲机制为什么涨跌都不怕","对冲机制：为什么涨跌都不怕",[115,19553,19554],{},"币安端永远做与 Deribit 完全相反的动作。Deribit 开合成多头，Binance 必开永续空头。BTC 暴涨暴跌时，两端的盈亏完全抵消，行情风险归零。",[115,19556,19557],{},"利润不来自方向判断，而是开仓瞬间两端的定价差。期权到期结算时，合成价格强制回归现货价，基差收敛完成无风险落袋。",[130,19559,19561],{"id":19560},"一个关键区分行权价-现价","一个关键区分：行权价 ≠ 现价",[115,19563,19564,19565,19568],{},"看板中的合约代码 ",[60,19566,19567],{},"BTC-10JUN26-62000","，62000 是行权价，不是当时的市场现价。当时 Binance 的标记价约为 62,991 美元。因为现价高于行权价，看涨期权呈浮盈状态，看跌期权的权利金已被吃满。",[115,19570,19571,19572,863],{},"比对套利机会时，标的不是行权价本身，而是",[125,19573,19574],{},"合成总成本 = 行权价 + 买Call权利金 - 卖Put权利金",[130,19576,19577],{"id":19577},"完整数值演练",[115,19579,19580],{},"设定场景：",[140,19582,19583,19586,19589],{},[143,19584,19585],{},"Binance 现价：$63,000",[143,19587,19588],{},"Deribit 行权价 $62,000 的期权：Call 权利金 $1,500，Put 权利金 $300",[143,19590,19591,19592],{},"合成空头总成本：$62,000 + $1,500 - $300 = ",[125,19593,19594],{},"$63,200",[115,19596,19597],{},[125,19598,19599],{},"开仓动作：",[140,19601,19602,19605],{},[143,19603,19604],{},"Binance 开多，$63,000",[143,19606,19607],{},"Deribit 合成空头（卖 Call + 买 Put），$63,200",[115,19609,19610,19611],{},"开仓瞬间锁定利润：$63,200 - $63,000 = ",[125,19612,19613],{},"$200",[115,19615,19616],{},[125,19617,19618],{},"极端行情 A - 暴涨至 $80,000：",[140,19620,19621,19624,19627],{},[143,19622,19623],{},"Binance 多头盈利：+$17,000",[143,19625,19626],{},"Deribit 合成空头亏损：-$16,800",[143,19628,19629,19630],{},"合计：",[125,19631,19632],{},"+$200",[115,19634,19635],{},[125,19636,19637],{},"极端行情 B - 暴跌至 $40,000：",[140,19639,19640,19643,19646],{},[143,19641,19642],{},"Binance 多头亏损：-$23,000",[143,19644,19645],{},"Deribit 合成空头盈利：+$23,200",[143,19647,19629,19648],{},[125,19649,19632],{},[115,19651,19652],{},"无论涨跌，总利润始终为开仓时锁定的 $200。到期自动结算收敛，整个过程是左手倒右手，利润在开仓那一下就已经确定了。",[130,19654,19656],{"id":19655},"模型不难难的是工程","模型不难，难的是工程",[115,19658,19659],{},"这套套利的交易系统作者说过一句话：套利模型倒是不难，难的是一些工程上的事情。",[115,19661,19662],{},"确实，逻辑上这就是一个低买高卖的价差捕捉，但实际跑起来要处理的问题远比模型复杂：两个交易所的 API 延迟差异、订单簿深度和滑点、手续费摩擦对利润空间的侵蚀、资金在两个平台之间的划转效率、期权到期时的自动结算对齐、极端行情下的风控（硬止损、Gamma 检查、行情降级）、以及资金容量上限——能套的价差就那么多，资金量大了反而吃不掉。这些工程细节才是决定系统能不能真正赚钱的关键。",{"title":625,"searchDepth":732,"depth":732,"links":19664},[19665,19666,19667,19668,19669,19670,19671],{"id":19498,"depth":732,"text":19499},{"id":19508,"depth":732,"text":19509},{"id":19529,"depth":732,"text":19529},{"id":19550,"depth":732,"text":19551},{"id":19560,"depth":732,"text":19561},{"id":19577,"depth":732,"text":19577},{"id":19655,"depth":732,"text":19656},"看到一套量化套利系统后追问AI，拆解Deribit合成期货与Binance永续合约之间的跨平台套利逻辑，含完整数值演练。",{},"\u002Fwriting\u002F2026-07-26-btc-synthetic-futures-arbitrage","2026-07-26T13:37:11+08:00",{"title":19490,"description":19672},"btc-synthetic-futures-arbitrage","writing\u002F2026-07-26-btc-synthetic-futures-arbitrage",[19680,19681,19682,19683,19684,19685],"BTC","期权","套利","合成期货","Deribit","Binance","gIKPEV_QCVSuhIUCasGE6y3oCH3B09doV6BtMezwe8E",{"id":19688,"title":19689,"body":19690,"category":824,"date":19299,"description":19855,"extension":827,"hidden":828,"kind":834,"meta":19856,"navigation":30,"path":19857,"publishedAt":19858,"seo":19859,"series":834,"slug":19860,"stem":19861,"tags":19862,"unit":834,"updated":14824,"__hash__":19865},"writing\u002Fwriting\u002F2026-07-26-why-vector-database.md","向量库的用途与选型",{"type":112,"value":19691,"toc":19848},[19692,19696,19699,19710,19713,19727,19731,19734,19748,19751,19755,19758,19761,19781,19788,19791,19794,19797,19815,19818,19821,19845],[130,19693,19695],{"id":19694},"sql-存个相同列-join-一下不就行了","SQL 存个相同列 join 一下不就行了？",[115,19697,19698],{},"这是我最初的问题。看起来很合理：embedding 不就是一串数字吗，存个字段，SQL join 一下就完事了。",[115,19700,19701,19702,19705,19706,19709],{},"但 SQL 擅长的是 ",[60,19703,19704],{},"WHERE A = B"," 这种精确匹配。Embedding 是高维浮点数组，代表语义特征。两句话语义相似，它们的向量数值却绝不相等。",[60,19707,19708],{},"WHERE Va = Vb"," 直接返回空。",[115,19711,19712],{},"所以向量库真正在做的事情有两层：",[4640,19714,19715,19721],{},[143,19716,19717,19720],{},[125,19718,19719],{},"数学计算","：用余弦相似度等算法算高维空间的\"距离\"，而不是判等。",[143,19722,19723,19726],{},[125,19724,19725],{},"专属索引（ANN 算法）","：传统 B-Tree 索引对高维数据失效，会退化成全表扫描。向量库用 HNSW 之类的索引在高维空间建\"地图\"，做到毫秒级近似最近邻检索。",[130,19728,19730],{"id":19729},"那-rag-到底是怎么跑的","那 RAG 到底是怎么跑的",[115,19732,19733],{},"搞清楚向量库的作用之后，整个 RAG 链路就顺了：",[4640,19735,19736,19739,19742,19745],{},[143,19737,19738],{},"用户提问 -> 经过 Embedding 模型 -> 转成向量",[143,19740,19741],{},"拿向量去向量库里找最近的文档片段",[143,19743,19744],{},"找到的相关资料 + 用户原问题一起拼成 Prompt 发给大模型",[143,19746,19747],{},"大模型结合资料回答，减少幻觉",[115,19749,19750],{},"核心就是：输入给大模型之前，先用向量检索把相关上下文捞回来，拼在一起再喂进去。",[130,19752,19754],{"id":19753},"全量召回还是选几条","全量召回还是选几条？",[115,19756,19757],{},"这是第二个直觉问题：如果数据库里相关的东西很多，比如 50 页文本都沾边，全部塞进大模型？还是挑几条最相关的？",[115,19759,19760],{},"答案是必须精选截断。原因有三个：",[140,19762,19763,19769,19775],{},[143,19764,19765,19768],{},[125,19766,19767],{},"上下文窗口有限","：大模型的 Context Window 有上限，塞不下就是塞不下。",[143,19770,19771,19774],{},[125,19772,19773],{},"中间迷失","：上下文太长时，大模型容易忽略中间的内容。",[143,19776,19777,19780],{},[125,19778,19779],{},"Token 费用","：输入越长越贵。",[115,19782,19783,19784,19787],{},"所以标准做法是设置 ",[60,19785,19786],{},"top_k","（比如 limit=5），只取相似度最高的前 N 个片段。入库之前也先把长文本切成小块（比如每段 500 字），精准匹配局部内容，避免带入无关段落。",[130,19789,19790],{"id":19790},"但我的数据量没那么大",[115,19792,19793],{},"说到这里我反应过来了--我的复盘日志根本没多少数据。几万字而已，全塞进去不就完了？",[115,19795,19796],{},"确实可以。小数据量下\"全上下文注入\"更简单有效：",[140,19798,19799,19805],{},[143,19800,19801,19804],{},[125,19802,19803],{},"逻辑完整","：保留完整的时间线和因果链，不会因为切片断章取义。",[143,19806,19807,19810,19811,19814],{},[125,19808,19809],{},"架构极简","：直接 SQL ",[60,19812,19813],{},"SELECT *","，全部喂给大模型，不需要向量索引和切片逻辑。",[130,19816,19817],{"id":19817},"那向量库还有没有意义",[115,19819,19820],{},"有。即便是小数据量，向量库（或 SQL 的向量插件）仍然有几个不可替代的能力：",[140,19822,19823,19833,19839],{},[143,19824,19825,19828,19829,19832],{},[125,19826,19827],{},"模糊语义匹配","：日志里写的是\"肝到两点\"或\"披星戴月\"，向量检索能匹配到\"加班\"的语义。传统 SQL ",[60,19830,19831],{},"LIKE"," 搜不到这种。",[143,19834,19835,19838],{},[125,19836,19837],{},"扩展性","：日志会越积越多，一两年后可能就塞不进上下文窗口了。向量检索的成本随数据量亚线性增长，全量注入则是线性上升。",[143,19840,19841,19844],{},[125,19842,19843],{},"聚类与挖掘","：基于向量距离可以做无标签的主题归类和情绪分析，这是全量注入给大模型做不到的。",[115,19846,19847],{},"所以比较务实的方案是：短周期（比如近一周）的分析直接 SQL 全量提取，喂给大模型；跨年度的特定问题搜索走向量匹配。两条路各管各的，不冲突。",{"title":625,"searchDepth":732,"depth":732,"links":19849},[19850,19851,19852,19853,19854],{"id":19694,"depth":732,"text":19695},{"id":19729,"depth":732,"text":19730},{"id":19753,"depth":732,"text":19754},{"id":19790,"depth":732,"text":19790},{"id":19817,"depth":732,"text":19817},"从\"SQL 加个列不就行了\"到理解向量检索和 RAG 的完整链路，以及小数据量场景下到底该怎么选。",{},"\u002Fwriting\u002F2026-07-26-why-vector-database","2026-07-26T00:09:14+08:00",{"title":19689,"description":19855},"why-vector-database","writing\u002F2026-07-26-why-vector-database",[19863,19864,12413],"向量数据库","Embedding","CV6urQnH_Fnpu5wueNjTKv9-xJI0kiwplniuO81pVaw",1793606445834]