[{"data":1,"prerenderedAt":18245},["ShallowReactive",2],{"site-settings":3,"writing-\u002Fwriting\u002F2026-09-21-python-week-2-outcomes-modules-exceptions":107,"writing-navigation":2644},{"site":4,"home":19,"writing":40,"services":48,"about":82,"series":97},{"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.example.com","private","hard-drive",{"name":74,"description":75,"href":76,"state":59,"icon":77,"showOnHome":30},"Status","服务可用性与故障记录","#","activity",{},"开发环境、相册、NAS 和运行状态。","settings\u002Fservices","nNDd88Y4nd-YjQixH8_jtFEkODmKRufWluDlkTuN2zs",{"id":83,"contactCopy":84,"contactLabel":85,"currentFocus":86,"extension":8,"focusLabel":87,"introduction":27,"meta":88,"seoDescription":89,"stem":90,"topicsLabel":91,"writingTopics":92,"__hash__":96},"aboutSettings\u002Fsettings\u002Fabout.yml","不是，你联系我干嘛？","联系","正在上班当牛马。","正在做的事",{},"一些基本信息，以及这个网站目前用来做什么。","settings\u002Fabout","关注的主题",[93,94,95],"炒股亏钱","锻炼身体（计划中）","刷抖音 刷b站 刷小红书","C2jRa_kgSnKFyYCZuIntlAA_iv-_kD8DNNOhDasDuOA",{"id":98,"extension":8,"items":99,"meta":104,"stem":105,"__hash__":106},"seriesSettings\u002Fsettings\u002Fseries.yml",[100],{"slug":101,"title":102,"description":103},"data-ai-one-year","从 Python 到 Data\u002FAI 一年计划","12 个阶段、52 周，每周的计划与成果分开成文。",{},"settings\u002Fseries","RBIhmORVxBPd7A55Ysw93qbqR37KaDYoEhB1wWOTGl8",{"id":108,"title":109,"body":110,"category":2624,"date":2625,"description":2626,"extension":2627,"hidden":2628,"kind":132,"meta":2629,"navigation":30,"path":2630,"publishedAt":2631,"seo":2632,"series":101,"slug":2633,"stem":2634,"tags":2635,"unit":2641,"updated":2642,"__hash__":2643},"writing\u002Fwriting\u002F2026-09-21-python-week-2-outcomes-modules-exceptions.md","Week 2 学习成果｜模块机制、异常体系与工程化重构",{"type":111,"value":112,"toc":2583},"minimark",[113,128,147,152,160,165,246,249,259,274,282,286,376,460,467,472,491,506,509,560,570,650,653,662,677,686,692,697,724,739,759,762,766,769,792,805,813,816,823,852,890,893,904,912,920,954,988,991,997,1000,1055,1067,1070,1109,1133,1137,1144,1148,1193,1231,1234,1267,1271,1282,1288,1298,1356,1364,1368,1377,1396,1420,1423,1429,1440,1449,1452,1493,1499,1502,1515,1518,1532,1535,1539,1542,1566,1569,1591,1594,1597,1602,1605,1609,1632,1639,1642,1664,1667,1670,1689,1692,1695,1703,1725,1730,1738,1743,1763,1773,1778,1808,1815,1818,1823,1832,1861,1874,1877,1929,1937,1941,1948,1955,1962,2006,2009,2069,2082,2085,2118,2121,2126,2145,2168,2173,2178,2198,2201,2204,2297,2300,2325,2339,2342,2345,2372,2379,2386,2392,2396,2413,2416,2518,2532,2535,2579],[114,115,116,117,121,122,127],"p",{},"本篇是 Week 2 的",[118,119,120],"strong",{},"学习成果","，与 ",[123,124,126],"a",{"href":125},"\u002Fwriting\u002F2026-09-20-python-week-2-functions-abstraction-modularization","学习计划"," 分开成文：计划篇保留当时的任务与要求，这里放实际做出来的东西和实验记录。目前有书面成果的是 Day 2（模块与 import 机制）、Day 3（异常处理）和 Day 4（dataclass 与类型标注），Day 5–7 完成后再补进来。",[114,129,130,133,134,138,139,138,143],{},[118,131,132],{},"成果","：",[123,135,137],{"href":136},"#day-2-%E6%88%90%E6%9E%9C%E6%A8%A1%E5%9D%97%E4%B8%8E%E5%8C%85import-%E6%9C%BA%E5%88%B6","Day 2 模块与包"," ｜ ",[123,140,142],{"href":141},"#day-3-%E6%88%90%E6%9E%9C%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86","Day 3 异常处理",[123,144,146],{"href":145},"#day-4-%E6%88%90%E6%9E%9Cdataclass-%E4%B8%8E%E7%B1%BB%E5%9E%8B%E6%A0%87%E6%B3%A8","Day 4 dataclass 与类型标注",[148,149,151],"h2",{"id":150},"day-2-成果模块与包import-机制","Day 2 成果｜模块与包：import 机制",[114,153,154,133,157],{},[118,155,156],{},"对应计划",[123,158,159],{"href":125},"Day 2｜模块与包：import 机制",[161,162,164],"h3",{"id":163},"模块-包-子包","模块 \u002F 包 \u002F 子包",[166,167,168,184],"table",{},[169,170,171],"thead",{},[172,173,174,178,181],"tr",{},[175,176,177],"th",{},"概念",[175,179,180],{},"对应什么",[175,182,183],{},"例子",[185,186,187,209,229],"tbody",{},[172,188,189,193,200],{},[190,191,192],"td",{},"模块 module",[190,194,195,196,199],{},"一个 ",[60,197,198],{},".py"," 文件",[190,201,202,205,206],{},[60,203,204],{},"core.py"," → 模块 ",[60,207,208],{},"mypkg.core",[172,210,211,214,220],{},[190,212,213],{},"包 package",[190,215,216,217],{},"一个目录，含 ",[60,218,219],{},"__init__.py",[190,221,222,225,226],{},[60,223,224],{},"mypkg\u002F"," → 包 ",[60,227,228],{},"mypkg",[172,230,231,234,237],{},[190,232,233],{},"子包 subpackage",[190,235,236],{},"包里面的包",[190,238,239,242,243],{},[60,240,241],{},"mypkg\u002Futils\u002F"," → 子包 ",[60,244,245],{},"mypkg.utils",[114,247,248],{},"实验用的结构：",[250,251,256],"pre",{"className":252,"code":254,"language":255},[253],"language-text","mypkg\u002F\n├── __init__.py\n├── core.py\n└── utils\u002F\n    ├── __init__.py\n    └── text.py\n","text",[60,257,254],{"__ignoreMap":258},"",[114,260,261,263,264,266,267,269,270,273],{},[60,262,228],{}," 是顶层包，",[60,265,208],{}," 是模块，",[60,268,245],{}," 是子包，",[60,271,272],{},"mypkg.utils.text"," 是子包里的模块。",[114,275,276],{},[118,277,278,279,281],{},"一句话：目录 = 包 \u002F 子包；",[60,280,198],{}," 文件 = 模块；点号表示层级。",[161,283,285],{"id":284},"三种-import-写法","三种 import 写法",[250,287,291],{"className":288,"code":289,"language":290,"meta":258,"style":258},"language-python shiki shiki-themes github-light github-dark","# 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()            # 当前命名空间绑定 core\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","python",[60,292,293,301,307,312,318,324,330,336,341,347,353,358,364,370],{"__ignoreMap":258},[294,295,298],"span",{"class":296,"line":297},"line",1,[294,299,300],{},"# 1. import x —— 绑定包名\n",[294,302,304],{"class":296,"line":303},2,[294,305,306],{},"import mypkg.core\n",[294,308,309],{"class":296,"line":34},[294,310,311],{},"mypkg.core.hello()      # 当前命名空间只绑定 mypkg，必须写全 mypkg.core\n",[294,313,315],{"class":296,"line":314},4,[294,316,317],{"emptyLinePlaceholder":30},"\n",[294,319,321],{"class":296,"line":320},5,[294,322,323],{},"# 2. from x import y —— 绑定里面的名字\n",[294,325,327],{"class":296,"line":326},6,[294,328,329],{},"from mypkg import core\n",[294,331,333],{"class":296,"line":332},7,[294,334,335],{},"core.hello()            # 当前命名空间绑定 core\n",[294,337,339],{"class":296,"line":338},8,[294,340,317],{"emptyLinePlaceholder":30},[294,342,344],{"class":296,"line":343},9,[294,345,346],{},"from mypkg.core import hello\n",[294,348,350],{"class":296,"line":349},10,[294,351,352],{},"hello()                 # 也可以直接导入函数\n",[294,354,356],{"class":296,"line":355},11,[294,357,317],{"emptyLinePlaceholder":30},[294,359,361],{"class":296,"line":360},12,[294,362,363],{},"# 3. from x import y as z —— 重命名\n",[294,365,367],{"class":296,"line":366},13,[294,368,369],{},"from mypkg.core import hello as hi\n",[294,371,373],{"class":296,"line":372},14,[294,374,375],{},"hi()\n",[166,377,378,391],{},[169,379,380],{},[172,381,382,385,388],{},[175,383,384],{},"写法",[175,386,387],{},"当前绑定谁",[175,389,390],{},"调用方式",[185,392,393,409,426,443],{},[172,394,395,400,404],{},[190,396,397],{},[60,398,399],{},"import mypkg.core",[190,401,402],{},[60,403,228],{},[190,405,406],{},[60,407,408],{},"mypkg.core.hello()",[172,410,411,416,421],{},[190,412,413],{},[60,414,415],{},"from mypkg import core",[190,417,418],{},[60,419,420],{},"core",[190,422,423],{},[60,424,425],{},"core.hello()",[172,427,428,433,438],{},[190,429,430],{},[60,431,432],{},"from mypkg.core import hello",[190,434,435],{},[60,436,437],{},"hello",[190,439,440],{},[60,441,442],{},"hello()",[172,444,445,450,455],{},[190,446,447],{},[60,448,449],{},"from mypkg.core import hello as hi",[190,451,452],{},[60,453,454],{},"hi",[190,456,457],{},[60,458,459],{},"hi()",[161,461,463,466],{"id":462},"__name__-与-main-守卫",[60,464,465],{},"__name__"," 与 main 守卫",[114,468,469,470,133],{},"每个模块都有一个 ",[60,471,465],{},[473,474,475,482],"ul",{},[476,477,478,479],"li",{},"直接运行文件：",[60,480,481],{},"__name__ == \"__main__\"",[476,483,484,485,487,488],{},"被导入时：",[60,486,465],{}," 是模块导入路径，如 ",[60,489,490],{},"\"mypkg.core\"",[250,492,494],{"className":288,"code":493,"language":290,"meta":258,"style":258},"if __name__ == \"__main__\":\n    hello()\n",[60,495,496,501],{"__ignoreMap":258},[294,497,498],{"class":296,"line":297},[294,499,500],{},"if __name__ == \"__main__\":\n",[294,502,503],{"class":296,"line":303},[294,504,505],{},"    hello()\n",[114,507,508],{},"作用：只有直接运行这个文件时才执行，被导入时不执行。",[166,510,511,525],{},[169,512,513],{},[172,514,515,518],{},[175,516,517],{},"运行方式",[175,519,520,522,523],{},[60,521,204],{}," 里的 ",[60,524,465],{},[185,526,527,539,550],{},[172,528,529,534],{},[190,530,531],{},[60,532,533],{},"python mypkg\u002Fcore.py",[190,535,536],{},[60,537,538],{},"\"__main__\"",[172,540,541,546],{},[190,542,543],{},[60,544,545],{},"python -m mypkg.core",[190,547,548],{},[60,549,538],{},[172,551,552,556],{},[190,553,554],{},[60,555,399],{},[190,557,558],{},[60,559,490],{},[161,561,563,566,567],{"id":562},"python-filepy-vs-python-m-pkgmod",[60,564,565],{},"python file.py"," vs ",[60,568,569],{},"python -m pkg.mod",[166,571,572,587],{},[169,573,574],{},[172,575,576,579,583],{},[175,577,578],{},"对比项",[175,580,581],{},[60,582,533],{},[175,584,585],{},[60,586,545],{},[185,588,589,603,617,628,639],{},[172,590,591,595,599],{},[190,592,593],{},[60,594,465],{},[190,596,597],{},[60,598,538],{},[190,600,601],{},[60,602,538],{},[172,604,605,610,614],{},[190,606,607],{},[60,608,609],{},"sys.path[0]",[190,611,612],{},[60,613,224],{},[190,615,616],{},"当前工作目录",[172,618,619,622,625],{},[190,620,621],{},"包上下文",[190,623,624],{},"无",[190,626,627],{},"有",[172,629,630,633,636],{},[190,631,632],{},"相对导入",[190,634,635],{},"失败",[190,637,638],{},"成功",[172,640,641,644,647],{},[190,642,643],{},"适合",[190,645,646],{},"独立脚本",[190,648,649],{},"包内模块",[114,651,652],{},"相对导入实验：",[250,654,656],{"className":288,"code":655,"language":290,"meta":258,"style":258},"from .utils import text\n",[60,657,658],{"__ignoreMap":258},[294,659,660],{"class":296,"line":297},[294,661,655],{},[473,663,664,672],{},[476,665,666,668,669],{},[60,667,533],{}," → 报错 ",[60,670,671],{},"attempted relative import with no known parent package",[476,673,674,676],{},[60,675,545],{}," → 正常",[114,678,679,680,682,683,685],{},"原因：",[60,681,565],{}," 把文件当独立脚本，没有父包；",[60,684,569],{}," 把模块放在包上下文里运行，有父包。",[161,687,689,691],{"id":688},"__init__py-与-re-export",[60,690,219],{}," 与 re-export",[114,693,694,696],{},[60,695,219],{}," 的作用：",[473,698,699,702,705,711,721],{},[476,700,701],{},"导入包时执行初始化代码",[476,703,704],{},"准备包对象上要暴露的名字",[476,706,707,708],{},"re-export，让外部可以直接 ",[60,709,710],{},"from mypkg import hello",[476,712,713,714,717,718],{},"定义 ",[60,715,716],{},"__all__","，控制 ",[60,719,720],{},"from mypkg import *",[476,722,723],{},"组织公开接口，隐藏内部结构",[250,725,727],{"className":288,"code":726,"language":290,"meta":258,"style":258},"# mypkg\u002F__init__.py\nfrom .core import hello\n",[60,728,729,734],{"__ignoreMap":258},[294,730,731],{"class":296,"line":297},[294,732,733],{},"# mypkg\u002F__init__.py\n",[294,735,736],{"class":296,"line":303},[294,737,738],{},"from .core import hello\n",[250,740,742],{"className":288,"code":741,"language":290,"meta":258,"style":258},"# 外部\nfrom mypkg import hello\nhello()\n",[60,743,744,749,754],{"__ignoreMap":258},[294,745,746],{"class":296,"line":297},[294,747,748],{},"# 外部\n",[294,750,751],{"class":296,"line":303},[294,752,753],{},"from mypkg import hello\n",[294,755,756],{"class":296,"line":34},[294,757,758],{},"hello()\n",[114,760,761],{},"好处：对外隐藏内部结构，重构内部文件时不影响外部调用。",[161,763,765],{"id":764},"import-查找顺序与缓存","import 查找顺序与缓存",[114,767,768],{},"导入时的顺序：",[770,771,772,781],"ol",{},[476,773,774,780],{},[118,775,776,779],{},[60,777,778],{},"sys.modules"," 缓存"," —— 先看这个模块之前有没有被导入过，有就直接用，不再重新执行。",[476,782,783,788,789,791],{},[118,784,785],{},[60,786,787],{},"sys.path"," —— 缓存里没有，就按 ",[60,790,787],{}," 里的目录顺序查找。",[114,793,794,796,797,800,801,804],{},[60,795,787],{}," 通常包括：当前目录或脚本目录、",[60,798,799],{},"PYTHONPATH"," 里的目录、Python 标准库目录、",[60,802,803],{},"site-packages"," 第三方库目录。",[114,806,807],{},[118,808,809,810,812],{},"结论：第一次导入会执行模块顶层代码并放入 ",[60,811,778],{},"；第二次导入直接复用缓存，不再执行。",[161,814,815],{"id":815},"循环导入",[114,817,818,819,822],{},"成因：两个模块在顶层互相 ",[60,820,821],{},"import","，其中一个还没执行完，另一个就试图使用它还没定义好的名字。",[250,824,826],{"className":288,"code":825,"language":290,"meta":258,"style":258},"# a.py\nimport b\n\ndef a_func():\n    print(\"a_func\")\n",[60,827,828,833,838,842,847],{"__ignoreMap":258},[294,829,830],{"class":296,"line":297},[294,831,832],{},"# a.py\n",[294,834,835],{"class":296,"line":303},[294,836,837],{},"import b\n",[294,839,840],{"class":296,"line":34},[294,841,317],{"emptyLinePlaceholder":30},[294,843,844],{"class":296,"line":314},[294,845,846],{},"def a_func():\n",[294,848,849],{"class":296,"line":320},[294,850,851],{},"    print(\"a_func\")\n",[250,853,855],{"className":288,"code":854,"language":290,"meta":258,"style":258},"# b.py\nimport a\n\ndef b_func():\n    print(\"b_func\")\n\na.a_func()   # 此时 a 可能还没定义完 a_func\n",[60,856,857,862,867,871,876,881,885],{"__ignoreMap":258},[294,858,859],{"class":296,"line":297},[294,860,861],{},"# b.py\n",[294,863,864],{"class":296,"line":303},[294,865,866],{},"import a\n",[294,868,869],{"class":296,"line":34},[294,870,317],{"emptyLinePlaceholder":30},[294,872,873],{"class":296,"line":314},[294,874,875],{},"def b_func():\n",[294,877,878],{"class":296,"line":320},[294,879,880],{},"    print(\"b_func\")\n",[294,882,883],{"class":296,"line":326},[294,884,317],{"emptyLinePlaceholder":30},[294,886,887],{"class":296,"line":332},[294,888,889],{},"a.a_func()   # 此时 a 可能还没定义完 a_func\n",[114,891,892],{},"常见解法：",[473,894,895,901],{},[476,896,897,898,900],{},"把 ",[60,899,821],{}," 移到函数内部（延迟导入）",[476,902,903],{},"抽公共模块解耦",[114,905,906],{},[118,907,908,909,911],{},"关键理解：把 ",[60,910,821],{}," 放进函数，就是把导入时机从「模块加载时」推迟到「函数调用时」，避免加载时互相卡住。",[161,913,915,916,919],{"id":914},"重点实验from-x-import-y-到底绑定了什么","重点实验：",[60,917,918],{},"from x import y"," 到底绑定了什么？",[250,921,923],{"className":288,"code":922,"language":290,"meta":258,"style":258},"# counter.py\ncount = 0\n\ndef inc():\n    global count\n    count += 1\n",[60,924,925,930,935,939,944,949],{"__ignoreMap":258},[294,926,927],{"class":296,"line":297},[294,928,929],{},"# counter.py\n",[294,931,932],{"class":296,"line":303},[294,933,934],{},"count = 0\n",[294,936,937],{"class":296,"line":34},[294,938,317],{"emptyLinePlaceholder":30},[294,940,941],{"class":296,"line":314},[294,942,943],{},"def inc():\n",[294,945,946],{"class":296,"line":320},[294,947,948],{},"    global count\n",[294,950,951],{"class":296,"line":326},[294,952,953],{},"    count += 1\n",[250,955,957],{"className":288,"code":956,"language":290,"meta":258,"style":258},"# main.py\nimport counter\nfrom counter import count\n\ncounter.inc()\nprint(counter.count, count)\n",[60,958,959,964,969,974,978,983],{"__ignoreMap":258},[294,960,961],{"class":296,"line":297},[294,962,963],{},"# main.py\n",[294,965,966],{"class":296,"line":303},[294,967,968],{},"import counter\n",[294,970,971],{"class":296,"line":34},[294,972,973],{},"from counter import count\n",[294,975,976],{"class":296,"line":314},[294,977,317],{"emptyLinePlaceholder":30},[294,979,980],{"class":296,"line":320},[294,981,982],{},"counter.inc()\n",[294,984,985],{"class":296,"line":326},[294,986,987],{},"print(counter.count, count)\n",[114,989,990],{},"输出：",[250,992,995],{"className":993,"code":994,"language":255},[253],"1 0\n",[60,996,994],{"__ignoreMap":258},[114,998,999],{},"解释：",[473,1001,1002,1012,1033,1046],{},[476,1003,1004,1007,1008,1011],{},[60,1005,1006],{},"import counter"," 后，",[60,1009,1010],{},"counter.count"," 是 0。",[476,1013,1014,1017,1018,1020,1021,1024,1025,1028,1029,1032],{},[60,1015,1016],{},"from counter import count"," 把 ",[60,1019,1010],{}," 当时指向的整数 ",[60,1022,1023],{},"0"," 绑定到 ",[60,1026,1027],{},"main.py"," 的 ",[60,1030,1031],{},"count","。",[476,1034,1035,1038,1039,1042,1043,1045],{},[60,1036,1037],{},"counter.inc()"," 修改的是 ",[60,1040,1041],{},"counter"," 模块自己的全局变量 ",[60,1044,1031],{},"，使其变成 1。",[476,1047,1048,522,1050,1052,1053,1032],{},[60,1049,1027],{},[60,1051,1031],{}," 仍指向旧整数 ",[60,1054,1023],{},[114,1056,1057],{},[118,1058,1059,1060,1062,1063,1066],{},"结论：",[60,1061,918],{}," 绑定的是导入那一刻 ",[60,1064,1065],{},"y"," 指向的对象，不是变量名本身。整数不可变，重新赋值不会影响已绑定的名字。",[161,1068,1069],{"id":1069},"验收四题",[770,1071,1072,1081,1092,1103],{},[476,1073,1074,1077,1078,1080],{},[118,1075,1076],{},"import 同一模块两次，模块代码执行几次？为什么？"," 只执行一次。第一次执行后放入 ",[60,1079,778],{}," 缓存，第二次直接复用。",[476,1082,1083,1088,1089,1091],{},[118,1084,1085,1087],{},[60,1086,219],{}," 能做什么？"," 执行包初始化、re-export、定义 ",[60,1090,716],{},"、组织公开接口。",[476,1093,1094,1102],{},[118,1095,1096,1098,1099,1101],{},[60,1097,565],{}," 和 ",[60,1100,569],{}," 的区别？"," 前者当独立脚本运行，无包上下文，相对导入失败；后者当包内模块运行，有包上下文，相对导入成功。",[476,1104,1105,1108],{},[118,1106,1107],{},"循环导入为什么发生？最常见的解法是什么？"," 两个模块顶层互相导入，一方还没执行完另一方就使用它。解法：延迟导入到函数内，或抽公共模块解耦。",[114,1110,1111],{},[118,1112,1113,1114,1116,1117,1119,1120,1122,1123,1125,1126,1128,1129,1132],{},"Day 2 一句话总结：模块是 ",[60,1115,198],{}," 文件，包是含 ",[60,1118,219],{}," 的目录。import 先查 ",[60,1121,778],{}," 缓存，再按 ",[60,1124,787],{}," 查找。",[60,1127,918],{}," 绑定的是对象，不是变量名。直接运行用 main 守卫；包内模块用 ",[60,1130,1131],{},"python -m"," 跑，相对导入才有效。循环导入靠延迟导入或解耦解决。",[148,1134,1136],{"id":1135},"day-3-成果异常处理","Day 3 成果｜异常处理",[114,1138,1139,133,1141],{},[118,1140,156],{},[123,1142,1143],{"href":125},"Day 3｜异常处理：健壮性基础",[161,1145,1147],{"id":1146},"基本结构try-except-else-finally","基本结构：try \u002F except \u002F else \u002F finally",[250,1149,1151],{"className":288,"code":1150,"language":290,"meta":258,"style":258},"try:\n    ...        # 可能出错的代码\nexcept ValueError:\n    ...        # 捕获并处理 ValueError\nelse:\n    ...        # try 中没有异常时执行\nfinally:\n    ...        # 无论有没有异常，最后都执行\n",[60,1152,1153,1158,1163,1168,1173,1178,1183,1188],{"__ignoreMap":258},[294,1154,1155],{"class":296,"line":297},[294,1156,1157],{},"try:\n",[294,1159,1160],{"class":296,"line":303},[294,1161,1162],{},"    ...        # 可能出错的代码\n",[294,1164,1165],{"class":296,"line":34},[294,1166,1167],{},"except ValueError:\n",[294,1169,1170],{"class":296,"line":314},[294,1171,1172],{},"    ...        # 捕获并处理 ValueError\n",[294,1174,1175],{"class":296,"line":320},[294,1176,1177],{},"else:\n",[294,1179,1180],{"class":296,"line":326},[294,1181,1182],{},"    ...        # try 中没有异常时执行\n",[294,1184,1185],{"class":296,"line":332},[294,1186,1187],{},"finally:\n",[294,1189,1190],{"class":296,"line":338},[294,1191,1192],{},"    ...        # 无论有没有异常，最后都执行\n",[166,1194,1195,1205],{},[169,1196,1197],{},[172,1198,1199,1202],{},[175,1200,1201],{},"情况",[175,1203,1204],{},"执行顺序",[185,1206,1207,1215,1223],{},[172,1208,1209,1212],{},[190,1210,1211],{},"try 成功",[190,1213,1214],{},"try → else → finally",[172,1216,1217,1220],{},[190,1218,1219],{},"try 失败并被捕获",[190,1221,1222],{},"try → except → finally",[172,1224,1225,1228],{},[190,1226,1227],{},"try 失败但没被捕获",[190,1229,1230],{},"try → finally，异常继续往上抛",[114,1232,1233],{},"关键点：",[473,1235,1236,1242,1252],{},[476,1237,1238,1241],{},[60,1239,1240],{},"else","：只有 try 没抛异常时才执行。",[476,1243,1244,1247,1248,1251],{},[60,1245,1246],{},"finally","：无论成功、失败、",[60,1249,1250],{},"return","，都会执行。",[476,1253,1254,522,1256,1258,1259,1261,1262,1264,1265,1032],{},[60,1255,1246],{},[60,1257,1250],{}," 会覆盖 try 里的 ",[60,1260,1250],{},"，所以不要在 ",[60,1263,1246],{}," 里随便写 ",[60,1266,1250],{},[161,1268,1270],{"id":1269},"except-匹配规则","except 匹配规则",[473,1272,1273,1279],{},[476,1274,1275,1278],{},[60,1276,1277],{},"except"," 从上往下匹配，第一个匹配上的就执行。",[476,1280,1281],{},"父类可以捕获子类，所以子类异常要写在父类异常前面。",[250,1283,1286],{"className":1284,"code":1285,"language":255},[253],"BaseException\n ├── KeyboardInterrupt\n ├── SystemExit\n ├── GeneratorExit\n └── Exception\n      ├── ValueError\n      ├── TypeError\n      ├── KeyError\n      ├── OSError\n      └── ...\n",[60,1287,1285],{"__ignoreMap":258},[161,1289,1291,1292,566,1295],{"id":1290},"裸-except-vs-except-exception","裸 ",[60,1293,1294],{},"except:",[60,1296,1297],{},"except Exception:",[166,1299,1300,1312],{},[169,1301,1302],{},[172,1303,1304,1306,1309],{},[175,1305,384],{},[175,1307,1308],{},"捕获范围",[175,1310,1311],{},"危险点",[185,1313,1314,1336],{},[172,1315,1316,1320,1326],{},[190,1317,1318],{},[60,1319,1297],{},[190,1321,1322,1325],{},[60,1323,1324],{},"Exception"," 及其子类",[190,1327,1328,1329,1332,1333],{},"不抓 ",[60,1330,1331],{},"KeyboardInterrupt","、",[60,1334,1335],{},"SystemExit",[172,1337,1338,1342,1349],{},[190,1339,1291,1340],{},[60,1341,1294],{},[190,1343,1344,1345,1348],{},"所有 ",[60,1346,1347],{},"BaseException"," 子类",[190,1350,1351,1352,1355],{},"会抓 Ctrl+C、",[60,1353,1354],{},"sys.exit()","，程序可能停不下来",[114,1357,1358],{},[118,1359,1360,1361,1363],{},"结论：裸 ",[60,1362,1294],{}," 更危险，因为它连退出信号都吞。",[161,1365,1367],{"id":1366},"raise-与异常链","raise 与异常链",[250,1369,1371],{"className":288,"code":1370,"language":290,"meta":258,"style":258},"raise ValueError(\"值不对\")        # 主动抛异常\n",[60,1372,1373],{"__ignoreMap":258},[294,1374,1375],{"class":296,"line":297},[294,1376,1370],{},[250,1378,1380],{"className":288,"code":1379,"language":290,"meta":258,"style":258},"except ValueError:\n    ...\n    raise                         # 重新抛出当前异常\n",[60,1381,1382,1386,1391],{"__ignoreMap":258},[294,1383,1384],{"class":296,"line":297},[294,1385,1167],{},[294,1387,1388],{"class":296,"line":303},[294,1389,1390],{},"    ...\n",[294,1392,1393],{"class":296,"line":34},[294,1394,1395],{},"    raise                         # 重新抛出当前异常\n",[250,1397,1399],{"className":288,"code":1398,"language":290,"meta":258,"style":258},"try:\n    int(\"abc\")\nexcept ValueError as e:\n    raise RuntimeError(\"配置解析失败\") from e\n",[60,1400,1401,1405,1410,1415],{"__ignoreMap":258},[294,1402,1403],{"class":296,"line":297},[294,1404,1157],{},[294,1406,1407],{"class":296,"line":303},[294,1408,1409],{},"    int(\"abc\")\n",[294,1411,1412],{"class":296,"line":34},[294,1413,1414],{},"except ValueError as e:\n",[294,1416,1417],{"class":296,"line":314},[294,1418,1419],{},"    raise RuntimeError(\"配置解析失败\") from e\n",[114,1421,1422],{},"traceback 会出现：",[250,1424,1427],{"className":1425,"code":1426,"language":255},[253],"The above exception was the direct cause of the following exception:\n",[60,1428,1426],{"__ignoreMap":258},[114,1430,1431,1432,1435,1436,1439],{},"含义：对外暴露 ",[60,1433,1434],{},"RuntimeError(\"配置解析失败\")","，对内保留 ",[60,1437,1438],{},"ValueError"," 作为直接原因。",[114,1441,1442],{},[118,1443,1444,1445,1448],{},"一句话：",[60,1446,1447],{},"raise ... from e"," = 上层业务异常包装 + 保留底层技术根因。",[161,1450,1451],{"id":1451},"自定义异常",[250,1453,1455],{"className":288,"code":1454,"language":290,"meta":258,"style":258},"class AppError(Exception):\n    pass\n\nclass ConfigError(AppError):\n    pass\n\nclass DataError(AppError):\n    pass\n",[60,1456,1457,1462,1467,1471,1476,1480,1484,1489],{"__ignoreMap":258},[294,1458,1459],{"class":296,"line":297},[294,1460,1461],{},"class AppError(Exception):\n",[294,1463,1464],{"class":296,"line":303},[294,1465,1466],{},"    pass\n",[294,1468,1469],{"class":296,"line":34},[294,1470,317],{"emptyLinePlaceholder":30},[294,1472,1473],{"class":296,"line":314},[294,1474,1475],{},"class ConfigError(AppError):\n",[294,1477,1478],{"class":296,"line":320},[294,1479,1466],{},[294,1481,1482],{"class":296,"line":326},[294,1483,317],{"emptyLinePlaceholder":30},[294,1485,1486],{"class":296,"line":332},[294,1487,1488],{},"class DataError(AppError):\n",[294,1490,1491],{"class":296,"line":338},[294,1492,1466],{},[250,1494,1497],{"className":1495,"code":1496,"language":255},[253],"Exception\n └── AppError\n      ├── ConfigError\n      └── DataError\n",[60,1498,1496],{"__ignoreMap":258},[114,1500,1501],{},"作用：按业务语义区分错误类型、让调用方精准捕获、可以附加额外信息。",[114,1503,1504,1505,1508,1509,1332,1512,1032],{},"值得自定义的场景：需要区分「配置错误 \u002F 数据错误 \u002F 网络错误」等业务类型；需要让上层只捕获 ",[60,1506,1507],{},"AppError","；需要给异常附加字段，比如 ",[60,1510,1511],{},"key",[60,1513,1514],{},"path",[114,1516,1517],{},"不值得的场景：",[250,1519,1521],{"className":288,"code":1520,"language":290,"meta":258,"style":258},"class MyError(ValueError):\n    pass\n",[60,1522,1523,1528],{"__ignoreMap":258},[294,1524,1525],{"class":296,"line":297},[294,1526,1527],{},"class MyError(ValueError):\n",[294,1529,1530],{"class":296,"line":303},[294,1531,1466],{},[114,1533,1534],{},"只换名字、不加信息、不做层级区分，就不值得。",[161,1536,1538],{"id":1537},"eafp-vs-lbyl","EAFP vs LBYL",[114,1540,1541],{},"LBYL（先检查，再操作）：",[250,1543,1545],{"className":288,"code":1544,"language":290,"meta":258,"style":258},"if key in config:\n    return config[key]\nelse:\n    return \"default\"\n",[60,1546,1547,1552,1557,1561],{"__ignoreMap":258},[294,1548,1549],{"class":296,"line":297},[294,1550,1551],{},"if key in config:\n",[294,1553,1554],{"class":296,"line":303},[294,1555,1556],{},"    return config[key]\n",[294,1558,1559],{"class":296,"line":34},[294,1560,1177],{},[294,1562,1563],{"class":296,"line":314},[294,1564,1565],{},"    return \"default\"\n",[114,1567,1568],{},"EAFP（先操作，出错再处理）：",[250,1570,1572],{"className":288,"code":1571,"language":290,"meta":258,"style":258},"try:\n    return config[key]\nexcept KeyError:\n    return \"default\"\n",[60,1573,1574,1578,1582,1587],{"__ignoreMap":258},[294,1575,1576],{"class":296,"line":297},[294,1577,1157],{},[294,1579,1580],{"class":296,"line":303},[294,1581,1556],{},[294,1583,1584],{"class":296,"line":34},[294,1585,1586],{},"except KeyError:\n",[294,1588,1589],{"class":296,"line":314},[294,1590,1565],{},[114,1592,1593],{},"EAFP 好处：不用自己写复杂预检查；代码更短、更直接；避免检查和操作之间状态变化。",[114,1595,1596],{},"EAFP 风险：try 范围太大，出错难定位；except 太宽，容易吞掉不该吞的异常；捕获后不处理，程序带伤继续跑。",[114,1598,1599],{},[118,1600,1601],{},"一句话：EAFP 省去预检查，靠异常处理；但 try 要小，except 要具体。",[161,1603,1604],{"id":1604},"反模式",[114,1606,1291,1607,133],{},[60,1608,1294],{},[250,1610,1612],{"className":288,"code":1611,"language":290,"meta":258,"style":258},"try:\n    do_something()\nexcept:\n    pass\n",[60,1613,1614,1618,1623,1628],{"__ignoreMap":258},[294,1615,1616],{"class":296,"line":297},[294,1617,1157],{},[294,1619,1620],{"class":296,"line":303},[294,1621,1622],{},"    do_something()\n",[294,1624,1625],{"class":296,"line":34},[294,1626,1627],{},"except:\n",[294,1629,1630],{"class":296,"line":314},[294,1631,1466],{},[114,1633,1634,1635,1332,1637,1032],{},"危险：会吞掉 ",[60,1636,1331],{},[60,1638,1335],{},[114,1640,1641],{},"吞异常不记录：",[250,1643,1645],{"className":288,"code":1644,"language":290,"meta":258,"style":258},"try:\n    do_something()\nexcept Exception:\n    pass\n",[60,1646,1647,1651,1655,1660],{"__ignoreMap":258},[294,1648,1649],{"class":296,"line":297},[294,1650,1157],{},[294,1652,1653],{"class":296,"line":303},[294,1654,1622],{},[294,1656,1657],{"class":296,"line":34},[294,1658,1659],{},"except Exception:\n",[294,1661,1662],{"class":296,"line":314},[294,1663,1466],{},[114,1665,1666],{},"危险：错误被隐藏，调试无线索。",[114,1668,1669],{},"正确做法：",[250,1671,1673],{"className":288,"code":1672,"language":290,"meta":258,"style":258},"except Exception:\n    logging.exception(\"出错了\")\n    raise\n",[60,1674,1675,1679,1684],{"__ignoreMap":258},[294,1676,1677],{"class":296,"line":297},[294,1678,1659],{},[294,1680,1681],{"class":296,"line":303},[294,1682,1683],{},"    logging.exception(\"出错了\")\n",[294,1685,1686],{"class":296,"line":34},[294,1687,1688],{},"    raise\n",[114,1690,1691],{},"或者只捕获具体异常，并明确处理。",[161,1693,1694],{"id":1694},"今天完成的实验",[114,1696,1697],{},[118,1698,1699,1700],{},"实验 1：",[60,1701,1702],{},"parse_int(s)",[114,1704,1705,1706,1709,1710,1332,1712,1709,1715,1718,1719,1721,1722,1032],{},"处理了 ",[60,1707,1708],{},"int(\"abc\")"," → ",[60,1711,1438],{},[60,1713,1714],{},"int(None)",[60,1716,1717],{},"TypeError","；成功时走 ",[60,1720,1240],{}," 并返回结果，出错时打印提示，隐式返回 ",[60,1723,1724],{},"None",[114,1726,1727],{},[118,1728,1729],{},"实验 2：try \u002F else \u002F finally",[114,1731,1732,1733,1735,1736,1032],{},"观察到成功时 ",[60,1734,1214],{},"，失败时 ",[60,1737,1222],{},[114,1739,1740],{},[118,1741,1742],{},"实验 3：自定义异常层级",[250,1744,1746],{"className":288,"code":1745,"language":290,"meta":258,"style":258},"class AppError(Exception): pass\nclass ConfigError(AppError): pass\nclass DataError(AppError): pass\n",[60,1747,1748,1753,1758],{"__ignoreMap":258},[294,1749,1750],{"class":296,"line":297},[294,1751,1752],{},"class AppError(Exception): pass\n",[294,1754,1755],{"class":296,"line":303},[294,1756,1757],{},"class ConfigError(AppError): pass\n",[294,1759,1760],{"class":296,"line":34},[294,1761,1762],{},"class DataError(AppError): pass\n",[114,1764,1765,1766,1769,1770,1032],{},"验证了 ",[60,1767,1768],{},"except AppError"," 能捕获 ",[60,1771,1772],{},"ConfigError",[114,1774,1775],{},[118,1776,1777],{},"实验 4：LBYL 改 EAFP",[250,1779,1781],{"className":288,"code":1780,"language":290,"meta":258,"style":258},"def get_config_EAFP(config, key):\n    try:\n        return config[key]\n    except KeyError:\n        return \"default\"\n",[60,1782,1783,1788,1793,1798,1803],{"__ignoreMap":258},[294,1784,1785],{"class":296,"line":297},[294,1786,1787],{},"def get_config_EAFP(config, key):\n",[294,1789,1790],{"class":296,"line":303},[294,1791,1792],{},"    try:\n",[294,1794,1795],{"class":296,"line":34},[294,1796,1797],{},"        return config[key]\n",[294,1799,1800],{"class":296,"line":314},[294,1801,1802],{},"    except KeyError:\n",[294,1804,1805],{"class":296,"line":320},[294,1806,1807],{},"        return \"default\"\n",[114,1809,1810],{},[118,1811,915,1812],{},[60,1813,1814],{},"raise ... from ...",[114,1816,1817],{},"观察到两个异常同时出现在 traceback 中，以及：",[250,1819,1821],{"className":1820,"code":1426,"language":255},[253],[60,1822,1426],{"__ignoreMap":258},[114,1824,1825],{},[118,1826,1827,1828,522,1830],{},"附加实验：",[60,1829,1246],{},[60,1831,1250],{},[250,1833,1835],{"className":288,"code":1834,"language":290,"meta":258,"style":258},"def f():\n    try:\n        return \"try\"\n    finally:\n        return \"finally\"\n",[60,1836,1837,1842,1846,1851,1856],{"__ignoreMap":258},[294,1838,1839],{"class":296,"line":297},[294,1840,1841],{},"def f():\n",[294,1843,1844],{"class":296,"line":303},[294,1845,1792],{},[294,1847,1848],{"class":296,"line":34},[294,1849,1850],{},"        return \"try\"\n",[294,1852,1853],{"class":296,"line":314},[294,1854,1855],{},"    finally:\n",[294,1857,1858],{"class":296,"line":320},[294,1859,1860],{},"        return \"finally\"\n",[114,1862,1863,1864,1867,1868,522,1870,1258,1872,1032],{},"最终返回 ",[60,1865,1866],{},"\"finally\"","，说明 ",[60,1869,1246],{},[60,1871,1250],{},[60,1873,1250],{},[161,1875,1069],{"id":1876},"验收四题-1",[770,1878,1879,1901,1917,1923],{},[476,1880,1881,1889,1890,1892,1893,1895,1896,1332,1898,1900],{},[118,1882,1883,1886,1887,1101],{},[60,1884,1885],{},"except Exception"," 和裸 ",[60,1888,1294],{}," ",[60,1891,1885],{}," 只抓普通异常；裸 ",[60,1894,1294],{}," 连 ",[60,1897,1331],{},[60,1899,1335],{}," 都抓，更危险。",[476,1902,1903,1889,1911,1913,1914,1916],{},[118,1904,1905,1907,1908,1910],{},[60,1906,1240],{}," 什么时候执行？",[60,1909,1246],{}," 呢？",[60,1912,1240],{}," 在 try 无异常时执行；",[60,1915,1246],{}," 无论有没有异常都执行。",[476,1918,1919,1922],{},[118,1920,1921],{},"什么时候值得自定义异常类？"," 需要按业务语义区分错误、精准捕获、附加额外信息时。",[476,1924,1925,1928],{},[118,1926,1927],{},"EAFP 是什么？相比 LBYL 的好处和风险？"," EAFP 是先做，出错再处理。好处是省去复杂预检查；风险是 try 和 except 范围失控会吞错。",[114,1930,1931],{},[118,1932,1933,1934,1936],{},"Day 3 一句话总结：try 放可能出错的代码，except 处理已知错误，else 走成功分支，finally 做兜底清理。异常要么明确处理，要么记录后 raise，不要吞。需要分层时用 ",[60,1935,1447],{},"。优先 EAFP，但 try 要小，except 要具体。",[148,1938,1940],{"id":1939},"day-4-成果dataclass-与类型标注","Day 4 成果｜dataclass 与类型标注",[114,1942,1943,133,1945],{},[118,1944,156],{},[123,1946,1947],{"href":125},"Day 4｜dataclass 与类型标注",[161,1949,1951,1954],{"id":1950},"dataclass-是什么",[60,1952,1953],{},"@dataclass"," 是什么",[114,1956,1957,1958,1961],{},"标准库 ",[60,1959,1960],{},"dataclasses"," 里的装饰器，自动为“数据类”生成样板方法。",[250,1963,1965],{"className":288,"code":1964,"language":290,"meta":258,"style":258},"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,1966,1967,1972,1976,1981,1986,1991,1996,2001],{"__ignoreMap":258},[294,1968,1969],{"class":296,"line":297},[294,1970,1971],{},"from dataclasses import dataclass, field\n",[294,1973,1974],{"class":296,"line":303},[294,1975,317],{"emptyLinePlaceholder":30},[294,1977,1978],{"class":296,"line":34},[294,1979,1980],{},"@dataclass\n",[294,1982,1983],{"class":296,"line":314},[294,1984,1985],{},"class Record:\n",[294,1987,1988],{"class":296,"line":320},[294,1989,1990],{},"    id: int\n",[294,1992,1993],{"class":296,"line":326},[294,1994,1995],{},"    name: str\n",[294,1997,1998],{"class":296,"line":332},[294,1999,2000],{},"    tags: list[str] = field(default_factory=list)\n",[294,2002,2003],{"class":296,"line":338},[294,2004,2005],{},"    created_at: str = \"\"\n",[114,2007,2008],{},"自动生成的方法：",[166,2010,2011,2021],{},[169,2012,2013],{},[172,2014,2015,2018],{},[175,2016,2017],{},"方法",[175,2019,2020],{},"何时生成",[185,2022,2023,2033,2042,2051],{},[172,2024,2025,2030],{},[190,2026,2027],{},[60,2028,2029],{},"__init__",[190,2031,2032],{},"默认",[172,2034,2035,2040],{},[190,2036,2037],{},[60,2038,2039],{},"__repr__",[190,2041,2032],{},[172,2043,2044,2049],{},[190,2045,2046],{},[60,2047,2048],{},"__eq__",[190,2050,2032],{},[172,2052,2053,2062],{},[190,2054,2055,2058,2059],{},[60,2056,2057],{},"__setattr__"," \u002F ",[60,2060,2061],{},"__delattr__",[190,2063,2064,2065,2068],{},"仅 ",[60,2066,2067],{},"frozen=True"," 时",[114,2070,2071,2072,2074,2075,2078,2079,2081],{},"原理：",[60,2073,1953],{}," 读类里的类型标注（存在 ",[60,2076,2077],{},"__annotations__","），据此生成 ",[60,2080,2029],{}," 等方法。",[161,2083,2084],{"id":2084},"默认值规则",[166,2086,2087,2096],{},[169,2088,2089],{},[172,2090,2091,2094],{},[175,2092,2093],{},"类型",[175,2095,384],{},[185,2097,2098,2108],{},[172,2099,2100,2103],{},[190,2101,2102],{},"不可变（int \u002F str \u002F bool）",[190,2104,2105],{},[60,2106,2107],{},"count: int = 0",[172,2109,2110,2113],{},[190,2111,2112],{},"可变（list \u002F dict \u002F set）",[190,2114,2115],{},[60,2116,2117],{},"tags: list[str] = field(default_factory=list)",[114,2119,2120],{},"原因：可变默认值会被所有实例共享，和 Day 1 的函数默认参数是同一个坑。规则：有默认值的字段必须放在没默认值的字段后面。",[161,2122,2124],{"id":2123},"frozentrue",[60,2125,2067],{},[250,2127,2129],{"className":288,"code":2128,"language":290,"meta":258,"style":258},"@dataclass(frozen=True)\nclass Item:\n    name: str\n",[60,2130,2131,2136,2141],{"__ignoreMap":258},[294,2132,2133],{"class":296,"line":297},[294,2134,2135],{},"@dataclass(frozen=True)\n",[294,2137,2138],{"class":296,"line":303},[294,2139,2140],{},"class Item:\n",[294,2142,2143],{"class":296,"line":34},[294,2144,1995],{},[473,2146,2147,2150,2156,2165],{},[476,2148,2149],{},"类级别开关，定义时决定",[476,2151,2152,2153],{},"实例创建后字段不可改，赋值抛 ",[60,2154,2155],{},"FrozenInstanceError",[476,2157,2158,2160,2161,2164],{},[60,2159,2029],{}," 内部走 ",[60,2162,2163],{},"object.__setattr__"," 绕过拦截",[476,2166,2167],{},"忘了加就一直可变，运行时无法补救",[161,2169,2171],{"id":2170},"__post_init__",[60,2172,2170],{},[114,2174,2175,2177],{},[60,2176,2029],{}," 执行完之后自动调用，用来做数据校验。",[250,2179,2181],{"className":288,"code":2180,"language":290,"meta":258,"style":258},"def __post_init__(self):\n    if self.price \u003C 0:\n        raise ValueError(\"price 不能为负数\")\n",[60,2182,2183,2188,2193],{"__ignoreMap":258},[294,2184,2185],{"class":296,"line":297},[294,2186,2187],{},"def __post_init__(self):\n",[294,2189,2190],{"class":296,"line":303},[294,2191,2192],{},"    if self.price \u003C 0:\n",[294,2194,2195],{"class":296,"line":34},[294,2196,2197],{},"        raise ValueError(\"price 不能为负数\")\n",[161,2199,2200],{"id":2200},"类型标注",[114,2202,2203],{},"本质：运行时不做检查，只是“说明”，给编辑器、mypy、AI、未来的你看。",[166,2205,2206,2215],{},[169,2207,2208],{},[172,2209,2210,2212],{},[175,2211,384],{},[175,2213,2214],{},"意思",[185,2216,2217,2233,2243,2253,2263,2277,2287],{},[172,2218,2219,2230],{},[190,2220,2221,2058,2224,2058,2227],{},[60,2222,2223],{},"int",[60,2225,2226],{},"str",[60,2228,2229],{},"bool",[190,2231,2232],{},"基本类型",[172,2234,2235,2240],{},[190,2236,2237],{},[60,2238,2239],{},"list[int]",[190,2241,2242],{},"整数列表",[172,2244,2245,2250],{},[190,2246,2247],{},[60,2248,2249],{},"dict[str, int]",[190,2251,2252],{},"键 str、值 int",[172,2254,2255,2260],{},[190,2256,2257],{},[60,2258,2259],{},"Optional[int]",[190,2261,2262],{},"int 或 None",[172,2264,2265,2270],{},[190,2266,2267],{},[60,2268,2269],{},"Union[int, str]",[190,2271,2272,2273,2276],{},"int 或 str（3.10+ 可写 ",[60,2274,2275],{},"int | str","）",[172,2278,2279,2284],{},[190,2280,2281],{},[60,2282,2283],{},"Callable[[int], str]",[190,2285,2286],{},"接收 int、返回 str 的函数",[172,2288,2289,2294],{},[190,2290,2291],{},[60,2292,2293],{},"Any",[190,2295,2296],{},"任何类型，mypy 不检查",[114,2298,2299],{},"用在哪：",[250,2301,2303],{"className":288,"code":2302,"language":290,"meta":258,"style":258},"x: Optional[int] = None            # 变量\ndef f(a: int, b: str) -> bool: ... # 参数 + 返回值\ndef g() -> Optional[Record]: ...   # 返回值可能是 None\nname: Optional[str] = None         # dataclass 字段\n",[60,2304,2305,2310,2315,2320],{"__ignoreMap":258},[294,2306,2307],{"class":296,"line":297},[294,2308,2309],{},"x: Optional[int] = None            # 变量\n",[294,2311,2312],{"class":296,"line":303},[294,2313,2314],{},"def f(a: int, b: str) -> bool: ... # 参数 + 返回值\n",[294,2316,2317],{"class":296,"line":34},[294,2318,2319],{},"def g() -> Optional[Record]: ...   # 返回值可能是 None\n",[294,2321,2322],{"class":296,"line":314},[294,2323,2324],{},"name: Optional[str] = None         # dataclass 字段\n",[114,2326,2327,2328,2331,2332,2335,2336,2338],{},"语义：",[60,2329,2330],{},"Optional[T]"," = ",[60,2333,2334],{},"T"," 或 ",[60,2337,1724],{},"，不限于返回值。",[161,2340,2341],{"id":2341},"mypy",[114,2343,2344],{},"静态类型检查工具，运行前检查，不执行代码。",[250,2346,2350],{"className":2347,"code":2348,"language":2349,"meta":258,"style":258},"language-bash shiki shiki-themes github-light github-dark","pip install mypy\nmypy 文件名.py\n","bash",[60,2351,2352,2365],{"__ignoreMap":258},[294,2353,2354,2358,2362],{"class":296,"line":297},[294,2355,2357],{"class":2356},"sScJk","pip",[294,2359,2361],{"class":2360},"sZZnC"," install",[294,2363,2364],{"class":2360}," mypy\n",[294,2366,2367,2369],{"class":296,"line":303},[294,2368,2341],{"class":2356},[294,2370,2371],{"class":2360}," 文件名.py\n",[114,2373,2374,2375,2378],{},"能查什么：参数类型不对、返回值类型不对、",[60,2376,2377],{},"Optional"," 没判空就用、访问不存在的属性、变量被赋了不兼容的类型。",[114,2380,2381,2382,2385],{},"不能查什么：具体值是否合理（比如 ",[60,2383,2384],{},"price = -999"," 是合法的 float）、业务逻辑错误。",[114,2387,2388,2389],{},"为什么有用：提前发现运行时不一定触发的 bug、代码即文档、编辑器补全更准、给 AI 的输出做质检。",[118,2390,2391],{},"AI 写代码，mypy 检查代码。写的人越快，检查越重要。",[161,2393,2395],{"id":2394},"ai-时代的补充","AI 时代的补充",[473,2397,2398,2407,2410],{},[476,2399,2400,2401,1332,2403,2406],{},"AI 也会犯类型错误，甚至可能拿 ",[60,2402,2293],{},[60,2404,2405],{},"# type: ignore"," 糊弄 mypy",[476,2408,2409],{},"你需要能看懂 mypy 报错，判断 AI 是真修复还是掩盖问题",[476,2411,2412],{},"类型标注是给 AI、编辑器、未来的你看的“接口契约”",[161,2414,1694],{"id":2415},"今天完成的实验-1",[166,2417,2418,2431],{},[169,2419,2420],{},[172,2421,2422,2425,2428],{},[175,2423,2424],{},"实验",[175,2426,2427],{},"内容",[175,2429,2430],{},"结果",[185,2432,2433,2454,2469,2485,2505],{},[172,2434,2435,2438,2451],{},[190,2436,2437],{},"1",[190,2439,2440,2443,2444,2447,2448],{},[60,2441,2442],{},"Record"," dataclass，",[60,2445,2446],{},"tags"," 用 ",[60,2449,2450],{},"default_factory",[190,2452,2453],{},"✅",[172,2455,2456,2459,2464],{},[190,2457,2458],{},"2",[190,2460,2461,2463],{},[60,2462,2067],{}," 赋值",[190,2465,2466,2467],{},"✅ ",[60,2468,2155],{},[172,2470,2471,2474,2483],{},[190,2472,2473],{},"3",[190,2475,2476,2478,2479,2482],{},[60,2477,2170],{}," 做 ",[60,2480,2481],{},"price >= 0"," 校验",[190,2484,2453],{},[172,2486,2487,2490,2503],{},[190,2488,2489],{},"4",[190,2491,2492,2493,2058,2496,2058,2499,2502],{},"补全 ",[60,2494,2495],{},"find_record",[60,2497,2498],{},"total_price",[60,2500,2501],{},"format_name"," 类型标注",[190,2504,2453],{},[172,2506,2507,2510,2513],{},[190,2508,2509],{},"5",[190,2511,2512],{},"制造 3 个类型错误并修复",[190,2514,2466,2515],{},[60,2516,2517],{},"Success: no issues found",[114,2519,2520],{},[118,2521,2522,2523,2525,2526,2528,2529,2531],{},"Day 4 一句话总结：",[60,2524,1953],{}," 用字段标注自动生成样板方法；可变默认值必须 ",[60,2527,2450],{},"；",[60,2530,2067],{}," 让实例只读；类型标注运行时不管，mypy 在运行前管；在 AI 写代码的时代，类型标注和 mypy 是给 AI 输出做质检的最低成本工具。",[148,2533,2534],{"id":2534},"来源与延伸阅读",[473,2536,2537,2551,2565,2572],{},[476,2538,2539,2545,2546],{},[123,2540,2544],{"href":2541,"rel":2542},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fmodules.html",[2543],"nofollow","Python Tutorial：Modules"," 与 ",[123,2547,2550],{"href":2548,"rel":2549},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fmodules.html#packages",[2543],"Packages",[476,2552,2553,2545,2559],{},[123,2554,2557],{"href":2555,"rel":2556},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fsys.html#sys.path",[2543],[60,2558,787],{},[123,2560,2563],{"href":2561,"rel":2562},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fsys.html#sys.modules",[2543],[60,2564,778],{},[476,2566,2567],{},[123,2568,2571],{"href":2569,"rel":2570},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Ferrors.html",[2543],"Python Tutorial：Errors and Exceptions",[476,2573,2574],{},[123,2575,2578],{"href":2576,"rel":2577},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fexceptions.html#exception-hierarchy",[2543],"内置异常层级",[2580,2581,2582],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":258,"searchDepth":303,"depth":303,"links":2584},[2585,2600,2612,2623],{"id":150,"depth":303,"text":151,"children":2586},[2587,2588,2589,2591,2593,2595,2596,2597,2599],{"id":163,"depth":34,"text":164},{"id":284,"depth":34,"text":285},{"id":462,"depth":34,"text":2590},"__name__ 与 main 守卫",{"id":562,"depth":34,"text":2592},"python file.py vs python -m pkg.mod",{"id":688,"depth":34,"text":2594},"__init__.py 与 re-export",{"id":764,"depth":34,"text":765},{"id":815,"depth":34,"text":815},{"id":914,"depth":34,"text":2598},"重点实验：from x import y 到底绑定了什么？",{"id":1069,"depth":34,"text":1069},{"id":1135,"depth":303,"text":1136,"children":2601},[2602,2603,2604,2606,2607,2608,2609,2610,2611],{"id":1146,"depth":34,"text":1147},{"id":1269,"depth":34,"text":1270},{"id":1290,"depth":34,"text":2605},"裸 except: vs except Exception:",{"id":1366,"depth":34,"text":1367},{"id":1451,"depth":34,"text":1451},{"id":1537,"depth":34,"text":1538},{"id":1604,"depth":34,"text":1604},{"id":1694,"depth":34,"text":1694},{"id":1876,"depth":34,"text":1069},{"id":1939,"depth":303,"text":1940,"children":2613},[2614,2616,2617,2618,2619,2620,2621,2622],{"id":1950,"depth":34,"text":2615},"@dataclass 是什么",{"id":2084,"depth":34,"text":2084},{"id":2123,"depth":34,"text":2067},{"id":2170,"depth":34,"text":2170},{"id":2200,"depth":34,"text":2200},{"id":2341,"depth":34,"text":2341},{"id":2394,"depth":34,"text":2395},{"id":2415,"depth":34,"text":1694},{"id":2534,"depth":303,"text":2534},"学习","2026-09-21","Week 2 的实际产出：Day 2 模块与 import 机制、Day 3 异常处理、Day 4 dataclass 与类型标注的完整总结与实验记录。","md",false,{},"\u002Fwriting\u002F2026-09-21-python-week-2-outcomes-modules-exceptions","2026-09-21T14:09:50+08:00",{"title":109,"description":2626},"python-week-2-outcomes-modules-exceptions","writing\u002F2026-09-21-python-week-2-outcomes-modules-exceptions",[2636,2637,2638,821,2639,2640,1451,120],"Python","Week2","模块","异常处理","EAFP","week-2","2026-09-23","OeoDJBPDZXAjyW_ol1wPam3_XW7Wi0s9zon7Ga0aX7A",[2645,4536,5138,7374,7911,9805,11084,12544,13000,13218,13479,13825,14270,17693,17867,18066],{"id":108,"title":109,"body":2646,"category":2624,"date":2625,"description":2626,"extension":2627,"hidden":2628,"kind":132,"meta":4533,"navigation":30,"path":2630,"publishedAt":2631,"seo":4534,"series":101,"slug":2633,"stem":2634,"tags":4535,"unit":2641,"updated":2642,"__hash__":2643},{"type":111,"value":2647,"toc":4498},[2648,2654,2664,2666,2672,2674,2728,2730,2735,2745,2751,2753,2813,2883,2887,2891,2903,2915,2917,2963,2969,3037,3039,3047,3059,3065,3069,3073,3091,3103,3119,3121,3123,3125,3141,3149,3155,3157,3161,3185,3217,3219,3227,3233,3237,3265,3293,3295,3300,3302,3338,3346,3348,3376,3392,3394,3400,3402,3438,3468,3470,3494,3496,3504,3509,3515,3559,3565,3567,3575,3591,3611,3613,3618,3624,3630,3632,3668,3673,3675,3683,3685,3697,3699,3701,3703,3723,3725,3745,3747,3749,3753,3755,3759,3779,3785,3787,3807,3809,3811,3827,3829,3831,3837,3851,3855,3861,3865,3881,3887,3891,3915,3921,3923,3928,3936,3960,3970,3972,4010,4016,4018,4024,4028,4032,4068,4070,4118,4126,4128,4156,4158,4162,4178,4194,4198,4202,4218,4220,4222,4296,4298,4318,4326,4328,4330,4348,4352,4356,4360,4362,4374,4376,4452,4462,4464,4496],[114,2649,116,2650,121,2652,127],{},[118,2651,120],{},[123,2653,126],{"href":125},[114,2655,2656,133,2658,138,2660,138,2662],{},[118,2657,132],{},[123,2659,137],{"href":136},[123,2661,142],{"href":141},[123,2663,146],{"href":145},[148,2665,151],{"id":150},[114,2667,2668,133,2670],{},[118,2669,156],{},[123,2671,159],{"href":125},[161,2673,164],{"id":163},[166,2675,2676,2686],{},[169,2677,2678],{},[172,2679,2680,2682,2684],{},[175,2681,177],{},[175,2683,180],{},[175,2685,183],{},[185,2687,2688,2702,2716],{},[172,2689,2690,2692,2696],{},[190,2691,192],{},[190,2693,195,2694,199],{},[60,2695,198],{},[190,2697,2698,205,2700],{},[60,2699,204],{},[60,2701,208],{},[172,2703,2704,2706,2710],{},[190,2705,213],{},[190,2707,216,2708],{},[60,2709,219],{},[190,2711,2712,225,2714],{},[60,2713,224],{},[60,2715,228],{},[172,2717,2718,2720,2722],{},[190,2719,233],{},[190,2721,236],{},[190,2723,2724,242,2726],{},[60,2725,241],{},[60,2727,245],{},[114,2729,248],{},[250,2731,2733],{"className":2732,"code":254,"language":255},[253],[60,2734,254],{"__ignoreMap":258},[114,2736,2737,263,2739,266,2741,269,2743,273],{},[60,2738,228],{},[60,2740,208],{},[60,2742,245],{},[60,2744,272],{},[114,2746,2747],{},[118,2748,278,2749,281],{},[60,2750,198],{},[161,2752,285],{"id":284},[250,2754,2755],{"className":288,"code":289,"language":290,"meta":258,"style":258},[60,2756,2757,2761,2765,2769,2773,2777,2781,2785,2789,2793,2797,2801,2805,2809],{"__ignoreMap":258},[294,2758,2759],{"class":296,"line":297},[294,2760,300],{},[294,2762,2763],{"class":296,"line":303},[294,2764,306],{},[294,2766,2767],{"class":296,"line":34},[294,2768,311],{},[294,2770,2771],{"class":296,"line":314},[294,2772,317],{"emptyLinePlaceholder":30},[294,2774,2775],{"class":296,"line":320},[294,2776,323],{},[294,2778,2779],{"class":296,"line":326},[294,2780,329],{},[294,2782,2783],{"class":296,"line":332},[294,2784,335],{},[294,2786,2787],{"class":296,"line":338},[294,2788,317],{"emptyLinePlaceholder":30},[294,2790,2791],{"class":296,"line":343},[294,2792,346],{},[294,2794,2795],{"class":296,"line":349},[294,2796,352],{},[294,2798,2799],{"class":296,"line":355},[294,2800,317],{"emptyLinePlaceholder":30},[294,2802,2803],{"class":296,"line":360},[294,2804,363],{},[294,2806,2807],{"class":296,"line":366},[294,2808,369],{},[294,2810,2811],{"class":296,"line":372},[294,2812,375],{},[166,2814,2815,2825],{},[169,2816,2817],{},[172,2818,2819,2821,2823],{},[175,2820,384],{},[175,2822,387],{},[175,2824,390],{},[185,2826,2827,2841,2855,2869],{},[172,2828,2829,2833,2837],{},[190,2830,2831],{},[60,2832,399],{},[190,2834,2835],{},[60,2836,228],{},[190,2838,2839],{},[60,2840,408],{},[172,2842,2843,2847,2851],{},[190,2844,2845],{},[60,2846,415],{},[190,2848,2849],{},[60,2850,420],{},[190,2852,2853],{},[60,2854,425],{},[172,2856,2857,2861,2865],{},[190,2858,2859],{},[60,2860,432],{},[190,2862,2863],{},[60,2864,437],{},[190,2866,2867],{},[60,2868,442],{},[172,2870,2871,2875,2879],{},[190,2872,2873],{},[60,2874,449],{},[190,2876,2877],{},[60,2878,454],{},[190,2880,2881],{},[60,2882,459],{},[161,2884,2885,466],{"id":462},[60,2886,465],{},[114,2888,469,2889,133],{},[60,2890,465],{},[473,2892,2893,2897],{},[476,2894,478,2895],{},[60,2896,481],{},[476,2898,484,2899,487,2901],{},[60,2900,465],{},[60,2902,490],{},[250,2904,2905],{"className":288,"code":493,"language":290,"meta":258,"style":258},[60,2906,2907,2911],{"__ignoreMap":258},[294,2908,2909],{"class":296,"line":297},[294,2910,500],{},[294,2912,2913],{"class":296,"line":303},[294,2914,505],{},[114,2916,508],{},[166,2918,2919,2931],{},[169,2920,2921],{},[172,2922,2923,2925],{},[175,2924,517],{},[175,2926,2927,522,2929],{},[60,2928,204],{},[60,2930,465],{},[185,2932,2933,2943,2953],{},[172,2934,2935,2939],{},[190,2936,2937],{},[60,2938,533],{},[190,2940,2941],{},[60,2942,538],{},[172,2944,2945,2949],{},[190,2946,2947],{},[60,2948,545],{},[190,2950,2951],{},[60,2952,538],{},[172,2954,2955,2959],{},[190,2956,2957],{},[60,2958,399],{},[190,2960,2961],{},[60,2962,490],{},[161,2964,2965,566,2967],{"id":562},[60,2966,565],{},[60,2968,569],{},[166,2970,2971,2985],{},[169,2972,2973],{},[172,2974,2975,2977,2981],{},[175,2976,578],{},[175,2978,2979],{},[60,2980,533],{},[175,2982,2983],{},[60,2984,545],{},[185,2986,2987,3001,3013,3021,3029],{},[172,2988,2989,2993,2997],{},[190,2990,2991],{},[60,2992,465],{},[190,2994,2995],{},[60,2996,538],{},[190,2998,2999],{},[60,3000,538],{},[172,3002,3003,3007,3011],{},[190,3004,3005],{},[60,3006,609],{},[190,3008,3009],{},[60,3010,224],{},[190,3012,616],{},[172,3014,3015,3017,3019],{},[190,3016,621],{},[190,3018,624],{},[190,3020,627],{},[172,3022,3023,3025,3027],{},[190,3024,632],{},[190,3026,635],{},[190,3028,638],{},[172,3030,3031,3033,3035],{},[190,3032,643],{},[190,3034,646],{},[190,3036,649],{},[114,3038,652],{},[250,3040,3041],{"className":288,"code":655,"language":290,"meta":258,"style":258},[60,3042,3043],{"__ignoreMap":258},[294,3044,3045],{"class":296,"line":297},[294,3046,655],{},[473,3048,3049,3055],{},[476,3050,3051,668,3053],{},[60,3052,533],{},[60,3054,671],{},[476,3056,3057,676],{},[60,3058,545],{},[114,3060,679,3061,682,3063,685],{},[60,3062,565],{},[60,3064,569],{},[161,3066,3067,691],{"id":688},[60,3068,219],{},[114,3070,3071,696],{},[60,3072,219],{},[473,3074,3075,3077,3079,3083,3089],{},[476,3076,701],{},[476,3078,704],{},[476,3080,707,3081],{},[60,3082,710],{},[476,3084,713,3085,717,3087],{},[60,3086,716],{},[60,3088,720],{},[476,3090,723],{},[250,3092,3093],{"className":288,"code":726,"language":290,"meta":258,"style":258},[60,3094,3095,3099],{"__ignoreMap":258},[294,3096,3097],{"class":296,"line":297},[294,3098,733],{},[294,3100,3101],{"class":296,"line":303},[294,3102,738],{},[250,3104,3105],{"className":288,"code":741,"language":290,"meta":258,"style":258},[60,3106,3107,3111,3115],{"__ignoreMap":258},[294,3108,3109],{"class":296,"line":297},[294,3110,748],{},[294,3112,3113],{"class":296,"line":303},[294,3114,753],{},[294,3116,3117],{"class":296,"line":34},[294,3118,758],{},[114,3120,761],{},[161,3122,765],{"id":764},[114,3124,768],{},[770,3126,3127,3133],{},[476,3128,3129,780],{},[118,3130,3131,779],{},[60,3132,778],{},[476,3134,3135,788,3139,791],{},[118,3136,3137],{},[60,3138,787],{},[60,3140,787],{},[114,3142,3143,796,3145,800,3147,804],{},[60,3144,787],{},[60,3146,799],{},[60,3148,803],{},[114,3150,3151],{},[118,3152,809,3153,812],{},[60,3154,778],{},[161,3156,815],{"id":815},[114,3158,818,3159,822],{},[60,3160,821],{},[250,3162,3163],{"className":288,"code":825,"language":290,"meta":258,"style":258},[60,3164,3165,3169,3173,3177,3181],{"__ignoreMap":258},[294,3166,3167],{"class":296,"line":297},[294,3168,832],{},[294,3170,3171],{"class":296,"line":303},[294,3172,837],{},[294,3174,3175],{"class":296,"line":34},[294,3176,317],{"emptyLinePlaceholder":30},[294,3178,3179],{"class":296,"line":314},[294,3180,846],{},[294,3182,3183],{"class":296,"line":320},[294,3184,851],{},[250,3186,3187],{"className":288,"code":854,"language":290,"meta":258,"style":258},[60,3188,3189,3193,3197,3201,3205,3209,3213],{"__ignoreMap":258},[294,3190,3191],{"class":296,"line":297},[294,3192,861],{},[294,3194,3195],{"class":296,"line":303},[294,3196,866],{},[294,3198,3199],{"class":296,"line":34},[294,3200,317],{"emptyLinePlaceholder":30},[294,3202,3203],{"class":296,"line":314},[294,3204,875],{},[294,3206,3207],{"class":296,"line":320},[294,3208,880],{},[294,3210,3211],{"class":296,"line":326},[294,3212,317],{"emptyLinePlaceholder":30},[294,3214,3215],{"class":296,"line":332},[294,3216,889],{},[114,3218,892],{},[473,3220,3221,3225],{},[476,3222,897,3223,900],{},[60,3224,821],{},[476,3226,903],{},[114,3228,3229],{},[118,3230,908,3231,911],{},[60,3232,821],{},[161,3234,915,3235,919],{"id":914},[60,3236,918],{},[250,3238,3239],{"className":288,"code":922,"language":290,"meta":258,"style":258},[60,3240,3241,3245,3249,3253,3257,3261],{"__ignoreMap":258},[294,3242,3243],{"class":296,"line":297},[294,3244,929],{},[294,3246,3247],{"class":296,"line":303},[294,3248,934],{},[294,3250,3251],{"class":296,"line":34},[294,3252,317],{"emptyLinePlaceholder":30},[294,3254,3255],{"class":296,"line":314},[294,3256,943],{},[294,3258,3259],{"class":296,"line":320},[294,3260,948],{},[294,3262,3263],{"class":296,"line":326},[294,3264,953],{},[250,3266,3267],{"className":288,"code":956,"language":290,"meta":258,"style":258},[60,3268,3269,3273,3277,3281,3285,3289],{"__ignoreMap":258},[294,3270,3271],{"class":296,"line":297},[294,3272,963],{},[294,3274,3275],{"class":296,"line":303},[294,3276,968],{},[294,3278,3279],{"class":296,"line":34},[294,3280,973],{},[294,3282,3283],{"class":296,"line":314},[294,3284,317],{"emptyLinePlaceholder":30},[294,3286,3287],{"class":296,"line":320},[294,3288,982],{},[294,3290,3291],{"class":296,"line":326},[294,3292,987],{},[114,3294,990],{},[250,3296,3298],{"className":3297,"code":994,"language":255},[253],[60,3299,994],{"__ignoreMap":258},[114,3301,999],{},[473,3303,3304,3310,3322,3330],{},[476,3305,3306,1007,3308,1011],{},[60,3307,1006],{},[60,3309,1010],{},[476,3311,3312,1017,3314,1020,3316,1024,3318,1028,3320,1032],{},[60,3313,1016],{},[60,3315,1010],{},[60,3317,1023],{},[60,3319,1027],{},[60,3321,1031],{},[476,3323,3324,1038,3326,1042,3328,1045],{},[60,3325,1037],{},[60,3327,1041],{},[60,3329,1031],{},[476,3331,3332,522,3334,1052,3336,1032],{},[60,3333,1027],{},[60,3335,1031],{},[60,3337,1023],{},[114,3339,3340],{},[118,3341,1059,3342,1062,3344,1066],{},[60,3343,918],{},[60,3345,1065],{},[161,3347,1069],{"id":1069},[770,3349,3350,3356,3364,3372],{},[476,3351,3352,1077,3354,1080],{},[118,3353,1076],{},[60,3355,778],{},[476,3357,3358,1088,3362,1091],{},[118,3359,3360,1087],{},[60,3361,219],{},[60,3363,716],{},[476,3365,3366,1102],{},[118,3367,3368,1098,3370,1101],{},[60,3369,565],{},[60,3371,569],{},[476,3373,3374,1108],{},[118,3375,1107],{},[114,3377,3378],{},[118,3379,1113,3380,1116,3382,1119,3384,1122,3386,1125,3388,1128,3390,1132],{},[60,3381,198],{},[60,3383,219],{},[60,3385,778],{},[60,3387,787],{},[60,3389,918],{},[60,3391,1131],{},[148,3393,1136],{"id":1135},[114,3395,3396,133,3398],{},[118,3397,156],{},[123,3399,1143],{"href":125},[161,3401,1147],{"id":1146},[250,3403,3404],{"className":288,"code":1150,"language":290,"meta":258,"style":258},[60,3405,3406,3410,3414,3418,3422,3426,3430,3434],{"__ignoreMap":258},[294,3407,3408],{"class":296,"line":297},[294,3409,1157],{},[294,3411,3412],{"class":296,"line":303},[294,3413,1162],{},[294,3415,3416],{"class":296,"line":34},[294,3417,1167],{},[294,3419,3420],{"class":296,"line":314},[294,3421,1172],{},[294,3423,3424],{"class":296,"line":320},[294,3425,1177],{},[294,3427,3428],{"class":296,"line":326},[294,3429,1182],{},[294,3431,3432],{"class":296,"line":332},[294,3433,1187],{},[294,3435,3436],{"class":296,"line":338},[294,3437,1192],{},[166,3439,3440,3448],{},[169,3441,3442],{},[172,3443,3444,3446],{},[175,3445,1201],{},[175,3447,1204],{},[185,3449,3450,3456,3462],{},[172,3451,3452,3454],{},[190,3453,1211],{},[190,3455,1214],{},[172,3457,3458,3460],{},[190,3459,1219],{},[190,3461,1222],{},[172,3463,3464,3466],{},[190,3465,1227],{},[190,3467,1230],{},[114,3469,1233],{},[473,3471,3472,3476,3482],{},[476,3473,3474,1241],{},[60,3475,1240],{},[476,3477,3478,1247,3480,1251],{},[60,3479,1246],{},[60,3481,1250],{},[476,3483,3484,522,3486,1258,3488,1261,3490,1264,3492,1032],{},[60,3485,1246],{},[60,3487,1250],{},[60,3489,1250],{},[60,3491,1246],{},[60,3493,1250],{},[161,3495,1270],{"id":1269},[473,3497,3498,3502],{},[476,3499,3500,1278],{},[60,3501,1277],{},[476,3503,1281],{},[250,3505,3507],{"className":3506,"code":1285,"language":255},[253],[60,3508,1285],{"__ignoreMap":258},[161,3510,1291,3511,566,3513],{"id":1290},[60,3512,1294],{},[60,3514,1297],{},[166,3516,3517,3527],{},[169,3518,3519],{},[172,3520,3521,3523,3525],{},[175,3522,384],{},[175,3524,1308],{},[175,3526,1311],{},[185,3528,3529,3545],{},[172,3530,3531,3535,3539],{},[190,3532,3533],{},[60,3534,1297],{},[190,3536,3537,1325],{},[60,3538,1324],{},[190,3540,1328,3541,1332,3543],{},[60,3542,1331],{},[60,3544,1335],{},[172,3546,3547,3551,3555],{},[190,3548,1291,3549],{},[60,3550,1294],{},[190,3552,1344,3553,1348],{},[60,3554,1347],{},[190,3556,1351,3557,1355],{},[60,3558,1354],{},[114,3560,3561],{},[118,3562,1360,3563,1363],{},[60,3564,1294],{},[161,3566,1367],{"id":1366},[250,3568,3569],{"className":288,"code":1370,"language":290,"meta":258,"style":258},[60,3570,3571],{"__ignoreMap":258},[294,3572,3573],{"class":296,"line":297},[294,3574,1370],{},[250,3576,3577],{"className":288,"code":1379,"language":290,"meta":258,"style":258},[60,3578,3579,3583,3587],{"__ignoreMap":258},[294,3580,3581],{"class":296,"line":297},[294,3582,1167],{},[294,3584,3585],{"class":296,"line":303},[294,3586,1390],{},[294,3588,3589],{"class":296,"line":34},[294,3590,1395],{},[250,3592,3593],{"className":288,"code":1398,"language":290,"meta":258,"style":258},[60,3594,3595,3599,3603,3607],{"__ignoreMap":258},[294,3596,3597],{"class":296,"line":297},[294,3598,1157],{},[294,3600,3601],{"class":296,"line":303},[294,3602,1409],{},[294,3604,3605],{"class":296,"line":34},[294,3606,1414],{},[294,3608,3609],{"class":296,"line":314},[294,3610,1419],{},[114,3612,1422],{},[250,3614,3616],{"className":3615,"code":1426,"language":255},[253],[60,3617,1426],{"__ignoreMap":258},[114,3619,1431,3620,1435,3622,1439],{},[60,3621,1434],{},[60,3623,1438],{},[114,3625,3626],{},[118,3627,1444,3628,1448],{},[60,3629,1447],{},[161,3631,1451],{"id":1451},[250,3633,3634],{"className":288,"code":1454,"language":290,"meta":258,"style":258},[60,3635,3636,3640,3644,3648,3652,3656,3660,3664],{"__ignoreMap":258},[294,3637,3638],{"class":296,"line":297},[294,3639,1461],{},[294,3641,3642],{"class":296,"line":303},[294,3643,1466],{},[294,3645,3646],{"class":296,"line":34},[294,3647,317],{"emptyLinePlaceholder":30},[294,3649,3650],{"class":296,"line":314},[294,3651,1475],{},[294,3653,3654],{"class":296,"line":320},[294,3655,1466],{},[294,3657,3658],{"class":296,"line":326},[294,3659,317],{"emptyLinePlaceholder":30},[294,3661,3662],{"class":296,"line":332},[294,3663,1488],{},[294,3665,3666],{"class":296,"line":338},[294,3667,1466],{},[250,3669,3671],{"className":3670,"code":1496,"language":255},[253],[60,3672,1496],{"__ignoreMap":258},[114,3674,1501],{},[114,3676,1504,3677,1508,3679,1332,3681,1032],{},[60,3678,1507],{},[60,3680,1511],{},[60,3682,1514],{},[114,3684,1517],{},[250,3686,3687],{"className":288,"code":1520,"language":290,"meta":258,"style":258},[60,3688,3689,3693],{"__ignoreMap":258},[294,3690,3691],{"class":296,"line":297},[294,3692,1527],{},[294,3694,3695],{"class":296,"line":303},[294,3696,1466],{},[114,3698,1534],{},[161,3700,1538],{"id":1537},[114,3702,1541],{},[250,3704,3705],{"className":288,"code":1544,"language":290,"meta":258,"style":258},[60,3706,3707,3711,3715,3719],{"__ignoreMap":258},[294,3708,3709],{"class":296,"line":297},[294,3710,1551],{},[294,3712,3713],{"class":296,"line":303},[294,3714,1556],{},[294,3716,3717],{"class":296,"line":34},[294,3718,1177],{},[294,3720,3721],{"class":296,"line":314},[294,3722,1565],{},[114,3724,1568],{},[250,3726,3727],{"className":288,"code":1571,"language":290,"meta":258,"style":258},[60,3728,3729,3733,3737,3741],{"__ignoreMap":258},[294,3730,3731],{"class":296,"line":297},[294,3732,1157],{},[294,3734,3735],{"class":296,"line":303},[294,3736,1556],{},[294,3738,3739],{"class":296,"line":34},[294,3740,1586],{},[294,3742,3743],{"class":296,"line":314},[294,3744,1565],{},[114,3746,1593],{},[114,3748,1596],{},[114,3750,3751],{},[118,3752,1601],{},[161,3754,1604],{"id":1604},[114,3756,1291,3757,133],{},[60,3758,1294],{},[250,3760,3761],{"className":288,"code":1611,"language":290,"meta":258,"style":258},[60,3762,3763,3767,3771,3775],{"__ignoreMap":258},[294,3764,3765],{"class":296,"line":297},[294,3766,1157],{},[294,3768,3769],{"class":296,"line":303},[294,3770,1622],{},[294,3772,3773],{"class":296,"line":34},[294,3774,1627],{},[294,3776,3777],{"class":296,"line":314},[294,3778,1466],{},[114,3780,1634,3781,1332,3783,1032],{},[60,3782,1331],{},[60,3784,1335],{},[114,3786,1641],{},[250,3788,3789],{"className":288,"code":1644,"language":290,"meta":258,"style":258},[60,3790,3791,3795,3799,3803],{"__ignoreMap":258},[294,3792,3793],{"class":296,"line":297},[294,3794,1157],{},[294,3796,3797],{"class":296,"line":303},[294,3798,1622],{},[294,3800,3801],{"class":296,"line":34},[294,3802,1659],{},[294,3804,3805],{"class":296,"line":314},[294,3806,1466],{},[114,3808,1666],{},[114,3810,1669],{},[250,3812,3813],{"className":288,"code":1672,"language":290,"meta":258,"style":258},[60,3814,3815,3819,3823],{"__ignoreMap":258},[294,3816,3817],{"class":296,"line":297},[294,3818,1659],{},[294,3820,3821],{"class":296,"line":303},[294,3822,1683],{},[294,3824,3825],{"class":296,"line":34},[294,3826,1688],{},[114,3828,1691],{},[161,3830,1694],{"id":1694},[114,3832,3833],{},[118,3834,1699,3835],{},[60,3836,1702],{},[114,3838,1705,3839,1709,3841,1332,3843,1709,3845,1718,3847,1721,3849,1032],{},[60,3840,1708],{},[60,3842,1438],{},[60,3844,1714],{},[60,3846,1717],{},[60,3848,1240],{},[60,3850,1724],{},[114,3852,3853],{},[118,3854,1729],{},[114,3856,1732,3857,1735,3859,1032],{},[60,3858,1214],{},[60,3860,1222],{},[114,3862,3863],{},[118,3864,1742],{},[250,3866,3867],{"className":288,"code":1745,"language":290,"meta":258,"style":258},[60,3868,3869,3873,3877],{"__ignoreMap":258},[294,3870,3871],{"class":296,"line":297},[294,3872,1752],{},[294,3874,3875],{"class":296,"line":303},[294,3876,1757],{},[294,3878,3879],{"class":296,"line":34},[294,3880,1762],{},[114,3882,1765,3883,1769,3885,1032],{},[60,3884,1768],{},[60,3886,1772],{},[114,3888,3889],{},[118,3890,1777],{},[250,3892,3893],{"className":288,"code":1780,"language":290,"meta":258,"style":258},[60,3894,3895,3899,3903,3907,3911],{"__ignoreMap":258},[294,3896,3897],{"class":296,"line":297},[294,3898,1787],{},[294,3900,3901],{"class":296,"line":303},[294,3902,1792],{},[294,3904,3905],{"class":296,"line":34},[294,3906,1797],{},[294,3908,3909],{"class":296,"line":314},[294,3910,1802],{},[294,3912,3913],{"class":296,"line":320},[294,3914,1807],{},[114,3916,3917],{},[118,3918,915,3919],{},[60,3920,1814],{},[114,3922,1817],{},[250,3924,3926],{"className":3925,"code":1426,"language":255},[253],[60,3927,1426],{"__ignoreMap":258},[114,3929,3930],{},[118,3931,1827,3932,522,3934],{},[60,3933,1246],{},[60,3935,1250],{},[250,3937,3938],{"className":288,"code":1834,"language":290,"meta":258,"style":258},[60,3939,3940,3944,3948,3952,3956],{"__ignoreMap":258},[294,3941,3942],{"class":296,"line":297},[294,3943,1841],{},[294,3945,3946],{"class":296,"line":303},[294,3947,1792],{},[294,3949,3950],{"class":296,"line":34},[294,3951,1850],{},[294,3953,3954],{"class":296,"line":314},[294,3955,1855],{},[294,3957,3958],{"class":296,"line":320},[294,3959,1860],{},[114,3961,1863,3962,1867,3964,522,3966,1258,3968,1032],{},[60,3963,1866],{},[60,3965,1246],{},[60,3967,1250],{},[60,3969,1250],{},[161,3971,1069],{"id":1876},[770,3973,3974,3990,4002,4006],{},[476,3975,3976,1889,3982,1892,3984,1895,3986,1332,3988,1900],{},[118,3977,3978,1886,3980,1101],{},[60,3979,1885],{},[60,3981,1294],{},[60,3983,1885],{},[60,3985,1294],{},[60,3987,1331],{},[60,3989,1335],{},[476,3991,3992,1889,3998,1913,4000,1916],{},[118,3993,3994,1907,3996,1910],{},[60,3995,1240],{},[60,3997,1246],{},[60,3999,1240],{},[60,4001,1246],{},[476,4003,4004,1922],{},[118,4005,1921],{},[476,4007,4008,1928],{},[118,4009,1927],{},[114,4011,4012],{},[118,4013,1933,4014,1936],{},[60,4015,1447],{},[148,4017,1940],{"id":1939},[114,4019,4020,133,4022],{},[118,4021,156],{},[123,4023,1947],{"href":125},[161,4025,4026,1954],{"id":1950},[60,4027,1953],{},[114,4029,1957,4030,1961],{},[60,4031,1960],{},[250,4033,4034],{"className":288,"code":1964,"language":290,"meta":258,"style":258},[60,4035,4036,4040,4044,4048,4052,4056,4060,4064],{"__ignoreMap":258},[294,4037,4038],{"class":296,"line":297},[294,4039,1971],{},[294,4041,4042],{"class":296,"line":303},[294,4043,317],{"emptyLinePlaceholder":30},[294,4045,4046],{"class":296,"line":34},[294,4047,1980],{},[294,4049,4050],{"class":296,"line":314},[294,4051,1985],{},[294,4053,4054],{"class":296,"line":320},[294,4055,1990],{},[294,4057,4058],{"class":296,"line":326},[294,4059,1995],{},[294,4061,4062],{"class":296,"line":332},[294,4063,2000],{},[294,4065,4066],{"class":296,"line":338},[294,4067,2005],{},[114,4069,2008],{},[166,4071,4072,4080],{},[169,4073,4074],{},[172,4075,4076,4078],{},[175,4077,2017],{},[175,4079,2020],{},[185,4081,4082,4090,4098,4106],{},[172,4083,4084,4088],{},[190,4085,4086],{},[60,4087,2029],{},[190,4089,2032],{},[172,4091,4092,4096],{},[190,4093,4094],{},[60,4095,2039],{},[190,4097,2032],{},[172,4099,4100,4104],{},[190,4101,4102],{},[60,4103,2048],{},[190,4105,2032],{},[172,4107,4108,4114],{},[190,4109,4110,2058,4112],{},[60,4111,2057],{},[60,4113,2061],{},[190,4115,2064,4116,2068],{},[60,4117,2067],{},[114,4119,2071,4120,2074,4122,2078,4124,2081],{},[60,4121,1953],{},[60,4123,2077],{},[60,4125,2029],{},[161,4127,2084],{"id":2084},[166,4129,4130,4138],{},[169,4131,4132],{},[172,4133,4134,4136],{},[175,4135,2093],{},[175,4137,384],{},[185,4139,4140,4148],{},[172,4141,4142,4144],{},[190,4143,2102],{},[190,4145,4146],{},[60,4147,2107],{},[172,4149,4150,4152],{},[190,4151,2112],{},[190,4153,4154],{},[60,4155,2117],{},[114,4157,2120],{},[161,4159,4160],{"id":2123},[60,4161,2067],{},[250,4163,4164],{"className":288,"code":2128,"language":290,"meta":258,"style":258},[60,4165,4166,4170,4174],{"__ignoreMap":258},[294,4167,4168],{"class":296,"line":297},[294,4169,2135],{},[294,4171,4172],{"class":296,"line":303},[294,4173,2140],{},[294,4175,4176],{"class":296,"line":34},[294,4177,1995],{},[473,4179,4180,4182,4186,4192],{},[476,4181,2149],{},[476,4183,2152,4184],{},[60,4185,2155],{},[476,4187,4188,2160,4190,2164],{},[60,4189,2029],{},[60,4191,2163],{},[476,4193,2167],{},[161,4195,4196],{"id":2170},[60,4197,2170],{},[114,4199,4200,2177],{},[60,4201,2029],{},[250,4203,4204],{"className":288,"code":2180,"language":290,"meta":258,"style":258},[60,4205,4206,4210,4214],{"__ignoreMap":258},[294,4207,4208],{"class":296,"line":297},[294,4209,2187],{},[294,4211,4212],{"class":296,"line":303},[294,4213,2192],{},[294,4215,4216],{"class":296,"line":34},[294,4217,2197],{},[161,4219,2200],{"id":2200},[114,4221,2203],{},[166,4223,4224,4232],{},[169,4225,4226],{},[172,4227,4228,4230],{},[175,4229,384],{},[175,4231,2214],{},[185,4233,4234,4246,4254,4262,4270,4280,4288],{},[172,4235,4236,4244],{},[190,4237,4238,2058,4240,2058,4242],{},[60,4239,2223],{},[60,4241,2226],{},[60,4243,2229],{},[190,4245,2232],{},[172,4247,4248,4252],{},[190,4249,4250],{},[60,4251,2239],{},[190,4253,2242],{},[172,4255,4256,4260],{},[190,4257,4258],{},[60,4259,2249],{},[190,4261,2252],{},[172,4263,4264,4268],{},[190,4265,4266],{},[60,4267,2259],{},[190,4269,2262],{},[172,4271,4272,4276],{},[190,4273,4274],{},[60,4275,2269],{},[190,4277,2272,4278,2276],{},[60,4279,2275],{},[172,4281,4282,4286],{},[190,4283,4284],{},[60,4285,2283],{},[190,4287,2286],{},[172,4289,4290,4294],{},[190,4291,4292],{},[60,4293,2293],{},[190,4295,2296],{},[114,4297,2299],{},[250,4299,4300],{"className":288,"code":2302,"language":290,"meta":258,"style":258},[60,4301,4302,4306,4310,4314],{"__ignoreMap":258},[294,4303,4304],{"class":296,"line":297},[294,4305,2309],{},[294,4307,4308],{"class":296,"line":303},[294,4309,2314],{},[294,4311,4312],{"class":296,"line":34},[294,4313,2319],{},[294,4315,4316],{"class":296,"line":314},[294,4317,2324],{},[114,4319,2327,4320,2331,4322,2335,4324,2338],{},[60,4321,2330],{},[60,4323,2334],{},[60,4325,1724],{},[161,4327,2341],{"id":2341},[114,4329,2344],{},[250,4331,4332],{"className":2347,"code":2348,"language":2349,"meta":258,"style":258},[60,4333,4334,4342],{"__ignoreMap":258},[294,4335,4336,4338,4340],{"class":296,"line":297},[294,4337,2357],{"class":2356},[294,4339,2361],{"class":2360},[294,4341,2364],{"class":2360},[294,4343,4344,4346],{"class":296,"line":303},[294,4345,2341],{"class":2356},[294,4347,2371],{"class":2360},[114,4349,2374,4350,2378],{},[60,4351,2377],{},[114,4353,2381,4354,2385],{},[60,4355,2384],{},[114,4357,2388,4358],{},[118,4359,2391],{},[161,4361,2395],{"id":2394},[473,4363,4364,4370,4372],{},[476,4365,2400,4366,1332,4368,2406],{},[60,4367,2293],{},[60,4369,2405],{},[476,4371,2409],{},[476,4373,2412],{},[161,4375,1694],{"id":2415},[166,4377,4378,4388],{},[169,4379,4380],{},[172,4381,4382,4384,4386],{},[175,4383,2424],{},[175,4385,2427],{},[175,4387,2430],{},[185,4389,4390,4404,4416,4428,4442],{},[172,4391,4392,4394,4402],{},[190,4393,2437],{},[190,4395,4396,2443,4398,2447,4400],{},[60,4397,2442],{},[60,4399,2446],{},[60,4401,2450],{},[190,4403,2453],{},[172,4405,4406,4408,4412],{},[190,4407,2458],{},[190,4409,4410,2463],{},[60,4411,2067],{},[190,4413,2466,4414],{},[60,4415,2155],{},[172,4417,4418,4420,4426],{},[190,4419,2473],{},[190,4421,4422,2478,4424,2482],{},[60,4423,2170],{},[60,4425,2481],{},[190,4427,2453],{},[172,4429,4430,4432,4440],{},[190,4431,2489],{},[190,4433,2492,4434,2058,4436,2058,4438,2502],{},[60,4435,2495],{},[60,4437,2498],{},[60,4439,2501],{},[190,4441,2453],{},[172,4443,4444,4446,4448],{},[190,4445,2509],{},[190,4447,2512],{},[190,4449,2466,4450],{},[60,4451,2517],{},[114,4453,4454],{},[118,4455,2522,4456,2525,4458,2528,4460,2531],{},[60,4457,1953],{},[60,4459,2450],{},[60,4461,2067],{},[148,4463,2534],{"id":2534},[473,4465,4466,4474,4486,4491],{},[476,4467,4468,2545,4471],{},[123,4469,2544],{"href":2541,"rel":4470},[2543],[123,4472,2550],{"href":2548,"rel":4473},[2543],[476,4475,4476,2545,4481],{},[123,4477,4479],{"href":2555,"rel":4478},[2543],[60,4480,787],{},[123,4482,4484],{"href":2561,"rel":4483},[2543],[60,4485,778],{},[476,4487,4488],{},[123,4489,2571],{"href":2569,"rel":4490},[2543],[476,4492,4493],{},[123,4494,2578],{"href":2576,"rel":4495},[2543],[2580,4497,2582],{},{"title":258,"searchDepth":303,"depth":303,"links":4499},[4500,4511,4522,4532],{"id":150,"depth":303,"text":151,"children":4501},[4502,4503,4504,4505,4506,4507,4508,4509,4510],{"id":163,"depth":34,"text":164},{"id":284,"depth":34,"text":285},{"id":462,"depth":34,"text":2590},{"id":562,"depth":34,"text":2592},{"id":688,"depth":34,"text":2594},{"id":764,"depth":34,"text":765},{"id":815,"depth":34,"text":815},{"id":914,"depth":34,"text":2598},{"id":1069,"depth":34,"text":1069},{"id":1135,"depth":303,"text":1136,"children":4512},[4513,4514,4515,4516,4517,4518,4519,4520,4521],{"id":1146,"depth":34,"text":1147},{"id":1269,"depth":34,"text":1270},{"id":1290,"depth":34,"text":2605},{"id":1366,"depth":34,"text":1367},{"id":1451,"depth":34,"text":1451},{"id":1537,"depth":34,"text":1538},{"id":1604,"depth":34,"text":1604},{"id":1694,"depth":34,"text":1694},{"id":1876,"depth":34,"text":1069},{"id":1939,"depth":303,"text":1940,"children":4523},[4524,4525,4526,4527,4528,4529,4530,4531],{"id":1950,"depth":34,"text":2615},{"id":2084,"depth":34,"text":2084},{"id":2123,"depth":34,"text":2067},{"id":2170,"depth":34,"text":2170},{"id":2200,"depth":34,"text":2200},{"id":2341,"depth":34,"text":2341},{"id":2394,"depth":34,"text":2395},{"id":2415,"depth":34,"text":1694},{"id":2534,"depth":303,"text":2534},{},{"title":109,"description":2626},[2636,2637,2638,821,2639,2640,1451,120],{"id":4537,"title":4538,"body":4539,"category":5120,"date":5121,"description":5122,"extension":2627,"hidden":2628,"kind":5123,"meta":5124,"navigation":30,"path":5125,"publishedAt":5126,"seo":5127,"series":5123,"slug":5128,"stem":5129,"tags":5130,"unit":5123,"updated":5123,"__hash__":5137},"writing\u002Fwriting\u002F2026-09-20-i-never-learned-how-to-build-relationships.md","我好像一直没学会怎么和人建立关系",{"type":111,"value":4540,"toc":5101},[4541,4544,4547,4550,4553,4556,4559,4562,4565,4568,4571,4574,4577,4580,4583,4586,4589,4594,4597,4601,4604,4607,4610,4613,4616,4619,4622,4628,4631,4637,4641,4644,4647,4650,4653,4658,4661,4667,4670,4673,4676,4679,4685,4688,4691,4694,4697,4700,4703,4706,4712,4716,4719,4722,4725,4728,4731,4734,4737,4740,4746,4749,4752,4755,4758,4761,4764,4784,4787,4790,4794,4797,4800,4803,4806,4809,4812,4816,4819,4822,4825,4831,4834,4837,4840,4843,4846,4849,4852,4855,4858,4861,4864,4867,4870,4876,4880,4883,4886,4889,4892,4895,4898,4902,4905,4908,4911,4914,4917,4920,4924,4927,4933,4936,4939,4943,4946,4949,4952,4955,4958,4963,4966,4969,4972,4977,4980,4983,4986,4992,4995,4998,5001,5004,5007,5010,5013,5016,5019,5023,5026,5029,5032,5035,5038,5041,5044,5047,5050,5053,5059,5062,5065,5068,5074,5077,5080,5083,5086,5089,5092,5095,5098],[114,4542,4543],{},"最近和 ChatGPT 聊了很久关于\"人为什么会被一些事情伤害\"这件事。",[114,4545,4546],{},"一开始其实只是一个非常小的问题：为什么游戏里一个完全不认识的人骂我一句，我明明知道这件事情没有任何现实意义，事后却还是会想很久？",[114,4548,4549],{},"甚至会反复想：\"我当时为什么没骂回去？\"",[114,4551,4552],{},"事情明明已经结束了，大脑却还在继续。",[114,4554,4555],{},"然后我又想到，现实里好像也一样。",[114,4557,4558],{},"一句话、一次拒绝、一次失败、一次争吵，甚至一段关系的结束，都可能让一个人很长时间走不出来。",[114,4560,4561],{},"于是我开始不断追问。",[148,4563,4564],{"id":4564},"从一句话为什么会伤人开始",[114,4566,4567],{},"最开始讨论的时候，我慢慢意识到，人并不是一个简单的\"发生事情 → 理性分析 → 得出结论 → 翻篇\"的机器。",[114,4569,4570],{},"事情本身有多大，并不完全决定它会带来多大的情绪。真正重要的，往往是这件事情在自己心里意味着什么。",[114,4572,4573],{},"别人骂我一句，表面上只是一句话。但它可能让我觉得：他是不是看不起我？我是不是显得很弱？我为什么没有反击？是不是我真的不够好？",[114,4575,4576],{},"于是，真正让我难受的可能已经不是那句话，而是它触碰到了我的自我价值和尊严。",[114,4578,4579],{},"这也让我理解了为什么有些人经历一次失败以后还能继续，而连续失败却可能让一个人彻底崩掉。",[114,4581,4582],{},"一次失败可以被理解成\"这次没做好\"，但反复失败以后，人可能开始把事情解释成\"是不是我这个人就不行？\"",[114,4584,4585],{},"这两句话之间，差别非常大。",[114,4587,4588],{},"所以后来我觉得，所谓韧性，并不是一个人永远不会被伤害，而是：",[114,4590,4591],{},[118,4592,4593],{},"允许事情刺痛自己，但不允许事情定义自己。",[114,4595,4596],{},"真正重要的可能不是\"我有没有碎掉\"，而是碎了以后，能不能一点一点把自己捡回来。承认自己的价值，认识自己的能力，也接受自己的不足。",[148,4598,4600],{"id":4599},"然后我开始问自尊到底是什么","然后我开始问：自尊到底是什么？",[114,4602,4603],{},"这就自然走到了\"自尊\"。",[114,4605,4606],{},"以前我总觉得自尊就是面子，但继续聊下去以后，我发现它好像比面子复杂得多。",[114,4608,4609],{},"自尊更像是一个人对于自己的基本判断：我是谁？我应该被怎样对待？哪些事情别人不能随便对我做？",[114,4611,4612],{},"于是我开始意识到，人与人交往的时候，其实到处都是边界：身体有边界、隐私有边界、说话有边界、情绪有边界、人格也有边界。",[114,4614,4615],{},"比如别人说\"你这件事情做得不好\"，这可能只是对能力和行为的评价。",[114,4617,4618],{},"但如果变成\"你这个人就是废物\"，性质就变了。",[114,4620,4621],{},"前者是在讨论一件事情，后者是在否定一个人。",[114,4623,4624,4625],{},"这让我开始理解一个很重要的区别：",[118,4626,4627],{},"不同意一个人，不等于否定这个人。",[114,4629,4630],{},"别人可以反驳我，可以不喜欢我，可以觉得我的观点很差，但这和羞辱、侵犯、贬低不是一回事。",[114,4632,4633,4634],{},"于是一个很简单的原则慢慢出现了：",[118,4635,4636],{},"尊重别人的边界，也守住自己的边界。",[148,4638,4640],{"id":4639},"可是奇怪的是人为什么一边需要边界一边又渴望亲密","可是奇怪的是，人为什么一边需要边界，一边又渴望亲密？",[114,4642,4643],{},"这是后来让我觉得特别有意思的问题。",[114,4645,4646],{},"面对陌生人，我们希望保护自己。可是面对真正亲近的人，我们又希望对方能够进入自己的内心。",[114,4648,4649],{},"我们希望被理解，但又害怕真的被看见。希望有人知道真实的自己，又担心自己的脆弱有一天会被拿来伤害自己。",[114,4651,4652],{},"这看起来像一个矛盾，但讨论到后来，我觉得它其实不是矛盾。因为：",[114,4654,4655],{},[118,4656,4657],{},"亲密并不意味着边界消失，更像是：边界变薄，而不是边界消失。",[114,4659,4660],{},"如果把自己想象成一栋房子：陌生人只能站在门外，普通朋友可以进入客厅，关系更近的人可以看到更多房间，而真正亲密的人，可能会看到自己最脆弱、最不愿意展示的地方。",[114,4662,4663,4664],{},"但关键是：",[118,4665,4666],{},"我愿意为你开门。",[114,4668,4669],{},"而不是：\"因为我们关系好，所以你可以随便闯进来。\"",[114,4671,4672],{},"所以真正健康的亲密关系，应该是两个完整的人，在保留自己的情况下，主动向彼此开放。",[148,4674,4675],{"id":4675},"然后我发现了自己的一个习惯",[114,4677,4678],{},"我突然想到自己平时和别人相处的方式。",[114,4680,4681,4682],{},"我其实不太知道应该怎么主动建立关系。我的一个很自然的原则一直是：",[118,4683,4684],{},"没人问，我就不说。",[114,4686,4687],{},"别人问我什么，我回答。别人不问，我就不主动讲自己的事情。",[114,4689,4690],{},"这其实挺安全的，因为别人不知道我的事情，也就很难伤害我。",[114,4692,4693],{},"但是聊到这里以后，我发现了一个问题：如果两个人都这样——你不问，我不说——那么关系很容易永远停留在表面。",[114,4695,4696],{},"因为亲密关系需要的不只是回答问题，还需要一点点主动暴露自己。",[114,4698,4699],{},"比如\"我今天其实心情不太好\"，或者\"最近工作有点无聊\"，甚至只是\"我最近在学 Python，但是感觉这玩意儿比想象中折磨人\"。",[114,4701,4702],{},"这种分享其实没有多严重，但它是在告诉对方：这里有一点真实的我。",[114,4704,4705],{},"然后看对方会不会接住。如果对方愿意回应，那就可以再多打开一点。如果对方敷衍、嘲笑，甚至利用这个弱点攻击自己，那就退回来。",[114,4707,4708,4709],{},"于是我开始理解所谓的\"试探\"其实不是故意攻击别人的边界，真正的试探是：",[118,4710,4711],{},"给出一点自己，然后看对方怎么对待它。",[148,4713,4715],{"id":4714},"可我又发现好像很多时候是我更在乎别人","可我又发现，好像很多时候是我更在乎别人",[114,4717,4718],{},"这又产生了另一个问题。",[114,4720,4721],{},"有时候我会觉得：\"为什么我对别人好像比别人对我更在意？\"",[114,4723,4724],{},"我会记得别人说过的话，会注意一些细节，也会在意朋友最近过得怎么样。但别人好像并没有那么在乎我。",[114,4726,4727],{},"一开始很容易把它理解成：\"他们是不是根本不在乎我？\"",[114,4729,4730],{},"可是继续想以后，发现还存在另一种可能：也许我根本没有给别人足够多了解我的机会。",[114,4732,4733],{},"因为我很少主动说自己的事情。别人甚至不知道我在乎什么，不知道我最近经历了什么，也不知道我把他们看得多重要。",[114,4735,4736],{},"所以\"别人不在乎我\"和\"我很少让别人有机会了解我\"，其实是两件完全不同的事情。",[114,4738,4739],{},"当然，人与人的投入程度本来就可能不一样。两个人可以都把对方当朋友，但心理上的重要程度完全不同。这很正常。",[114,4741,4742,4743],{},"所以可能真正需要学习的，不是要求所有关系都绝对对等，而是：",[118,4744,4745],{},"根据实际的反馈调整自己的投入。",[148,4747,4748],{"id":4748},"但人际关系好像根本没有一套精确公式",[114,4750,4751],{},"聊到这里，我开始有点焦虑。",[114,4753,4754],{},"因为我很想知道：到底什么程度才算朋友？主动多少次算正常？什么话算越界？什么时候应该更进一步？对方多久不联系我，才说明他不在乎我？",[114,4756,4757],{},"但最后发现，这些东西根本没有精确答案。人与人之间没有数学公式。",[114,4759,4760],{},"不存在\"主动三次 = 对方重视你\"，也不存在\"聊到某个程度 = 可以成为朋友\"。",[114,4762,4763],{},"最后只能留下几条很粗的原则：",[473,4765,4766,4769,4772,4775,4778,4781],{},[476,4767,4768],{},"尊重自己，也尊重别人。",[476,4770,4771],{},"亲密是逐渐增加的。",[476,4773,4774],{},"给一点自己，观察对方如何回应。",[476,4776,4777],{},"关系可以不完全对等，但不能长期只有一方投入。",[476,4779,4780],{},"别人的拒绝不代表自己的价值下降。",[476,4782,4783],{},"自己做错了可以修复，但不是所有关系都必须留下。",[114,4785,4786],{},"这些东西更像坐标系，而不是答案。",[114,4788,4789],{},"所谓\"分寸感\"，可能本来就不是读书读出来的，而是在一次次真实互动里慢慢长出来的。",[148,4791,4793],{"id":4792},"网络上为什么这么容易现实里为什么这么难","网络上为什么这么容易，现实里为什么这么难？",[114,4795,4796],{},"后来我又想到一个特别有意思的事情。",[114,4798,4799],{},"网上其实很容易试探陌生人的边界。你可以不断说一些更过分的话，然后观察对方什么时候生气。",[114,4801,4802],{},"因为网络互动的成本太低了：不知道对方是谁，以后也不一定再见，说错了，大不了关掉聊天窗口。",[114,4804,4805],{},"但现实不一样。现实里的一个人可能明天还会见到，可能有共同朋友，可能在同一个活动里，甚至可能慢慢变成自己的朋友。",[114,4807,4808],{},"所以现实社交的成本更高，也正因为如此，现实关系的建立速度更慢。",[114,4810,4811],{},"但它也因此更加真实。网络上的互动可能几分钟就完成一次边界测试，现实关系可能需要几周、几个月，甚至几年。",[148,4813,4815],{"id":4814},"于是问题变成了现实中到底怎么认识陌生人","于是问题变成了：现实中到底怎么认识陌生人？",[114,4817,4818],{},"这是后来最实际的问题。",[114,4820,4821],{},"我发现，单纯说\"去认识陌生人\"其实没有意义，因为马上就会遇到一个问题：我为什么要和这个陌生人说话？",[114,4823,4824],{},"如果两个人只是站在街上，谁也不认识谁，没有共同活动，也没有共同目标，主动开口确实很困难。",[114,4826,4827,4828],{},"所以真正合理的路径可能不是\"陌生人 → 直接认识\"，而是：",[118,4829,4830],{},"先找到一个圈子。",[114,4832,4833],{},"这个圈子可以是：羽毛球、足球、跑团、徒步、桌游、音乐、摄影、DIY、编程 \u002F AI、读书会、志愿活动、固定课程。",[114,4835,4836],{},"重点不是活动本身有多高级，而是：大家有一个共同理由出现在这里。",[114,4838,4839],{},"于是\"为什么跟你说话\"这个问题自然消失了。",[148,4841,4842],{"id":4842},"关系原来是这样慢慢长出来的",[114,4844,4845],{},"第一次见面，可能只是：\"你好，你也第一次来吗？\"",[114,4847,4848],{},"第二次：\"诶，又来了。\"",[114,4850,4851],{},"第三次：\"你平时周几来？\"",[114,4853,4854],{},"第四次：\"下周还来吗？\"",[114,4856,4857],{},"第五次：\"下次一起？\"",[114,4859,4860],{},"再往后：\"结束以后一起吃个饭？\"",[114,4862,4863],{},"原来真正的关系并不是\"陌生人 → 好朋友\"，而是：",[114,4865,4866],{},"陌生人 → 熟面孔 → 认识 → 熟悉 → 一起做事情 → 朋友 → 更亲近。",[114,4868,4869],{},"这让我觉得，过去可能一直把\"交朋友\"想得太重了。",[114,4871,4872,4873],{},"第一次见面根本不需要决定\"这个人能不能成为我的朋友？\"，只需要：",[118,4874,4875],{},"让下一次见面发生。",[148,4877,4879],{"id":4878},"可是我现在独居也没什么爱好","可是我现在独居，也没什么爱好",[114,4881,4882],{},"到这里又碰到了最现实的问题。",[114,4884,4885],{},"我现在独居，下班以后基本就是自己的生活，而且也没有什么特别明确的爱好。",[114,4887,4888],{},"这时候我才意识到：也许问题根本不是\"我不会社交\"，而是我的生活里根本没有多少社交发生的土壤。",[114,4890,4891],{},"如果每天都是：上班 → 回家 → 手机 → 电脑 → 睡觉。",[114,4893,4894],{},"那么即使一个人的社交能力完全正常，也很难凭空认识新的朋友。",[114,4896,4897],{},"所以需要改变的首先不是\"聊天技巧\"，而是生活结构。",[148,4899,4901],{"id":4900},"可羽毛球一个人怎么打呢","可羽毛球一个人怎么打呢？😂",[114,4903,4904],{},"聊到这里甚至出现了一个很现实的问题：羽毛球一个人怎么打？😂",[114,4906,4907],{},"确实。这也暴露出了前面思考里的一个漏洞。",[114,4909,4910],{},"我们真正需要寻找的不是\"一个人也可以完成的爱好\"，而是\"一个人可以自然进入一群人的活动\"。",[114,4912,4913],{},"所以羽毛球可以通过新手局、拼场、固定约球、初学课程进入。",[114,4915,4916],{},"如果连陌生人拼场都觉得压力很大，那就先选择课程型活动。一个人报名，老师组织，大家一起练，慢慢认识旁边的人。",[114,4918,4919],{},"这样心理压力会低很多。",[148,4921,4923],{"id":4922},"所以我到底有没有必要先找到真正的爱好","所以我到底有没有必要先找到\"真正的爱好\"？",[114,4925,4926],{},"后来我觉得没有。",[114,4928,4929,4930],{},"不需要先找到一个\"我此生热爱的活动\"，只需要问自己：",[118,4931,4932],{},"什么事情我愿意每周出去做两个小时？",[114,4934,4935],{},"第一次觉得一般，很正常。第三次可能觉得还行。十次以后，也许就真的产生兴趣了。",[114,4937,4938],{},"甚至有可能：爱好、朋友和生活圈是一起长出来的。",[148,4940,4942],{"id":4941},"真正困难的可能只是第一步","真正困难的，可能只是第一步",[114,4944,4945],{},"可是即便所有道理都明白了，第一步还是很难。",[114,4947,4948],{},"因为第一步里面包含太多不确定性：我要不要去？去了会不会尴尬？会不会一个人站在那里？别人会不会不理我？我该说什么？如果不好玩怎么办？",[114,4950,4951],{},"于是大脑会非常自然地告诉自己：\"算了，今天先不去。\"",[114,4953,4954],{},"然后回家。手机一刷。电脑一开。舒服了。第二天继续。",[114,4956,4957],{},"所以第一步真正需要解决的，不是\"我要马上变成一个会社交的人\"，而是：",[114,4959,4960],{},[118,4961,4962],{},"我要让自己第一次离开原来的生活轨道。",[148,4964,4965],{"id":4965},"第一课甚至可以简单到什么都不发生",[114,4967,4968],{},"所以最后我给自己设想的目标已经非常低了。",[114,4970,4971],{},"不是\"我要认识一个新朋友\"，而是：",[114,4973,4974],{},[118,4975,4976],{},"\"这周去一个以前不会去的地方待一个小时。\"",[114,4978,4979],{},"不要求认识任何人，不要求聊天，不要求交朋友，甚至不要求喜欢那个地方。",[114,4981,4982],{},"只要求：去。",[114,4984,4985],{},"如果去了，任务就完成了。如果和别人说了两句话，那是额外奖励。",[114,4987,4988,4989],{},"真正训练的，其实是：",[118,4990,4991],{},"从一个人的生活，进入一个有其他人的生活。",[148,4993,4994],{"id":4994},"然后我突然意识到",[114,4996,4997],{},"聊到最后，我对这整件事情有了一个很奇怪但又很准确的感觉：",[114,4999,5000],{},"我的人生好像缺了一课。",[114,5002,5003],{},"不是缺了一门知识，而是缺了一门实践课。",[114,5005,5006],{},"学校教过数学，教过英语，教过专业，后来工作又教了业务。但好像从来没有一门课真正教过：",[114,5008,5009],{},"怎么认识陌生人。怎么和别人建立朋友关系。怎么表达自己的需要。怎么判断人与人的距离。怎么打开自己。怎么接受拒绝。怎么处理冲突。怎么面对一个人慢慢离开自己的生活。",[114,5011,5012],{},"这些东西，很多人可能是在青春期、宿舍、社团、恋爱和朋友关系里，一点一点摔出来的。",[114,5014,5015],{},"而我可能只是没有经历那么多。",[114,5017,5018],{},"所以现在才发现：原来还有这么一门课。",[148,5020,5022],{"id":5021},"这门课现在开始学也不晚","这门课现在开始学，也不晚",[114,5024,5025],{},"而且这门课其实没有标准答案。",[114,5027,5028],{},"它不像数学，没有\"输入 A → 输出 B\"，也没有\"主动三次 → 对方就是你的朋友\"，更没有\"聊到某个程度 → 就可以进入亲密关系\"。",[114,5030,5031],{},"真正能够依靠的，只有一些底层原则：",[114,5033,5034],{},"尊重自己，也尊重别人。边界可以打开，但不能消失。给一点自己，观察对方如何回应。对方愿意靠近，就多走一步；对方不愿意，就停下来。别人的拒绝不意味着自己的价值下降。自己做错了可以修复，但不是所有关系都必须保住。",[114,5036,5037],{},"剩下的东西：靠经历。",[148,5039,5040],{"id":5040},"所以现在不应该继续想明白了",[114,5042,5043],{},"这可能是这次聊天最后真正让我意识到的事情。",[114,5045,5046],{},"我们已经讨论了：自尊、边界、亲密、朋友、投入的不对等、陌生人、网络社交、现实社交、圈子、兴趣、独居、第一步。",[114,5048,5049],{},"如果继续坐在这里分析，还可以继续分析很多。",[114,5051,5052],{},"但分析到这里，已经足够了。因为真正缺的东西已经不是理论，而是实践。",[114,5054,5055,5056],{},"所以接下来可能只需要做一件很小的事情：",[118,5057,5058],{},"去一次。",[114,5060,5061],{},"可能第一次很尴尬。可能一个朋友都没认识。可能回来以后只觉得：\"就这？\"",[114,5063,5064],{},"也没关系。因为这不是考试。",[114,5066,5067],{},"这本身就是课程。",[114,5069,5070,5071],{},"人与人的关系，很多时候不是想明白以后才敢开始，而是：",[118,5072,5073],{},"开始以后，才慢慢想明白。",[114,5075,5076],{},"也许所谓社交能力，也根本不是一种天生的性格。它只是一个人经过足够多次：",[114,5078,5079],{},"认识 → 互动 → 暴露 → 反馈 → 调整 → 再认识",[114,5081,5082],{},"之后，逐渐形成的一种生活能力。",[114,5084,5085],{},"所以如果真的要给自己的人生补上这一课，那么第一课可能非常简单：去一个有人的地方。",[114,5087,5088],{},"第二课：留下来。",[114,5090,5091],{},"第三课：和一个人说一句话。",[114,5093,5094],{},"然后慢慢地，认识一些人，也让一些人有机会认识真正的自己。",[114,5096,5097],{},"这门课没有毕业考试。",[114,5099,5100],{},"但可以从现在开始。",{"title":258,"searchDepth":303,"depth":303,"links":5102},[5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,5113,5114,5115,5116,5117,5118,5119],{"id":4564,"depth":303,"text":4564},{"id":4599,"depth":303,"text":4600},{"id":4639,"depth":303,"text":4640},{"id":4675,"depth":303,"text":4675},{"id":4714,"depth":303,"text":4715},{"id":4748,"depth":303,"text":4748},{"id":4792,"depth":303,"text":4793},{"id":4814,"depth":303,"text":4815},{"id":4842,"depth":303,"text":4842},{"id":4878,"depth":303,"text":4879},{"id":4900,"depth":303,"text":4901},{"id":4922,"depth":303,"text":4923},{"id":4941,"depth":303,"text":4942},{"id":4965,"depth":303,"text":4965},{"id":4994,"depth":303,"text":4994},{"id":5021,"depth":303,"text":5022},{"id":5040,"depth":303,"text":5040},"随笔","2026-09-20","从一次关于情绪和自尊的思考开始，我一步步追问边界、亲密、友谊和现实社交，最后发现自己真正缺少的可能不是社交技巧，而是一次次真实的人际实践。",null,{},"\u002Fwriting\u002F2026-09-20-i-never-learned-how-to-build-relationships","2026-09-20T02:06:59+08:00",{"title":4538,"description":5122},"i-never-learned-how-to-build-relationships","writing\u002F2026-09-20-i-never-learned-how-to-build-relationships",[5131,5132,5133,5134,5135,5136],"自尊","边界","亲密关系","社交","独居","自我成长","PRb0ToQohkcQbkVsXeOn7F3KrqSja60zkMKQ7Hntblw",{"id":5139,"title":5140,"body":5141,"category":2624,"date":5121,"description":7360,"extension":2627,"hidden":2628,"kind":7361,"meta":7362,"navigation":30,"path":125,"publishedAt":7363,"seo":7364,"series":101,"slug":7365,"stem":7366,"tags":7367,"unit":2641,"updated":5123,"__hash__":7373},"writing\u002Fwriting\u002F2026-09-20-python-week-2-functions-abstraction-modularization.md","Week 2 学习计划｜Python 函数、抽象与模块化",{"type":111,"value":5142,"toc":7309},[5143,5147,5150,5153,5156,5160,5249,5253,5256,5259,5262,5266,5271,5275,5278,5294,5297,5300,5349,5353,5360,5365,5368,5373,5412,5419,5424,5438,5441,5446,5449,5463,5466,5489,5492,5507,5510,5515,5522,5527,5533,5537,5540,5561,5564,5568,5571,5590,5592,5628,5631,5642,5645,5648,5653,5656,5661,5672,5677,5688,5693,5709,5714,5717,5732,5735,5740,5750,5755,5783,5811,5820,5823,5826,5843,5846,5850,5853,5866,5885,5888,5894,5897,5900,5905,5910,5922,5927,5934,5939,5942,5948,5953,5956,5961,5981,5988,5993,6021,6024,6027,6029,6057,6060,6064,6067,6084,6099,6118,6121,6124,6129,6134,6151,6171,6176,6183,6188,6194,6199,6204,6209,6212,6217,6220,6238,6241,6258,6261,6264,6266,6290,6294,6298,6301,6304,6316,6325,6336,6339,6342,6348,6351,6356,6359,6373,6378,6392,6397,6416,6421,6424,6429,6440,6443,6445,6466,6470,6474,6477,6492,6495,6498,6501,6506,6513,6518,6524,6530,6535,6564,6567,6572,6578,6581,6586,6593,6598,6601,6604,6606,6625,6628,6632,6636,6639,6642,6672,6675,6678,6684,6687,6702,6705,6714,6719,6760,6765,6785,6790,6795,6833,6836,6841,6847,6850,6855,6857,6882,6887,6898,6901,6907,6910,6914,6917,6946,6950,6953,6959,6962,6966,6970,6978,6982,6985,6989,6992,6996,6999,7004,7035,7040,7065,7070,7088,7093,7111,7116,7136,7141,7158,7162,7215,7219,7222,7225,7228,7234,7240,7243,7245,7248,7306],[148,5144,5146],{"id":5145},"week-2-核心主题函数抽象与模块化","Week 2 核心主题：函数、抽象与模块化",[114,5148,5149],{},"本周目标不是单纯继续学习 Python 语法，而是开始从：",[114,5151,5152],{},"「会写 Python 代码」→「能够组织 Python 代码」→「能够设计一个小型项目」",[114,5154,5155],{},"逐步过渡。本周会把 Week 1 的简单数据结构与 Key-Value Store 项目继续向工程化方向推进。",[148,5157,5159],{"id":5158},"一week-2-总览","一、Week 2 总览",[166,5161,5162,5175],{},[169,5163,5164],{},[172,5165,5166,5169,5172],{},[175,5167,5168],{},"Day",[175,5170,5171],{},"主题",[175,5173,5174],{},"对应目标",[185,5176,5177,5188,5198,5207,5217,5228,5238],{},[172,5178,5179,5182,5185],{},[190,5180,5181],{},"Day 1",[190,5183,5184],{},"函数进阶：参数、作用域、闭包",[190,5186,5187],{},"L1",[172,5189,5190,5193,5196],{},[190,5191,5192],{},"Day 2",[190,5194,5195],{},"模块与包：import 机制",[190,5197,5187],{},[172,5199,5200,5203,5205],{},[190,5201,5202],{},"Day 3",[190,5204,2639],{},[190,5206,5187],{},[172,5208,5209,5212,5215],{},[190,5210,5211],{},"Day 4",[190,5213,5214],{},"dataclass 与 type hints",[190,5216,5187],{},[172,5218,5219,5222,5225],{},[190,5220,5221],{},"Day 5",[190,5223,5224],{},"项目重构（一）：目录拆分、logging、配置",[190,5226,5227],{},"L2",[172,5229,5230,5233,5236],{},[190,5231,5232],{},"Day 6",[190,5234,5235],{},"项目重构（二）：类型检查、异常整合",[190,5237,5227],{},[172,5239,5240,5243,5246],{},[190,5241,5242],{},"Day 7",[190,5244,5245],{},"插件式架构设计 + 周复盘",[190,5247,5248],{},"L3",[148,5250,5252],{"id":5251},"二每日学习节奏","二、每日学习节奏",[114,5254,5255],{},"每天控制在 1.5–2 小时，固定节奏：",[114,5257,5258],{},"学习 45min → 实验 45min → 验收 15min",[114,5260,5261],{},"核心原则：理论不是最终目标，必须通过实验把概念变成自己的代码能力。因此如果当天理论已经理解，但实验没有真正写出来，则当天仍然不算完全完成。",[148,5263,5265],{"id":5264},"day-1函数进阶参数作用域闭包","Day 1｜函数进阶：参数、作用域、闭包",[114,5267,5268],{},[118,5269,5270],{},"时间：1.5–2h",[161,5272,5274],{"id":5273},"学习-45min","学习 45min",[114,5276,5277],{},"阅读：",[473,5279,5280,5287],{},[476,5281,5282],{},[123,5283,5286],{"href":5284,"rel":5285},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fcontrolflow.html#more-on-defining-functions",[2543],"Python Tutorial：More on Defining Functions",[476,5288,5289],{},[123,5290,5293],{"href":5291,"rel":5292},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fclasses.html#scopes-and-namespaces-example",[2543],"Python Tutorial：Scopes and Namespaces",[114,5295,5296],{},"重点：默认参数、关键字参数、任意参数列表、Lambda、LEGB。",[114,5298,5299],{},"掌握：",[473,5301,5302,5305,5313,5316,5319,5327,5330,5333,5336,5341],{},[476,5303,5304],{},"positional \u002F keyword \u002F default 参数",[476,5306,5307,2058,5310],{},[60,5308,5309],{},"*args",[60,5311,5312],{},"**kwargs",[476,5314,5315],{},"可变默认参数陷阱",[476,5317,5318],{},"LEGB 作用域",[476,5320,5321,2058,5324],{},[60,5322,5323],{},"global",[60,5325,5326],{},"nonlocal",[476,5328,5329],{},"闭包：函数记住定义时的环境",[476,5331,5332],{},"函数是一等对象：可赋值、可传参、可返回",[476,5334,5335],{},"lambda",[476,5337,5338],{},[60,5339,5340],{},"sorted(key=...)",[476,5342,5343,2058,5346],{},[60,5344,5345],{},"map()",[60,5347,5348],{},"filter()",[161,5350,5352],{"id":5351},"实验-45min","实验 45min",[114,5354,5355,5356,5359],{},"创建 ",[60,5357,5358],{},"function_demo.py","，完成：",[114,5361,5362],{},[118,5363,5364],{},"1. 三种参数传递方式",[114,5366,5367],{},"分别写出 positional、keyword、default 参数示例。",[114,5369,5370],{},[118,5371,5372],{},"2. 可变默认参数陷阱",[250,5374,5376],{"className":288,"code":5375,"language":290,"meta":258,"style":258},"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,5377,5378,5383,5388,5393,5397,5402,5407],{"__ignoreMap":258},[294,5379,5380],{"class":296,"line":297},[294,5381,5382],{},"def append_to(item, target=[]):\n",[294,5384,5385],{"class":296,"line":303},[294,5386,5387],{},"    target.append(item)\n",[294,5389,5390],{"class":296,"line":34},[294,5391,5392],{},"    return target\n",[294,5394,5395],{"class":296,"line":314},[294,5396,317],{"emptyLinePlaceholder":30},[294,5398,5399],{"class":296,"line":320},[294,5400,5401],{},"print(append_to(1))\n",[294,5403,5404],{"class":296,"line":326},[294,5405,5406],{},"print(append_to(2))\n",[294,5408,5409],{"class":296,"line":332},[294,5410,5411],{},"print(append_to(3, []))\n",[114,5413,5414,5415,5418],{},"写出三次输出并解释原因。然后使用 ",[60,5416,5417],{},"target=None"," 进行修复。",[114,5420,5421],{},[118,5422,5423],{},"3. log_call",[250,5425,5427],{"className":288,"code":5426,"language":290,"meta":258,"style":258},"def log_call(func, *args, **kwargs):\n    ...\n",[60,5428,5429,5434],{"__ignoreMap":258},[294,5430,5431],{"class":296,"line":297},[294,5432,5433],{},"def log_call(func, *args, **kwargs):\n",[294,5435,5436],{"class":296,"line":303},[294,5437,1390],{},[114,5439,5440],{},"要求：打印函数名称、打印位置参数、打印关键字参数、调用原函数、返回原函数结果。",[114,5442,5443],{},[118,5444,5445],{},"4. 闭包计数器",[114,5447,5448],{},"实现：",[250,5450,5452],{"className":288,"code":5451,"language":290,"meta":258,"style":258},"def make_counter():\n    ...\n",[60,5453,5454,5459],{"__ignoreMap":258},[294,5455,5456],{"class":296,"line":297},[294,5457,5458],{},"def make_counter():\n",[294,5460,5461],{"class":296,"line":303},[294,5462,1390],{},[114,5464,5465],{},"要求：",[250,5467,5469],{"className":288,"code":5468,"language":290,"meta":258,"style":258},"counter = make_counter()\nprint(counter())\nprint(counter())\nprint(counter())\n",[60,5470,5471,5476,5481,5485],{"__ignoreMap":258},[294,5472,5473],{"class":296,"line":297},[294,5474,5475],{},"counter = make_counter()\n",[294,5477,5478],{"class":296,"line":303},[294,5479,5480],{},"print(counter())\n",[294,5482,5483],{"class":296,"line":34},[294,5484,5480],{},[294,5486,5487],{"class":296,"line":314},[294,5488,5480],{},[114,5490,5491],{},"得到 1、2、3。同时验证：",[250,5493,5495],{"className":288,"code":5494,"language":290,"meta":258,"style":258},"a = make_counter()\nb = make_counter()\n",[60,5496,5497,5502],{"__ignoreMap":258},[294,5498,5499],{"class":296,"line":297},[294,5500,5501],{},"a = make_counter()\n",[294,5503,5504],{"class":296,"line":303},[294,5505,5506],{},"b = make_counter()\n",[114,5508,5509],{},"两个计数器是否相互独立。",[114,5511,5512],{},[118,5513,5514],{},"5. Lambda + sorted",[114,5516,5517,5518,5521],{},"构造 ",[60,5519,5520],{},"(name, age, score)"," 形式的数据，使用 lambda 实现多字段排序，例如先按年龄，再按成绩。",[114,5523,5524],{},[118,5525,5526],{},"6. global",[114,5528,5529,5530,5532],{},"在函数内部使用 ",[60,5531,5323],{}," 修改全局变量。观察：修改前、函数内部修改、修改后的全局变量，理解全局状态带来的副作用。",[161,5534,5536],{"id":5535},"验收-15min","验收 15min",[114,5538,5539],{},"不用资料解释：",[473,5541,5542,5545,5548,5555,5558],{},[476,5543,5544],{},"默认参数什么时候求值？",[476,5546,5547],{},"为什么 list 默认值危险？",[476,5549,5550,1098,5552,5554],{},[60,5551,5309],{},[60,5553,5312],{}," 有什么区别？",[476,5556,5557],{},"闭包捕获的是值还是变量？",[476,5559,5560],{},"\"函数是对象\"意味着哪三件事？",[148,5562,159],{"id":5563},"day-2模块与包import-机制",[114,5565,5566],{},[118,5567,5270],{},[161,5569,5274],{"id":5570},"学习-45min-1",[114,5572,5277,5573,1332,5576,1332,5580,1332,5585,1032],{},[123,5574,2544],{"href":2541,"rel":5575},[2543],[123,5577,5579],{"href":2548,"rel":5578},[2543],"Python Tutorial：Packages",[123,5581,5583],{"href":2555,"rel":5582},[2543],[60,5584,787],{},[123,5586,5588],{"href":2561,"rel":5587},[2543],[60,5589,778],{},[114,5591,5299],{},[473,5593,5594,5597,5607,5611,5614,5617,5623,5626],{},[476,5595,5596],{},"module \u002F package \u002F subpackage",[476,5598,5599,1332,5602,1332,5604],{},[60,5600,5601],{},"import x",[60,5603,918],{},[60,5605,5606],{},"from x import y as z",[476,5608,5609],{},[60,5610,481],{},[476,5612,5613],{},"re-export",[476,5615,5616],{},"绝对导入 \u002F 相对导入",[476,5618,5619,2058,5621],{},[60,5620,778],{},[60,5622,787],{},[476,5624,5625],{},"import 缓存",[476,5627,815],{},[114,5629,5630],{},"理解 import 流程：",[114,5632,5633,5634,5636,5637,5639,5640,1032],{},"检查 ",[60,5635,778],{}," → 没有则按 ",[60,5638,787],{}," 查找 → 加载模块 → 执行模块代码 → 放入 ",[60,5641,778],{},[161,5643,5352],{"id":5644},"实验-45min-1",[114,5646,5647],{},"创建包结构：",[250,5649,5651],{"className":5650,"code":254,"language":255},[253],[60,5652,254],{"__ignoreMap":258},[114,5654,5655],{},"完成：",[114,5657,5658],{},[118,5659,5660],{},"1. 三种 import",[114,5662,5663,5664,1332,5666,1332,5668,5671],{},"分别实验 ",[60,5665,399],{},[60,5667,415],{},[60,5669,5670],{},"from mypkg.core import xxx","，并理解它们绑定的对象有什么不同。",[114,5673,5674],{},[118,5675,5676],{},"2. re-export",[114,5678,5679,5680,5683,5684,5687],{},"在 ",[60,5681,5682],{},"mypkg\u002F__init__.py"," 中进行 re-export，验证 ",[60,5685,5686],{},"from mypkg import xxx"," 是否能够直接使用。",[114,5689,5690],{},[118,5691,5692],{},"3. 模块运行方式",[114,5694,5695,5696,5699,5700,5702,5703,5705,5706,5708],{},"分别运行 ",[60,5697,5698],{},"python core.py"," 以及 ",[60,5701,545],{},"，观察 ",[60,5704,465],{}," 有什么不同。理解：",[60,5707,1131],{}," 是按照模块路径运行模块，而不是简单按照文件路径执行文件。",[114,5710,5711],{},[118,5712,5713],{},"4. sys.path",[114,5715,5716],{},"运行：",[250,5718,5720],{"className":288,"code":5719,"language":290,"meta":258,"style":258},"import sys\nprint(sys.path)\n",[60,5721,5722,5727],{"__ignoreMap":258},[294,5723,5724],{"class":296,"line":297},[294,5725,5726],{},"import sys\n",[294,5728,5729],{"class":296,"line":303},[294,5730,5731],{},"print(sys.path)\n",[114,5733,5734],{},"理解 Python 到哪里寻找模块。",[114,5736,5737],{},[118,5738,5739],{},"5. 循环导入",[114,5741,5742,5743,1332,5746,5749],{},"人为制造 ",[60,5744,5745],{},"a.py → import b",[60,5747,5748],{},"b.py → import a","，观察错误，然后修复。",[114,5751,5752],{},[118,5753,5754],{},"重点实验：import 绑定关系",[250,5756,5757],{"className":288,"code":922,"language":290,"meta":258,"style":258},[60,5758,5759,5763,5767,5771,5775,5779],{"__ignoreMap":258},[294,5760,5761],{"class":296,"line":297},[294,5762,929],{},[294,5764,5765],{"class":296,"line":303},[294,5766,934],{},[294,5768,5769],{"class":296,"line":34},[294,5770,317],{"emptyLinePlaceholder":30},[294,5772,5773],{"class":296,"line":314},[294,5774,943],{},[294,5776,5777],{"class":296,"line":320},[294,5778,948],{},[294,5780,5781],{"class":296,"line":326},[294,5782,953],{},[250,5784,5785],{"className":288,"code":956,"language":290,"meta":258,"style":258},[60,5786,5787,5791,5795,5799,5803,5807],{"__ignoreMap":258},[294,5788,5789],{"class":296,"line":297},[294,5790,963],{},[294,5792,5793],{"class":296,"line":303},[294,5794,968],{},[294,5796,5797],{"class":296,"line":34},[294,5798,973],{},[294,5800,5801],{"class":296,"line":314},[294,5802,317],{"emptyLinePlaceholder":30},[294,5804,5805],{"class":296,"line":320},[294,5806,982],{},[294,5808,5809],{"class":296,"line":326},[294,5810,987],{},[114,5812,5813,5814,5816,5817,1032],{},"预测输出。重点解释：为什么两个 count 可能不一致？最终理解：",[60,5815,918],{}," 绑定的是当前时刻从模块中取得的对象引用，而不是之后动态读取 ",[60,5818,5819],{},"x.y",[161,5821,5536],{"id":5822},"验收-15min-1",[114,5824,5825],{},"回答：",[473,5827,5828,5831,5837,5840],{},[476,5829,5830],{},"import 同一个模块两次，模块代码执行几次？为什么？",[476,5832,5833,1098,5835,5554],{},[60,5834,565],{},[60,5836,569],{},[476,5838,5839],{},"循环导入为什么发生？",[476,5841,5842],{},"最常见的解决思路是什么？",[148,5844,1143],{"id":5845},"day-3异常处理健壮性基础",[114,5847,5848],{},[118,5849,5270],{},[161,5851,5274],{"id":5852},"学习-45min-2",[114,5854,5277,5855,1332,5858,1332,5861,1032],{},[123,5856,2571],{"href":2569,"rel":5857},[2543],[123,5859,2578],{"href":2576,"rel":5860},[2543],[123,5862,5865],{"href":5863,"rel":5864},"https:\u002F\u002Fdocs.python.org\u002F3\u002Freference\u002Fsimple_stmts.html#the-raise-statement",[2543],"raise 文档",[114,5867,5868,5869,2058,5872,2058,5874,2058,5876,1332,5878,5881,5882,5884],{},"重点掌握：",[60,5870,5871],{},"try",[60,5873,1277],{},[60,5875,1240],{},[60,5877,1246],{},[60,5879,5880],{},"raise","、异常链 ",[60,5883,1814],{},"、自定义异常、EAFP、LBYL。",[114,5886,5887],{},"重点认识异常层级：",[250,5889,5892],{"className":5890,"code":5891,"language":255},[253],"Exception\n├── ValueError\n├── TypeError\n├── OSError\n└── ...\n",[60,5893,5891],{"__ignoreMap":258},[114,5895,5896],{},"以及：异常不是单纯\"报错\"，而是一种控制异常情况的机制。",[161,5898,5352],{"id":5899},"实验-45min-2",[114,5901,5355,5902,5359],{},[60,5903,5904],{},"exception_demo.py",[114,5906,5907],{},[118,5908,5909],{},"1. parse_int",[114,5911,5912,5913,5915,5916,1332,5918,1332,5920,1032],{},"实现 ",[60,5914,1702],{},"，分别处理 ",[60,5917,1438],{},[60,5919,1717],{},[60,5921,1724],{},[114,5923,5924],{},[118,5925,5926],{},"2. try \u002F except \u002F else \u002F finally",[114,5928,5929,5930,5933],{},"全部使用，通过 ",[60,5931,5932],{},"print(...)"," 观察实际执行顺序。",[114,5935,5936],{},[118,5937,5938],{},"3. 自定义异常",[114,5940,5941],{},"建立：",[250,5943,5946],{"className":5944,"code":5945,"language":255},[253],"AppError\n├── ConfigError\n└── DataError\n",[60,5947,5945],{"__ignoreMap":258},[114,5949,5950],{},[118,5951,5952],{},"4. EAFP 改写",[114,5954,5955],{},"将 Week 1 项目中一段\"先检查 → 再执行\"的 LBYL 代码改写成\"直接执行 → 捕获异常\"的 EAFP 风格，比较两种写法的可读性。",[114,5957,5958],{},[118,5959,5960],{},"5. 异常链",[250,5962,5963],{"className":288,"code":1398,"language":290,"meta":258,"style":258},[60,5964,5965,5969,5973,5977],{"__ignoreMap":258},[294,5966,5967],{"class":296,"line":297},[294,5968,1157],{},[294,5970,5971],{"class":296,"line":303},[294,5972,1409],{},[294,5974,5975],{"class":296,"line":34},[294,5976,1414],{},[294,5978,5979],{"class":296,"line":314},[294,5980,1419],{},[114,5982,5983,5984,5987],{},"观察 traceback 中 ",[60,5985,5986],{},"The above exception was the direct cause...","，理解：新异常表达当前层面的业务语义，原异常保留底层原因。",[114,5989,5990],{},[118,5991,5992],{},"6. finally + return",[250,5994,5996],{"className":288,"code":5995,"language":290,"meta":258,"style":258},"def test():\n    try:\n        return 1\n    finally:\n        return 2\n",[60,5997,5998,6003,6007,6012,6016],{"__ignoreMap":258},[294,5999,6000],{"class":296,"line":297},[294,6001,6002],{},"def test():\n",[294,6004,6005],{"class":296,"line":303},[294,6006,1792],{},[294,6008,6009],{"class":296,"line":34},[294,6010,6011],{},"        return 1\n",[294,6013,6014],{"class":296,"line":314},[294,6015,1855],{},[294,6017,6018],{"class":296,"line":320},[294,6019,6020],{},"        return 2\n",[114,6022,6023],{},"观察最终返回值。",[161,6025,5536],{"id":6026},"验收-15min-2",[114,6028,5825],{},[473,6030,6031,6037,6043,6047,6051,6054],{},[476,6032,6033,1886,6035,5554],{},[60,6034,1885],{},[60,6036,1294],{},[476,6038,6039,6040,6042],{},"为什么裸 ",[60,6041,1294],{}," 危险？",[476,6044,6045,1907],{},[60,6046,1240],{},[476,6048,6049,1907],{},[60,6050,1246],{},[476,6052,6053],{},"什么情况下值得自定义异常？",[476,6055,6056],{},"EAFP 是什么？相比 LBYL 有什么优势和风险？",[148,6058,1947],{"id":6059},"day-4dataclass-与类型标注",[114,6061,6062],{},[118,6063,5270],{},[161,6065,5274],{"id":6066},"学习-45min-3",[114,6068,5277,6069,1332,6074,1332,6079,1032],{},[123,6070,6073],{"href":6071,"rel":6072},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fdataclasses.html",[2543],"dataclasses 官方文档",[123,6075,6078],{"href":6076,"rel":6077},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Ftyping.html",[2543],"typing 官方文档",[123,6080,6083],{"href":6081,"rel":6082},"https:\u002F\u002Fmypy.readthedocs.io\u002Fen\u002Fstable\u002Fgetting_started.html",[2543],"mypy Getting Started",[114,6085,6086,6087,1332,6090,1332,6092,1332,6095,1332,6097,1032],{},"重点：",[60,6088,6089],{},"dataclass",[60,6091,1953],{},[60,6093,6094],{},"field",[60,6096,2450],{},[60,6098,2067],{},[114,6100,6101,6102,1332,6104,1332,6107,1332,6110,1332,6113,1332,6116,1032],{},"类型标注：变量标注、参数标注、返回值标注、",[60,6103,2377],{},[60,6105,6106],{},"Union",[60,6108,6109],{},"list[T]",[60,6111,6112],{},"dict[K, V]",[60,6114,6115],{},"Callable",[60,6117,2293],{},[114,6119,6120],{},"理解：mypy 是静态类型检查工具，不负责在运行时执行类型检查。",[161,6122,5352],{"id":6123},"实验-45min-3",[114,6125,5355,6126,1032],{},[60,6127,6128],{},"typed_demo.py",[114,6130,6131],{},[118,6132,6133],{},"1. Record",[250,6135,6137],{"className":288,"code":6136,"language":290,"meta":258,"style":258},"@dataclass\nclass Record:\n    ...\n",[60,6138,6139,6143,6147],{"__ignoreMap":258},[294,6140,6141],{"class":296,"line":297},[294,6142,1980],{},[294,6144,6145],{"class":296,"line":303},[294,6146,1985],{},[294,6148,6149],{"class":296,"line":34},[294,6150,1390],{},[114,6152,6153,6154,1332,6157,1332,6160,1332,6162,6165,6166,6168,6169,1032],{},"字段：",[60,6155,6156],{},"id",[60,6158,6159],{},"name",[60,6161,2446],{},[60,6163,6164],{},"created_at","。其中 ",[60,6167,2446],{}," 必须使用 ",[60,6170,2450],{},[114,6172,6173],{},[118,6174,6175],{},"2. frozen=True",[114,6177,6178,6179,6182],{},"实验 ",[60,6180,6181],{},"item.name = \"new\"","，观察错误，理解不可变 dataclass。",[114,6184,6185],{},[118,6186,6187],{},"3. 数据校验",[114,6189,6190,6191,6193],{},"增加 ",[60,6192,2481],{}," 等数据校验。",[114,6195,6196],{},[118,6197,6198],{},"4. 类型标注",[114,6200,6201,6202,1032],{},"为几个函数补全参数类型、返回值类型，至少包含一个 ",[60,6203,2330],{},[114,6205,6206],{},[118,6207,6208],{},"5. mypy",[114,6210,6211],{},"安装并运行 mypy。故意制造至少 3 个类型错误，然后逐个修复。",[114,6213,6214],{},[118,6215,6216],{},"重点实验：dataclass 可变默认值",[114,6218,6219],{},"先尝试：",[250,6221,6223],{"className":288,"code":6222,"language":290,"meta":258,"style":258},"@dataclass\nclass Item:\n    tags: list[str] = []\n",[60,6224,6225,6229,6233],{"__ignoreMap":258},[294,6226,6227],{"class":296,"line":297},[294,6228,1980],{},[294,6230,6231],{"class":296,"line":303},[294,6232,2140],{},[294,6234,6235],{"class":296,"line":34},[294,6236,6237],{},"    tags: list[str] = []\n",[114,6239,6240],{},"观察错误，然后修复：",[250,6242,6244],{"className":288,"code":6243,"language":290,"meta":258,"style":258},"@dataclass\nclass Item:\n    tags: list[str] = field(default_factory=list)\n",[60,6245,6246,6250,6254],{"__ignoreMap":258},[294,6247,6248],{"class":296,"line":297},[294,6249,1980],{},[294,6251,6252],{"class":296,"line":303},[294,6253,2140],{},[294,6255,6256],{"class":296,"line":34},[294,6257,2000],{},[114,6259,6260],{},"思考：dataclass 和 Day 1 的普通函数默认参数为什么会出现类似问题？最终理解：可变对象默认值需要在实例创建时生成，而不是共享一个对象。",[161,6262,5536],{"id":6263},"验收-15min-3",[114,6265,5825],{},[473,6267,6268,6273,6279,6284,6287],{},[476,6269,6270,6272],{},[60,6271,1953],{}," 自动生成了哪些常用方法？",[476,6274,6275,6278],{},[60,6276,6277],{},"list[str]"," 为什么不能直接作为 dataclass 默认值？正确写法是什么？",[476,6280,6281,6283],{},[60,6282,2259],{}," 是什么意思？",[476,6285,6286],{},"类型标注会不会自动阻止错误运行？",[476,6288,6289],{},"mypy 在什么阶段发挥作用？",[148,6291,6293],{"id":6292},"day-5l2-重构一目录拆分-logging-配置","Day 5｜L2 重构（一）：目录拆分 + logging + 配置",[114,6295,6296],{},[118,6297,5270],{},[114,6299,6300],{},"这一日开始正式进入工程化。前四天主要学习语言和工具，从 Day 5 开始，把这些能力真正用于 Week 1 项目。",[161,6302,5274],{"id":6303},"学习-45min-4",[114,6305,5277,6306,1332,6311,1032],{},[123,6307,6310],{"href":6308,"rel":6309},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Flogging.html",[2543],"logging 官方文档",[123,6312,6315],{"href":6313,"rel":6314},"https:\u002F\u002Fdocs.python.org\u002F3\u002Fhowto\u002Flogging.html",[2543],"Logging HOWTO",[114,6317,6318,6319,1332,6322,1032],{},"掌握：Logger、Handler、Formatter、日志级别、",[60,6320,6321],{},"logging.getLogger(__name__)",[60,6323,6324],{},"basicConfig",[114,6326,6327,6328,6331,6332,6335],{},"同时学习基本分层思想：",[60,6329,6330],{},"storage → core → cli","，以及 ",[60,6333,6334],{},"config"," 负责配置。",[161,6337,6338],{"id":6338},"目标目录",[114,6340,6341],{},"创建：",[250,6343,6346],{"className":6344,"code":6345,"language":255},[253],"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,6347,6345],{"__ignoreMap":258},[161,6349,5352],{"id":6350},"实验-45min-4",[114,6352,6353],{},[118,6354,6355],{},"1. 目录拆分",[114,6357,6358],{},"将 Week 1 项目按照职责拆分。例如：",[473,6360,6361,6364,6367,6370],{},[476,6362,6363],{},"storage：负责数据保存、读取",[476,6365,6366],{},"core：负责业务逻辑",[476,6368,6369],{},"cli：负责用户交互 \u002F 程序入口",[476,6371,6372],{},"config：负责配置",[114,6374,6375],{},[118,6376,6377],{},"2. 配置与代码分离",[114,6379,5355,6380,6383,6384,6387,6388,6391],{},[60,6381,6382],{},"config.json","，使用 Day 4 的 dataclass ",[60,6385,6386],{},"AppConfig","，实现 ",[60,6389,6390],{},"load_config()","，完成 JSON → dict → AppConfig。",[114,6393,6394],{},[118,6395,6396],{},"3. logging",[114,6398,6399,6400,6402,6403,1332,6406,1332,6409,6412,6413,6415],{},"将项目中的 ",[60,6401,5932],{}," 逐渐替换成 ",[60,6404,6405],{},"logger.info(...)",[60,6407,6408],{},"logger.warning(...)",[60,6410,6411],{},"logger.error(...)","。使用 ",[60,6414,6321],{},"，统一日志格式：时间、模块、日志级别、消息。",[114,6417,6418],{},[118,6419,6420],{},"4. 测试",[114,6422,6423],{},"保持原有测试能够运行，并新增一个 storage 测试，验证目录重构没有改变原有行为。",[114,6425,6426],{},[118,6427,6428],{},"重点实验：basicConfig",[114,6430,6431,6432,6435,6436,6439],{},"分别在 storage、cli 调用 ",[60,6433,6434],{},"logging.basicConfig(...)","，观察行为。最终总结：",[60,6437,6438],{},"basicConfig()"," 应该由应用入口统一配置，而不是由底层库模块随意配置。",[161,6441,5536],{"id":6442},"验收-15min-4",[114,6444,5825],{},[473,6446,6447,6450,6457,6460,6463],{},[476,6448,6449],{},"四层结构分别负责什么？",[476,6451,6452,6453,6456],{},"为什么推荐 ",[60,6454,6455],{},"getLogger(__name__)","？",[476,6458,6459],{},"哪些东西应该放配置文件？",[476,6461,6462],{},"哪些东西不应该放配置文件？",[476,6464,6465],{},"什么信号说明\"拆对了\"？什么信号说明\"越拆越乱\"？",[148,6467,6469],{"id":6468},"day-6l2-重构二类型检查-异常整合","Day 6｜L2 重构（二）：类型检查 + 异常整合",[114,6471,6472],{},[118,6473,5270],{},[161,6475,5274],{"id":6476},"学习-45min-5",[114,6478,5277,6479,1332,6482,1332,6487,1032],{},[123,6480,6083],{"href":6081,"rel":6481},[2543],[123,6483,6486],{"href":6484,"rel":6485},"https:\u002F\u002Fmypy.readthedocs.io\u002Fen\u002Fstable\u002Fcheat_sheet_py3.html",[2543],"mypy cheat sheet",[123,6488,6491],{"href":6489,"rel":6490},"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Freference\u002Freference.html#pytest-raises",[2543],"pytest.raises",[114,6493,6494],{},"掌握：类型检查、Optional 未判空、返回类型错误、Any 扩散、分层异常、异常翻译、异常测试。",[161,6496,5352],{"id":6497},"实验-45min-5",[114,6499,6500],{},"继续 Day 5 的项目。",[114,6502,6503],{},[118,6504,6505],{},"1. 完整类型标注",[114,6507,6508,6509,6512],{},"给 storage、core 中的公开函数增加完整类型标注。运行 ",[60,6510,6511],{},"mypy ...","，目标是通过。记录：一共发现多少错误、修复了多少错误、其中有哪些是真实问题。",[114,6514,6515],{},[118,6516,6517],{},"2. 建立异常体系",[114,6519,5355,6520,6523],{},[60,6521,6522],{},"app\u002Fexceptions.py","，结构：",[250,6525,6528],{"className":6526,"code":6527,"language":255},[253],"AppError\n├── StorageError\n├── ConfigError\n└── ValidationError\n",[60,6529,6527],{"__ignoreMap":258},[114,6531,6532],{},[118,6533,6534],{},"3. Storage 层异常翻译",[250,6536,6538],{"className":288,"code":6537,"language":290,"meta":258,"style":258},"def load_records(path: str) -> list[Record]:\n    try:\n        ...\n    except (OSError, json.JSONDecodeError) as e:\n        raise StorageError(f\"无法读取 {path}\") from e\n",[60,6539,6540,6545,6549,6554,6559],{"__ignoreMap":258},[294,6541,6542],{"class":296,"line":297},[294,6543,6544],{},"def load_records(path: str) -> list[Record]:\n",[294,6546,6547],{"class":296,"line":303},[294,6548,1792],{},[294,6550,6551],{"class":296,"line":34},[294,6552,6553],{},"        ...\n",[294,6555,6556],{"class":296,"line":314},[294,6557,6558],{},"    except (OSError, json.JSONDecodeError) as e:\n",[294,6560,6561],{"class":296,"line":320},[294,6562,6563],{},"        raise StorageError(f\"无法读取 {path}\") from e\n",[114,6565,6566],{},"理解：底层异常（OSError \u002F JSONDecodeError）→ StorageError，上层业务只需要理解 StorageError。",[114,6568,6569],{},[118,6570,6571],{},"4. CLI 统一处理",[114,6573,6574,6575,6577],{},"CLI 层：捕获 ",[60,6576,1507],{}," → 输出用户友好的信息 → 设置正确退出码。",[114,6579,6580],{},"Core 层：尽量不充满 try\u002Fexcept。",[114,6582,6583],{},[118,6584,6585],{},"5. 异常测试",[114,6587,6588,6589,6592],{},"使用 ",[60,6590,6591],{},"pytest.raises(...)","，至少增加 2 个异常路径测试。",[114,6594,6595],{},[118,6596,6597],{},"6. pyproject.toml",[114,6599,6600],{},"统一配置 pytest、mypy，让项目工具配置集中管理。",[161,6602,5536],{"id":6603},"验收-15min-5",[114,6605,5825],{},[473,6607,6608,6611,6614,6619,6622],{},[476,6609,6610],{},"为什么 core 层不应该写满 try\u002Fexcept？",[476,6612,6613],{},"为什么要在 storage 层翻译异常？",[476,6615,6616,6618],{},[60,6617,1447],{}," 有什么价值？",[476,6620,6621],{},"mypy 帮你发现了哪些真实问题？",[476,6623,6624],{},"每一层应该\"抛什么、捕什么\"？",[114,6626,6627],{},"最终能够用一句话描述 storage、core、cli 的异常责任。",[148,6629,6631],{"id":6630},"day-7l3-插件式架构设计-周复盘","Day 7｜L3 插件式架构设计 + 周复盘",[114,6633,6634],{},[118,6635,5270],{},[114,6637,6638],{},"这一日不追求大量代码，核心目标：开始从\"写代码\"进入\"设计代码\"。",[161,6640,5274],{"id":6641},"学习-45min-6",[114,6643,5277,6644,1332,6649,1332,6654,1332,6661,1332,6666,6671],{},[123,6645,6648],{"href":6646,"rel":6647},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fabc.html",[2543],"abc 官方文档",[123,6650,6653],{"href":6651,"rel":6652},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fabc.html#abc.ABC",[2543],"ABC",[123,6655,6658],{"href":6656,"rel":6657},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fabc.html#abc.abstractmethod",[2543],[60,6659,6660],{},"@abstractmethod",[123,6662,6665],{"href":6663,"rel":6664},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Ftyping.html#typing.Protocol",[2543],"typing.Protocol",[123,6667,6670],{"href":6668,"rel":6669},"https:\u002F\u002Fpeps.python.org\u002Fpep-0544\u002F",[2543],"PEP 544","、开闭原则 OCP 短文。",[114,6673,6674],{},"掌握抽象：抽象基类可以定义一个实现必须遵守的契约。",[114,6676,6677],{},"接口与实现，例如：",[250,6679,6682],{"className":6680,"code":6681,"language":255},[253],"Reader\n├── CSVReader\n├── JSONReader\n└── ExcelReader\n",[60,6683,6681],{"__ignoreMap":258},[114,6685,6686],{},"Processor 不应该关心你到底是 CSV、JSON 还是 Excel，它只需要知道 Reader 接口。",[114,6688,6689,6690,6693,6694,6697,6698,6701],{},"依赖注入：",[60,6691,6692],{},"Processor(reader)"," 而不是 ",[60,6695,6696],{},"Processor()"," 内部写死 ",[60,6699,6700],{},"CSVReader()","，这样可以降低耦合。",[161,6703,5352],{"id":6704},"实验-45min-6",[114,6706,6707,6708,1332,6711,1032],{},"产出：",[60,6709,6710],{},"architecture.md",[60,6712,6713],{},"readers.py",[114,6715,6716],{},[118,6717,6718],{},"1. 定义 Reader 接口",[250,6720,6722],{"className":288,"code":6721,"language":290,"meta":258,"style":258},"class Reader(ABC):\n    @abstractmethod\n    def read(self, path: str) -> list[Record]:\n        ...\n\n    @abstractmethod\n    def can_handle(self, path: str) -> bool:\n        ...\n",[60,6723,6724,6729,6734,6739,6743,6747,6751,6756],{"__ignoreMap":258},[294,6725,6726],{"class":296,"line":297},[294,6727,6728],{},"class Reader(ABC):\n",[294,6730,6731],{"class":296,"line":303},[294,6732,6733],{},"    @abstractmethod\n",[294,6735,6736],{"class":296,"line":34},[294,6737,6738],{},"    def read(self, path: str) -> list[Record]:\n",[294,6740,6741],{"class":296,"line":314},[294,6742,6553],{},[294,6744,6745],{"class":296,"line":320},[294,6746,317],{"emptyLinePlaceholder":30},[294,6748,6749],{"class":296,"line":326},[294,6750,6733],{},[294,6752,6753],{"class":296,"line":332},[294,6754,6755],{},"    def can_handle(self, path: str) -> bool:\n",[294,6757,6758],{"class":296,"line":338},[294,6759,6553],{},[114,6761,6762],{},[118,6763,6764],{},"2. 三种 Reader",[114,6766,5355,6767,1332,6770,1332,6773,6776,6777,6780,6781,6784],{},[60,6768,6769],{},"CSVReader",[60,6771,6772],{},"JSONReader",[60,6774,6775],{},"ExcelReader","，暂时只写空壳，方法内部 ",[60,6778,6779],{},"raise NotImplementedError","。验证：Reader 不能直接实例化（",[60,6782,6783],{},"Reader()"," 应该报错），子类漏实现抽象方法也应该无法实例化。",[114,6786,6787],{},[118,6788,6789],{},"3. Processor",[114,6791,713,6792,6794],{},[60,6793,6692],{},"，让 Processor 通过构造函数接收 Reader。",[250,6796,6798],{"className":288,"code":6797,"language":290,"meta":258,"style":258},"class Processor:\n    def __init__(self, reader):\n        self.reader = reader\n\n    def process(self, path):\n        records = self.reader.read(path)\n        ...\n",[60,6799,6800,6805,6810,6815,6819,6824,6829],{"__ignoreMap":258},[294,6801,6802],{"class":296,"line":297},[294,6803,6804],{},"class Processor:\n",[294,6806,6807],{"class":296,"line":303},[294,6808,6809],{},"    def __init__(self, reader):\n",[294,6811,6812],{"class":296,"line":34},[294,6813,6814],{},"        self.reader = reader\n",[294,6816,6817],{"class":296,"line":314},[294,6818,317],{"emptyLinePlaceholder":30},[294,6820,6821],{"class":296,"line":320},[294,6822,6823],{},"    def process(self, path):\n",[294,6825,6826],{"class":296,"line":326},[294,6827,6828],{},"        records = self.reader.read(path)\n",[294,6830,6831],{"class":296,"line":332},[294,6832,6553],{},[114,6834,6835],{},"Processor 不关心具体 Reader。",[114,6837,6838],{},[118,6839,6840],{},"4. 绘制结构图",[250,6842,6845],{"className":6843,"code":6844,"language":255},[253],"CSVReader ─┐\nJSONReader ├──→ Reader 接口 ──→ Processor ──→ 输出\nExcelReader┘\n",[60,6846,6844],{"__ignoreMap":258},[114,6848,6849],{},"核心思想：具体实现 → 统一接口 → 业务逻辑。",[114,6851,6852],{},[118,6853,6854],{},"5. architecture.md",[114,6856,5825],{},[473,6858,6859,6866,6873,6876],{},[476,6860,6861,6862,6865],{},"问题一：如果新增 ",[60,6863,6864],{},"XMLReader","，完整步骤是什么？明确列出哪些文件需要修改，以及哪些文件完全不需要修改？",[476,6867,6868,6869,6872],{},"问题二：为什么 ",[60,6870,6871],{},"read() -> list[Record]"," 是一个关键设计？",[476,6874,6875],{},"问题三：异常约定——Reader 抛什么？Processor 捕不捕？写出你的设计。",[476,6877,6878,6879,6881],{},"问题四：如果 ",[60,6880,6775],{}," 需要 sheet 参数，参数应该怎么设计？比较构造参数、read() 参数、可选配置对象，最终选择一种，并写出理由。",[114,6883,6884],{},[118,6885,6886],{},"重点设计题",[114,6888,6889,6890,6893,6894,6897],{},"思考：如果把 ",[60,6891,6892],{},"read()"," 的返回值从统一的 ",[60,6895,6896],{},"list[Record]"," 改成各 Reader 返回各自格式的 dict，会发生什么？",[114,6899,6900],{},"分析：",[250,6902,6905],{"className":6903,"code":6904,"language":255},[253],"Reader 返回不同结构\n  ↓\nProcessor 必须认识各种结构\n  ↓\nProcessor 出现大量 if \u002F isinstance\n  ↓\n新增 Reader 需要修改 Processor\n  ↓\n系统耦合增加\n",[60,6906,6904],{"__ignoreMap":258},[114,6908,6909],{},"最终回答：这说明统一接口和统一数据模型有什么价值？",[161,6911,6913],{"id":6912},"day-7-验收","Day 7 验收",[114,6915,6916],{},"不用资料回答：",[473,6918,6919,6922,6925,6928,6934,6937,6940,6943],{},[476,6920,6921],{},"为什么新增 Reader 不需要修改 Processor？",[476,6923,6924],{},"ABC 和 Protocol 的核心区别是什么？",[476,6926,6927],{},"你的 Reader 接口最难设计的地方是什么？",[476,6929,6930,6931,6933],{},"为什么 ",[60,6932,6896],{}," 比\"各自返回 dict\"更重要？",[476,6935,6936],{},"什么是依赖注入？",[476,6938,6939],{},"本周最有收获的概念是什么？",[476,6941,6942],{},"本周项目最大的改进是什么？",[476,6944,6945],{},"什么问题留到 Week 3？",[148,6947,6949],{"id":6948},"三week-2-最终项目结构","三、Week 2 最终项目结构",[114,6951,6952],{},"如果本周全部完成，项目最终大致应该形成：",[250,6954,6957],{"className":6955,"code":6956,"language":255},[253],"project\u002F\n├── app\u002F\n│   ├── __init__.py\n│   ├── core\u002F\n│   │   ├── __init__.py\n│   │   └── ...\n│   ├── storage\u002F\n│   │   ├── __init__.py\n│   │   └── ...\n│   ├── cli.py\n│   ├── config.py\n│   └── exceptions.py\n├── tests\u002F\n│   └── ...\n├── architecture.md\n├── config.json\n├── pyproject.toml\n└── ...\n",[60,6958,6956],{"__ignoreMap":258},[114,6960,6961],{},"并具备：模块化 → 类型标注 → mypy → 异常体系 → logging → 配置分离 → 测试 → 接口抽象 → 插件式架构雏形。",[148,6963,6965],{"id":6964},"四本周能力目标","四、本周能力目标",[161,6967,6969],{"id":6968},"l1语言能力","L1｜语言能力",[114,6971,6972,6973,1332,6975,6977],{},"能够独立理解和使用：函数参数、",[60,6974,5309],{},[60,6976,5312],{},"、作用域、闭包、import、module、package、exception、dataclass、type hints。",[161,6979,6981],{"id":6980},"l2工程能力","L2｜工程能力",[114,6983,6984],{},"能够：合理拆分目录、管理模块依赖、使用 logging、分离配置、建立异常层级、使用 mypy、编写异常测试、使用 pytest、对已有项目进行重构。",[161,6986,6988],{"id":6987},"l3设计能力","L3｜设计能力",[114,6990,6991],{},"开始理解：抽象、接口、依赖注入、开闭原则、插件式架构、数据模型统一、降低模块耦合。",[148,6993,6995],{"id":6994},"五本周最终验收标准","五、本周最终验收标准",[114,6997,6998],{},"本周不是以\"看完教程\"为完成标准。真正的完成标准是：",[114,7000,7001],{},[118,7002,7003],{},"Python 语言层",[473,7005,7006,7009,7016,7019,7022,7029,7032],{},[476,7007,7008],{},"能解释函数参数绑定",[476,7010,7011,7012,2058,7014],{},"能正确使用 ",[60,7013,5309],{},[60,7015,5312],{},[476,7017,7018],{},"能解释可变默认参数陷阱",[476,7020,7021],{},"能解释 LEGB",[476,7023,7024,7025,2058,7027],{},"能使用 ",[60,7026,5323],{},[60,7028,5326],{},[476,7030,7031],{},"能写闭包",[476,7033,7034],{},"能把函数作为参数传递",[114,7036,7037],{},[118,7038,7039],{},"模块层",[473,7041,7042,7045,7048,7053,7057,7062],{},[476,7043,7044],{},"理解 module \u002F package",[476,7046,7047],{},"理解 import 机制",[476,7049,7050,7051],{},"理解 ",[60,7052,778],{},[476,7054,7050,7055],{},[60,7056,787],{},[476,7058,7050,7059],{},[60,7060,7061],{},"__main__",[476,7063,7064],{},"能定位循环导入问题",[114,7066,7067],{},[118,7068,7069],{},"异常层",[473,7071,7072,7075,7078,7082,7085],{},[476,7073,7074],{},"能正确使用 try \u002F except \u002F else \u002F finally",[476,7076,7077],{},"能使用 raise",[476,7079,7024,7080],{},[60,7081,1814],{},[476,7083,7084],{},"能设计简单异常层级",[476,7086,7087],{},"理解 EAFP \u002F LBYL",[114,7089,7090],{},[118,7091,7092],{},"数据模型层",[473,7094,7095,7098,7102,7105,7108],{},[476,7096,7097],{},"能使用 dataclass",[476,7099,7024,7100],{},[60,7101,2450],{},[476,7103,7104],{},"能写类型标注",[476,7106,7107],{},"能使用 Optional \u002F Union",[476,7109,7110],{},"能运行 mypy",[114,7112,7113],{},[118,7114,7115],{},"工程层",[473,7117,7118,7121,7124,7127,7130,7133],{},[476,7119,7120],{},"项目能够模块化拆分",[476,7122,7123],{},"使用 logging",[476,7125,7126],{},"配置与代码分离",[476,7128,7129],{},"建立统一异常体系",[476,7131,7132],{},"类型检查通过",[476,7134,7135],{},"异常测试通过",[114,7137,7138],{},[118,7139,7140],{},"架构层",[473,7142,7143,7146,7149,7152,7155],{},[476,7144,7145],{},"能定义简单抽象接口",[476,7147,7148],{},"理解 ABC",[476,7150,7151],{},"知道 Protocol 的基本思想",[476,7153,7154],{},"理解依赖注入",[476,7156,7157],{},"能解释插件式架构为什么降低耦合",[148,7159,7161],{"id":7160},"六本周产出清单","六、本周产出清单",[473,7163,7164,7168,7172,7176,7180,7183,7186,7189,7194,7197,7200,7205,7209,7212],{},[476,7165,7166],{},[60,7167,5358],{},[476,7169,7170],{},[60,7171,224],{},[476,7173,7174],{},[60,7175,5904],{},[476,7177,7178],{},[60,7179,6128],{},[476,7181,7182],{},"Week 1 项目完成目录拆分",[476,7184,7185],{},"logging 接入",[476,7187,7188],{},"配置文件接入",[476,7190,7191],{},[60,7192,7193],{},"exceptions.py",[476,7195,7196],{},"mypy 检查通过",[476,7198,7199],{},"异常测试",[476,7201,7202],{},[60,7203,7204],{},"pyproject.toml",[476,7206,7207],{},[60,7208,6710],{},[476,7210,7211],{},"Reader 接口设计",[476,7213,7214],{},"CSV \u002F JSON \u002F Excel Reader 骨架",[148,7216,7218],{"id":7217},"七week-2-的核心认知变化","七、Week 2 的核心认知变化",[114,7220,7221],{},"Week 1 主要解决：\"Python 中的数据是什么？数据结构怎么工作？\"",[114,7223,7224],{},"Week 2 开始解决：\"代码应该如何组织？\"",[114,7226,7227],{},"进一步：",[250,7229,7232],{"className":7230,"code":7231,"language":255},[253],"Week 1\n数据结构\n  ↓\n函数\n  ↓\n模块\n  ↓\n异常\n  ↓\n数据模型\n  ↓\n工程重构\n  ↓\n抽象接口\n  ↓\n插件式架构\n",[60,7233,7231],{"__ignoreMap":258},[114,7235,7236,7237],{},"最终希望形成一个重要的工程意识：",[118,7238,7239],{},"好的代码不只是\"能运行\"，还应该容易理解、容易修改、容易测试，并且能够在需求变化时控制修改范围。",[114,7241,7242],{},"Week 2 的最终目标，就是从\"我能写一个功能\"逐渐过渡到\"我能把功能组织成一个可维护的小型 Python 项目\"。",[148,7244,2534],{"id":2534},[114,7246,7247],{},"本周涉及的主要官方文档与规范：",[473,7249,7250,7258,7265,7274,7280,7286,7292,7299],{},[476,7251,7252,7257],{},[123,7253,7256],{"href":7254,"rel":7255},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Findex.html",[2543],"Python Tutorial","（函数、模块、包、错误与异常）",[476,7259,7260],{},[123,7261,7264],{"href":7262,"rel":7263},"https:\u002F\u002Fdocs.python.org\u002F3\u002Freference\u002Fdatamodel.html",[2543],"Python Data Model \u002F 对象模型",[476,7266,7267,2545,7270],{},[123,7268,1960],{"href":6071,"rel":7269},[2543],[123,7271,7273],{"href":6076,"rel":7272},[2543],"typing",[476,7275,7276],{},[123,7277,7279],{"href":6646,"rel":7278},[2543],"abc：抽象基类",[476,7281,7282],{},[123,7283,7285],{"href":6668,"rel":7284},[2543],"PEP 544 – Protocols: Structural subtyping",[476,7287,7288],{},[123,7289,7291],{"href":6313,"rel":7290},[2543],"logging HOWTO",[476,7293,7294],{},[123,7295,7298],{"href":7296,"rel":7297},"https:\u002F\u002Fmypy.readthedocs.io\u002Fen\u002Fstable\u002Findex.html",[2543],"mypy 文档",[476,7300,7301],{},[123,7302,7305],{"href":7303,"rel":7304},"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002F",[2543],"pytest 文档",[2580,7307,7308],{},"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":258,"searchDepth":303,"depth":303,"links":7310},[7311,7312,7313,7314,7319,7324,7329,7334,7340,7345,7350,7351,7356,7357,7358,7359],{"id":5145,"depth":303,"text":5146},{"id":5158,"depth":303,"text":5159},{"id":5251,"depth":303,"text":5252},{"id":5264,"depth":303,"text":5265,"children":7315},[7316,7317,7318],{"id":5273,"depth":34,"text":5274},{"id":5351,"depth":34,"text":5352},{"id":5535,"depth":34,"text":5536},{"id":5563,"depth":303,"text":159,"children":7320},[7321,7322,7323],{"id":5570,"depth":34,"text":5274},{"id":5644,"depth":34,"text":5352},{"id":5822,"depth":34,"text":5536},{"id":5845,"depth":303,"text":1143,"children":7325},[7326,7327,7328],{"id":5852,"depth":34,"text":5274},{"id":5899,"depth":34,"text":5352},{"id":6026,"depth":34,"text":5536},{"id":6059,"depth":303,"text":1947,"children":7330},[7331,7332,7333],{"id":6066,"depth":34,"text":5274},{"id":6123,"depth":34,"text":5352},{"id":6263,"depth":34,"text":5536},{"id":6292,"depth":303,"text":6293,"children":7335},[7336,7337,7338,7339],{"id":6303,"depth":34,"text":5274},{"id":6338,"depth":34,"text":6338},{"id":6350,"depth":34,"text":5352},{"id":6442,"depth":34,"text":5536},{"id":6468,"depth":303,"text":6469,"children":7341},[7342,7343,7344],{"id":6476,"depth":34,"text":5274},{"id":6497,"depth":34,"text":5352},{"id":6603,"depth":34,"text":5536},{"id":6630,"depth":303,"text":6631,"children":7346},[7347,7348,7349],{"id":6641,"depth":34,"text":5274},{"id":6704,"depth":34,"text":5352},{"id":6912,"depth":34,"text":6913},{"id":6948,"depth":303,"text":6949},{"id":6964,"depth":303,"text":6965,"children":7352},[7353,7354,7355],{"id":6968,"depth":34,"text":6969},{"id":6980,"depth":34,"text":6981},{"id":6987,"depth":34,"text":6988},{"id":6994,"depth":303,"text":6995},{"id":7160,"depth":303,"text":7161},{"id":7217,"depth":303,"text":7218},{"id":2534,"depth":303,"text":2534},"Week 2 从函数进阶出发，逐步学习模块、异常、数据建模、类型检查与工程化重构，最终完成一个具备模块化、日志、配置、类型检查、异常体系和插件式架构雏形的 Python 项目。","计划",{},"2026-09-20T01:51:31+08:00",{"title":5140,"description":7360},"python-week-2-functions-abstraction-modularization","writing\u002F2026-09-20-python-week-2-functions-abstraction-modularization",[2636,2637,7368,2638,7369,6089,2200,7370,7371,7372],"函数","异常","重构","模块化","架构","kVha6UBcc5UTFu7MWXuPqZeyGvS3U5feLKD1E1EWq7g",{"id":7375,"title":7376,"body":7377,"category":7897,"date":5121,"description":7898,"extension":2627,"hidden":2628,"kind":5123,"meta":7899,"navigation":30,"path":7900,"publishedAt":7901,"seo":7902,"series":5123,"slug":7903,"stem":7904,"tags":7905,"unit":5123,"updated":5123,"__hash__":7910},"writing\u002Fwriting\u002F2026-09-20-dca-spot-vs-futures.md","B站评论区幽默的定投哥们",{"type":111,"value":7378,"toc":7886},[7379,7382,7385,7388,7394,7401,7404,7407,7413,7416,7421,7424,7427,7432,7435,7440,7443,7448,7454,7457,7461,7464,7467,7470,7473,7479,7482,7485,7490,7493,7497,7500,7503,7506,7509,7512,7515,7518,7521,7524,7529,7533,7536,7539,7542,7545,7548,7551,7554,7557,7560,7563,7566,7569,7572,7575,7579,7582,7585,7588,7591,7596,7599,7616,7619,7623,7626,7629,7632,7635,7640,7643,7646,7651,7653,7657,7660,7665,7668,7671,7676,7679,7682,7685,7688,7691,7694,7697,7702,7706,7709,7712,7715,7720,7723,7729,7732,7735,7741,7744,7750,7757,7761,7764,7769,7772,7775,7780,7783,7786,7789,7792,7795,7798,7802,7805,7811,7817,7820,7823,7826,7831,7834,7839,7842,7845,7850,7853,7856,7861,7864,7867,7870,7875,7878,7881],[114,7380,7381],{},"定投现货和定投合约，可能只差两个字，结果天差地别。",[114,7383,7384],{},"今天刷到一个挺有意思的帖子。",[114,7386,7387],{},"UP 主发了一条动态，晒自己的现货定投战绩：",[7389,7390,7391],"blockquote",{},[114,7392,7393],{},"大饼不会辜负任何一个坚定信仰者。ok啊，大饼从 2024 年 9 月 1 号的 6 万开始定投到 12 万，最近一年又跌回 6 万，现在重回 8 万。定投部分的大饼回本了，后续收益拭目以待，会在群里每周更新。优质资产只要能坚持住定投，不会辜负你的。",[114,7395,7396],{},[7397,7398],"img",{"alt":7399,"src":7400},"UP 主晒的现货定投记录与策略面板","\u002Fuploads\u002Fdca-bitcoin-dca-log.jpg",[114,7402,7403],{},"面板上的数字很朴素：已运行 747 天，累计投入约 5,819 USDT，每天 15:00 定投 15 USDT。这就是一个标准的、老老实实的现货定投。",[114,7405,7406],{},"然后看评论区，画风就变了：",[114,7408,7409],{},[7397,7410],{"alt":7411,"src":7412},"B站评论区：定投的讨论","\u002Fuploads\u002Fdca-comment-section.jpg",[114,7414,7415],{},"有人跑来跟帖：",[7389,7417,7418],{},[114,7419,7420],{},"跟了，今天开始定投，天天爆仓妈的",[114,7422,7423],{},"\"定投\"和\"天天爆仓\"放在同一句话里，本身就很有喜感。",[114,7425,7426],{},"UP 主好心劝他：",[7389,7428,7429],{},[114,7430,7431],{},"老老实实定投，别碰合约这种。",[114,7433,7434],{},"他回了一句更妙的：",[7389,7436,7437],{},[114,7438,7439],{},"是的 下轮牛市止盈",[114,7441,7442],{},"然后还有人补刀：",[7389,7444,7445],{},[114,7446,7447],{},"为什么不这轮止盈，熊跌的时候再定投？",[114,7449,7450,7451],{},"看到这里是真的绷不住了：",[118,7452,7453],{},"UP 主在晒现货定投，评论区来踢馆的哥们把\"定投\"直接做在了合约上。",[114,7455,7456],{},"不是哥们，定投不是应该定投现货吗？",[148,7458,7460],{"id":7459},"一正常人理解的定投是什么","一、正常人理解的\"定投\"是什么？",[114,7462,7463],{},"通常说的定投，是指：",[114,7465,7466],{},"固定时间 + 固定金额 + 持续买入某种资产。",[114,7468,7469],{},"比如每个月拿出 1000 元买某个指数基金，或者买某种现货资产。",[114,7471,7472],{},"上涨的时候买一点，下跌的时候也买一点。",[114,7474,7475,7476,1032],{},"它的核心目的并不是预测市场什么时候涨、什么时候跌，而是",[118,7477,7478],{},"降低自己对择时的依赖",[114,7480,7481],{},"市场贵的时候买得少，便宜的时候买得多。",[114,7483,7484],{},"所以定投真正解决的问题，是：",[114,7486,7487],{},[118,7488,7489],{},"\"我什么时候买？\"",[114,7491,7492],{},"它并不能保证赚钱，也不意味着任何资产都适合定投。",[148,7494,7496],{"id":7495},"二问题来了为什么定投合约听起来这么离谱","二、问题来了：为什么\"定投合约\"听起来这么离谱？",[114,7498,7499],{},"因为合约和现货的风险结构完全不一样。",[114,7501,7502],{},"买现货，本质上是：",[114,7504,7505],{},"我花 1000 元，买价值 1000 元的资产。",[114,7507,7508],{},"哪怕资产价格跌了 50%，只要资产本身还存在，你的仓位通常还在那里。",[114,7510,7511],{},"但合约交易往往涉及：",[114,7513,7514],{},"杠杆、保证金、强平、资金费率、多空方向、短期波动风险。",[114,7516,7517],{},"假设有人用 10 倍杠杆做多。",[114,7519,7520],{},"那么标的价格出现较大幅度的不利波动，就可能让保证金迅速缩水，最终触发强平。",[114,7522,7523],{},"这时候就出现了一个非常尴尬的问题：",[114,7525,7526],{},[118,7527,7528],{},"定投的长期逻辑还没来得及发挥作用，仓位可能已经没了。",[148,7530,7532],{"id":7531},"三定投可以降低择时风险但不能消灭杠杆风险","三、定投可以降低择时风险，但不能消灭杠杆风险",[114,7534,7535],{},"这是这件事情最核心的地方。",[114,7537,7538],{},"假设一个人每个月买 1000 元现货。",[114,7540,7541],{},"市场：跌。",[114,7543,7544],{},"他：买。",[114,7546,7547],{},"市场继续跌。",[114,7549,7550],{},"他：继续买。",[114,7552,7553],{},"如果资产最终上涨，那么前面低价买入的仓位可能成为收益的一部分。",[114,7555,7556],{},"这就是典型的长期定投逻辑。",[114,7558,7559],{},"但如果换成高杠杆合约：",[114,7561,7562],{},"跌 → 仓位亏损 → 接近强平 → 强平。",[114,7564,7565],{},"下一次准备投入的 1000 元，并不能让上一笔已经被强平的仓位重新回来。",[114,7567,7568],{},"于是所谓的\"定投\"就可能变成：",[114,7570,7571],{},"定期投入资金 → 定期承担高杠杆风险 → 定期重新开仓。",[114,7573,7574],{},"这和传统意义上的 DCA 已经不是同一种风险结构了。",[148,7576,7578],{"id":7577},"四最搞笑的其实是下轮牛市止盈","四、最搞笑的其实是\"下轮牛市止盈\"",[114,7580,7581],{},"整个讨论里最有喜剧效果的一句话就是：",[114,7583,7584],{},"\"是的 下轮牛市止盈。\"",[114,7586,7587],{},"听起来非常长期主义。",[114,7589,7590],{},"但问题是：",[114,7592,7593],{},[118,7594,7595],{},"你得先活到下轮牛市。",[114,7597,7598],{},"而按下注的对象不同，命运也不同：",[473,7600,7601,7607],{},[476,7602,7603,7606],{},[118,7604,7605],{},"现货定投","（就是帖子开头那位 UP 主在做的）：熊市继续定投 → 慢慢积累仓位 → 等待下一轮行情。至少在逻辑上是可以成立的。",[476,7608,7609,7612,7613],{},[118,7610,7611],{},"高杠杆合约定投","（就是评论区那位哥们）：你判断几年后会涨，市场说\"我知道，但我现在先跌 30%\"，然后你的高杠杆仓位——",[118,7614,7615],{},"没了。",[114,7617,7618],{},"长期方向判断正确，并不意味着高杠杆仓位一定能够活到那个时候。",[114,7620,7621],{},[118,7622,7595],{},[114,7624,7625],{},"如果一个人买的是现货，那么：",[114,7627,7628],{},"熊市继续定投 → 慢慢积累仓位 → 等待下一轮行情",[114,7630,7631],{},"至少在逻辑上是可以成立的。",[114,7633,7634],{},"但如果一个人持续使用高杠杆合约，他面对的是另一套问题：",[114,7636,7637],{},[118,7638,7639],{},"你能不能让这个仓位活到你的长期判断兑现？",[114,7641,7642],{},"你判断：\"几年后会涨。\"",[114,7644,7645],{},"市场可能：\"我知道，但我现在先跌 30%。\"",[114,7647,7648,7649],{},"然后你的高杠杆仓位：",[118,7650,7615],{},[114,7652,7618],{},[148,7654,7656],{"id":7655},"五所以定投合约到底是什么","五、所以\"定投合约\"到底是什么？",[114,7658,7659],{},"如果一定要给它起一个名字，我觉得可以叫：",[114,7661,7662],{},[118,7663,7664],{},"长期主义地承担短期爆仓风险。",[114,7666,7667],{},"当然，这只是玩笑。",[114,7669,7670],{},"真正值得注意的是，它暴露出了一个很常见的投资认知问题：",[114,7672,7673],{},[118,7674,7675],{},"一个策略里的某个概念正确，并不意味着把这个概念套到任何东西上都正确。",[114,7677,7678],{},"\"定投\"是一种仓位建立方式。",[114,7680,7681],{},"\"合约\"则是一种带有杠杆、保证金和强平机制的交易工具。",[114,7683,7684],{},"把两者简单拼在一起：",[114,7686,7687],{},"定投 + 合约 = 定投合约",[114,7689,7690],{},"并不会自动得到：",[114,7692,7693],{},"长期投资 + 风险降低。",[114,7695,7696],{},"有时候得到的反而是：",[114,7698,7699],{},[118,7700,7701],{},"机械化承担高风险。",[148,7703,7705],{"id":7704},"六现货和合约的区别不只是哪个赚钱更多","六、现货和合约的区别，不只是\"哪个赚钱更多\"",[114,7707,7708],{},"很多人讨论投资的时候，容易只关注：",[114,7710,7711],{},"\"哪个收益高？\"",[114,7713,7714],{},"但更加重要的问题其实是：",[114,7716,7717],{},[118,7718,7719],{},"这个仓位能不能活到我的投资逻辑兑现？",[114,7721,7722],{},"这也是现货和合约之间非常重要的区别之一。",[114,7724,7725,7728],{},[118,7726,7727],{},"现货","可以长期持有、分批买入、熊市继续积累，不存在合约意义上的强平线。",[114,7730,7731],{},"当然，这并不意味着现货没有风险。",[114,7733,7734],{},"如果资产本身长期下跌，或者最终失去价值，现货投资者同样可能遭受巨大损失。",[114,7736,7737,7738],{},"所以：",[118,7739,7740],{},"现货 ≠ 安全。",[114,7742,7743],{},"它只是通常不存在合约那种由于杠杆和强平机制导致仓位突然归零的风险。",[114,7745,7746,7749],{},[118,7747,7748],{},"合约","则需要额外考虑：杠杆倍数、保证金率、强平价格、资金费率、仓位管理、止损、极端行情。",[114,7751,7752,7753,7756],{},"因此，合约更接近一种",[118,7754,7755],{},"交易工具","，而不是天然适合长期定投的资产持有方式。",[148,7758,7760],{"id":7759},"七真正值得长期坚持的可能不是定投两个字","七、真正值得长期坚持的，可能不是\"定投\"两个字",[114,7762,7763],{},"如果把这件事情再往深处想一步，会发现：",[114,7765,7766],{},[118,7767,7768],{},"投资策略从来不是几个流行词简单排列组合出来的。",[114,7770,7771],{},"不是\"定投 = 好\"，也不是\"合约 = 坏\"。",[114,7773,7774],{},"真正需要分析的是完整的风险结构：",[114,7776,7777],{},[118,7778,7779],{},"买什么 + 怎么买 + 用什么工具买 + 杠杆多少 + 能承受多大回撤 + 什么情况下退出。",[114,7781,7782],{},"例如：",[114,7784,7785],{},"长期看好某项资产 → 使用现货 → 分批建仓 → 控制仓位 → 承受正常波动 → 根据自己的目标和风险承受能力逐步退出。",[114,7787,7788],{},"这是一个完整的逻辑。",[114,7790,7791],{},"而：",[114,7793,7794],{},"长期看好某项资产 → 高杠杆做多 → 定期继续开仓 → 希望几年后牛市止盈。",[114,7796,7797],{},"虽然同样包含\"长期看好\"和\"定期投入\"，但中间的风险结构已经完全不同了。",[148,7799,7801],{"id":7800},"八这件事为什么会让人觉得特别好笑","八、这件事为什么会让人觉得特别好笑？",[114,7803,7804],{},"因为它把两个本来完全不同的概念硬凑在了一起。",[114,7806,7807,7808],{},"正常人的想象是：",[118,7809,7810],{},"定投 → 慢慢积累资产。",[114,7812,7813,7814],{},"结果这哥们：",[118,7815,7816],{},"定投 → 慢慢积累爆仓经验。",[114,7818,7819],{},"正常的长期投资：牛市不追、熊市继续积累、时间换空间。",[114,7821,7822],{},"他的长期投资：每天固定时间开仓、每天固定承担杠杆风险、然后希望几年以后自己还在场内。",[114,7824,7825],{},"于是整个逻辑就变成了：",[114,7827,7828],{},[118,7829,7830],{},"长期主义地短期归零。",[114,7832,7833],{},"甚至可以戏称为：",[114,7835,7836],{},[118,7837,7838],{},"DCA：Daily Capital Annihilation，每日资金毁灭计划。",[114,7840,7841],{},"当然，这只是一个玩笑。",[114,7843,7844],{},"但玩笑背后的道理其实很简单：",[114,7846,7847],{},[118,7848,7849],{},"长期持有的逻辑，必须建立在仓位能够长期存在的前提上。",[148,7851,7852],{"id":7852},"最后",[114,7854,7855],{},"所以看到\"定投合约\"这几个字，我第一反应确实就是：",[114,7857,7858,7860],{},[118,7859,7456],{}," 😂",[114,7862,7863],{},"定投最重要的意义之一，是降低自己对短期择时的依赖。",[114,7865,7866],{},"而高杠杆合约恰恰会放大短期价格波动，并引入强平风险。",[114,7868,7869],{},"所以，一个人可以长期看好某项资产，也可以采用定投策略，但这并不意味着：",[114,7871,7872],{},[118,7873,7874],{},"\"长期看好 + 定投 + 杠杆合约\"就天然等于长期投资。",[114,7876,7877],{},"有时候，真正的长期主义不是一直加仓。",[114,7879,7880],{},"而是：",[114,7882,7883],{},[118,7884,7885],{},"先保证自己的仓位能够活到自己判断兑现的那一天。",{"title":258,"searchDepth":303,"depth":303,"links":7887},[7888,7889,7890,7891,7892,7893,7894,7895,7896],{"id":7459,"depth":303,"text":7460},{"id":7495,"depth":303,"text":7496},{"id":7531,"depth":303,"text":7532},{"id":7577,"depth":303,"text":7578},{"id":7655,"depth":303,"text":7656},{"id":7704,"depth":303,"text":7705},{"id":7759,"depth":303,"text":7760},{"id":7800,"depth":303,"text":7801},{"id":7852,"depth":303,"text":7852},"观察","定投可以降低择时依赖，但把定投用在高杠杆合约上并不会消除风险，反而可能变成机械化承担爆仓风险。",{},"\u002Fwriting\u002F2026-09-20-dca-spot-vs-futures","2026-09-20T01:33:33+08:00",{"title":7376,"description":7898},"dca-spot-vs-futures","writing\u002F2026-09-20-dca-spot-vs-futures",[7906,7748,7727,7907,7908,7909],"定投","杠杆","加密货币","风险管理","Inem-bPsScDR_8zkoIVta741NIii8NhP9bhVLJ_H3Lk",{"id":7912,"title":7913,"body":7914,"category":2624,"date":9790,"description":9791,"extension":2627,"hidden":2628,"kind":132,"meta":9792,"navigation":30,"path":9793,"publishedAt":9794,"seo":9795,"series":101,"slug":9796,"stem":9797,"tags":9798,"unit":9803,"updated":5123,"__hash__":9804},"writing\u002Fwriting\u002F2026-09-09-week1-learning-outcomes.md","Week 1 学习成果｜复杂度实测、对象模型与数据结构实战",{"type":111,"value":7915,"toc":9773},[7916,7926,7942,7946,7954,7960,7978,7984,8154,8159,8164,8192,8197,8222,8231,8345,8350,8369,8374,8416,8422,8426,8434,8437,8445,8449,8458,8461,8483,8489,8494,8498,8501,8557,8562,8584,8617,8621,8638,8667,8671,8709,8733,8736,8778,8785,8789,8792,8796,8854,8860,8864,8881,8911,8915,8949,8983,8990,8994,9001,9005,9013,9016,9020,9025,9060,9065,9115,9120,9175,9180,9229,9233,9238,9281,9286,9301,9306,9375,9379,9432,9436,9441,9450,9455,9480,9485,9548,9553,9570,9577,9580,9680,9689,9691,9771],[114,7917,7918,7919,7921,7922,7925],{},"本篇是 Week 1 的",[118,7920,120],{},"，与",[123,7923,126],{"href":7924},"\u002Fwriting\u002F2026-08-25-week1-python-object-model-data-structures","分开成文：计划篇保留当时的任务与要求，这里集中放实际做出来的东西和实测数据。目前有书面成果的是 Day 2–4，Day 5–7 完成后再补进来。",[114,7927,7928,133,7930,138,7934,138,7938],{},[118,7929,132],{},[123,7931,7933],{"href":7932},"#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","Day 2 复杂度实测",[123,7935,7937],{"href":7936},"#day-3-%E6%88%90%E6%9E%9Cobject_modelmd","Day 3 object_model.md",[123,7939,7941],{"href":7940},"#day-4-%E6%88%90%E6%9E%9C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%AE%9E%E6%88%98","Day 4 数据结构实战",[148,7943,7945],{"id":7944},"day-2-成果复杂度实测与对照表","Day 2 成果｜复杂度实测与对照表",[114,7947,7948,133,7950],{},[118,7949,156],{},[123,7951,7953],{"href":7952},"\u002Fwriting\u002F2026-08-25-week1-python-object-model-data-structures#day-2list-dict-set-big-o","Day 2｜List \u002F Dict \u002F Set + Big-O",[114,7955,7956,7959],{},[118,7957,7958],{},"实验方法","（Python 3 + timeit，repeat=5 取均值）：",[473,7961,7962,7969,7972,7975],{},[476,7963,7964,7965,7968],{},"计时：",[60,7966,7967],{},"timeit.timeit(lambda: func(data), number=N) \u002F N","，得到单次操作耗时",[476,7970,7971],{},"每轮测试前重新 copy 干净数据，且 copy 不计入计时",[476,7973,7974],{},"search 测“查找不存在的值 n”（最坏情况），避开“搜第一个元素”的最好情况陷阱",[476,7976,7977],{},"dict set \u002F set add 用不存在的新键，测真实插入而非覆盖",[114,7979,7980,7983],{},[118,7981,7982],{},"实测数据","（单位：秒\u002F次）：",[166,7985,7986,8005],{},[169,7987,7988],{},[172,7989,7990,7993,7996,7999,8002],{},[175,7991,7992],{},"操作",[175,7994,7995],{},"n=1,000",[175,7997,7998],{},"n=10,000",[175,8000,8001],{},"n=100,000",[175,8003,8004],{},"增长规律",[185,8006,8007,8024,8041,8058,8074,8090,8106,8122,8137],{},[172,8008,8009,8012,8015,8018,8021],{},[190,8010,8011],{},"list index",[190,8013,8014],{},"1.07e-7",[190,8016,8017],{},"1.13e-7",[190,8019,8020],{},"1.09e-7",[190,8022,8023],{},"恒定 → O(1)",[172,8025,8026,8029,8032,8035,8038],{},[190,8027,8028],{},"list append",[190,8030,8031],{},"9.4e-8",[190,8033,8034],{},"1.00e-7",[190,8036,8037],{},"1.25e-7",[190,8039,8040],{},"基本恒定 → 均摊 O(1)",[172,8042,8043,8046,8049,8052,8055],{},[190,8044,8045],{},"list insert(0)",[190,8047,8048],{},"2.46e-7",[190,8050,8051],{},"1.58e-6",[190,8053,8054],{},"1.32e-5",[190,8056,8057],{},"n×10 → 时间×10 → O(n)",[172,8059,8060,8063,8066,8069,8072],{},[190,8061,8062],{},"list search",[190,8064,8065],{},"4.73e-6",[190,8067,8068],{},"4.64e-5",[190,8070,8071],{},"4.75e-4",[190,8073,8057],{},[172,8075,8076,8079,8082,8085,8088],{},[190,8077,8078],{},"dict get",[190,8080,8081],{},"1.41e-7",[190,8083,8084],{},"1.31e-7",[190,8086,8087],{},"1.40e-7",[190,8089,8023],{},[172,8091,8092,8095,8098,8101,8104],{},[190,8093,8094],{},"dict set",[190,8096,8097],{},"2.07e-7",[190,8099,8100],{},"1.88e-7",[190,8102,8103],{},"1.92e-7",[190,8105,8023],{},[172,8107,8108,8111,8114,8117,8120],{},[190,8109,8110],{},"dict membership",[190,8112,8113],{},"1.04e-7",[190,8115,8116],{},"9.5e-8",[190,8118,8119],{},"9.6e-8",[190,8121,8023],{},[172,8123,8124,8127,8130,8133,8135],{},[190,8125,8126],{},"set add",[190,8128,8129],{},"1.10e-7",[190,8131,8132],{},"1.02e-7",[190,8134,8034],{},[190,8136,8023],{},[172,8138,8139,8142,8145,8148,8151],{},[190,8140,8141],{},"set membership",[190,8143,8144],{},"9.9e-8",[190,8146,8147],{},"1.29e-7",[190,8149,8150],{},"9.1e-8",[190,8152,8153],{},"恒定（小幅波动为噪声）→ O(1)",[114,8155,8156],{},[7397,8157],{"alt":258,"src":8158},"\u002Fuploads\u002F684ae7e5-cb2e-4eb8-b220-bcb49fea2bfc.png",[114,8160,8161],{},[118,8162,8163],{},"实验中的三个重要发现：",[770,8165,8166,8180,8186],{},[476,8167,8168,8171,8172,8175,8176,8179],{},[118,8169,8170],{},"最好情况陷阱","：最初 search 测 ",[60,8173,8174],{},"0 in lst","，结果恒定在 ~8e-8 秒，看起来像 O(1)。改成 ",[60,8177,8178],{},"n in lst","（不存在，必须扫全表）后立刻变成标准线性增长。→ 测什么数据点，直接决定结论对不对。",[476,8181,8182,8185],{},[118,8183,8184],{},"固定开销稀释线性规律","：insert 在三个规模间只增长 6~8 倍而非 10 倍，因为测得的时间 = 固定开销（lambda 调用等 ~1e-7s）+ 线性成本。减去固定开销后，倍数恢复到 ×10 \u002F ×9。→ 小规模数据测出来的主要是调用开销，不是算法本身。",[476,8187,8188,8191],{},[118,8189,8190],{},"同为 O(n)，常数差 36 倍","：n=100,000 时 insert(0)（memmove 连续内存搬运）比 search（Python 层逐个比较）快约 36 倍。→ 大 O 只描述增长趋势，不描述绝对速度；真实选型要靠实测。",[114,8193,8194],{},[118,8195,8196],{},"重点理解：",[473,8198,8199,8206,8209,8212,8219],{},[476,8200,8201,8202,8205],{},"list 为什么随机访问 O(1)\n→ list 底层是连续的指针数组，",[60,8203,8204],{},"lst[i]"," 通过「首地址 + i × 指针大小」直接算出位置，一步到位，与 n 无关。",[476,8207,8208],{},"list 为什么头部插入 O(n)\n→ 连续内存要保序，insert(0) 必须把后面全部 n 个元素整体后移一位（C 层 memmove）。实测 n=100,000 时约 1.3e-5 秒，随 n 线性增长。",[476,8210,8211],{},"dict 为什么平均查找 O(1)\n→ 哈希表：key 经 hash 函数算出槽位，直接跳过去取，不需要遍历。实测 get 在 n=1,000~100,000 间稳定在 ~1.4e-7 秒，纹丝不动。",[476,8213,8214,8215,8218],{},"set 为什么 membership 平均 O(1)\n→ set 本质是只有 key 没有 value 的哈希表，",[60,8216,8217],{},"x in s"," 同样直接算槽位。实测 ~1e-7 秒，甚至比 list 的 index 还快（哈希路径更短）。",[476,8220,8221],{},"为什么“平均 O(1)”不等于“永远 O(1)”\n→ 哈希冲突：多个 key 落进同一槽位时退化为链式探测，最坏 O(n)。\n→ rehash 扩容：元素数超过负载因子阈值时，哈希表要扩容并重新安置全部元素，单次插入瞬间变 O(n)，均摊后仍是 O(1)。\n→ 所以大 O 有“平均\u002F均摊”的前提；benchmark 测大量操作取平均，恰好把这个因素平均掉了。",[114,8223,8224,8230],{},[118,8225,8226,8229],{},[60,8227,8228],{},"complexity.md"," 复杂度对照表","（✅ = 本次实测验证；未标 = 理论值，本轮未测）：",[166,8232,8233,8248],{},[169,8234,8235],{},[172,8236,8237,8239,8242,8245],{},[175,8238,7992],{},[175,8240,8241],{},"list",[175,8243,8244],{},"dict",[175,8246,8247],{},"set",[185,8249,8250,8267,8286,8312,8328],{},[172,8251,8252,8255,8261,8264],{},[190,8253,8254],{},"随机访问",[190,8256,8257,8260],{},[118,8258,8259],{},"O(1)"," ✅",[190,8262,8263],{},"—（无索引概念）",[190,8265,8266],{},"—（不支持下标）",[172,8268,8269,8272,8277,8283],{},[190,8270,8271],{},"查找",[190,8273,8274,8260],{},[118,8275,8276],{},"O(n)",[190,8278,8279,8282],{},[118,8280,8281],{},"O(1) 平均"," ✅（按 key）",[190,8284,8285],{},"—",[172,8287,8288,8291,8304,8308],{},[190,8289,8290],{},"插入",[190,8292,8293,8294,8297,8298,8301,8302,8260],{},"append 尾部 ",[118,8295,8296],{},"O(1) 均摊"," ✅；",[8299,8300],"br",{},"insert(0) 头部 ",[118,8303,8276],{},[190,8305,8306,8260],{},[118,8307,8281],{},[190,8309,8310,8260],{},[118,8311,8281],{},[172,8313,8314,8317,8323,8326],{},[190,8315,8316],{},"删除",[190,8318,8319,8320,8322],{},"pop() 尾部 O(1)；",[8299,8321],{},"pop(0)\u002Fremove O(n)（未实测）",[190,8324,8325],{},"O(1) 平均（未实测）",[190,8327,8325],{},[172,8329,8330,8333,8337,8341],{},[190,8331,8332],{},"membership",[190,8334,8335,8260],{},[118,8336,8276],{},[190,8338,8339,8260],{},[118,8340,8281],{},[190,8342,8343,8260],{},[118,8344,8281],{},[114,8346,8347],{},[118,8348,8349],{},"原因解释：",[473,8351,8352,8357,8363],{},[476,8353,8354,8356],{},[118,8355,8241],{},"：底层是连续指针数组。按下标访问是算术运算 → O(1)；查找、头部插入\u002F删除都要扫描或移动大量元素 → O(n)；尾部 append 均摊 O(1)（偶尔扩容拷贝全表，摊到每次可忽略）。",[476,8358,8359,8362],{},[118,8360,8361],{},"dict \u002F set","：底层是哈希表。get\u002Fset\u002Fadd\u002Fdel\u002Fin 都先算 hash 定位槽位，直接到达 → 平均 O(1)。代价：无序、更占内存、key 必须可哈希（不可变类型）。",[476,8364,8365,8368],{},[118,8366,8367],{},"选型口诀","：按下标快速访问 → list；按 key 快速存取\u002F判断存在性 → dict\u002Fset；频繁头部插删 → 用 collections.deque。",[114,8370,8371],{},[118,8372,8373],{},"待补项（Day 3 或有空时）：",[473,8375,8378,8399,8405],{"className":8376},[8377],"contains-task-list",[476,8379,8382,8386,8387,1332,8390,1332,8393,1332,8396],{"className":8380},[8381],"task-list-item",[8383,8384],"input",{"disabled":30,"type":8385},"checkbox"," 补测 delete 类操作：",[60,8388,8389],{},"list.pop(0)",[60,8391,8392],{},"list.remove()",[60,8394,8395],{},"dict del",[60,8397,8398],{},"set discard",[476,8400,8402,8404],{"className":8401},[8381],[8383,8403],{"disabled":30,"type":8385}," 补测 1,000,000 规模（注意调小 number，search 会很慢）",[476,8406,8408,8410,8411,566,8414],{"className":8407},[8381],[8383,8409],{"disabled":30,"type":8385}," 可选：加 collections.deque 对比 ",[60,8412,8413],{},"popleft",[60,8415,8389],{},[114,8417,8418,8421],{},[118,8419,8420],{},"未做的部分","（delete、100 万规模）如实标注了“未实测\u002F未测”。",[148,8423,8425],{"id":8424},"day-3-成果object_modelmd","Day 3 成果｜object_model.md",[114,8427,8428,133,8430],{},[118,8429,156],{},[123,8431,8433],{"href":8432},"\u002Fwriting\u002F2026-08-25-week1-python-object-model-data-structures#day-3function-scope-copy","Day 3｜Function + Scope + Copy",[114,8435,8436],{},"验收要求写一页解释 name → object → reference → scope 的文档，以下是当天产出的全文：",[7389,8438,8439],{},[114,8440,8441,8442],{},"Day 3 验收文档 · 核心链条：",[118,8443,8444],{},"名字 → 对象 → 引用 → 作用域",[161,8446,8448],{"id":8447},"一总览一行代码里发生了什么","一、总览：一行代码里发生了什么",[250,8450,8452],{"className":288,"code":8451,"language":290,"meta":258,"style":258},"a = [1, 2, 3]\n",[60,8453,8454],{"__ignoreMap":258},[294,8455,8456],{"class":296,"line":297},[294,8457,8451],{},[114,8459,8460],{},"这一行做了两件事：",[770,8462,8463,8470],{},[476,8464,8465,8466,8469],{},"内存里创建了一个 list ",[118,8467,8468],{},"object","（对象）",[476,8471,8472,8473,8475,8476,8478,8479,8482],{},"创建了一个 ",[118,8474,6159],{},"（名字）",[60,8477,123],{},"，让它 ",[118,8480,8481],{},"reference","（引用\u002F指向）这个对象",[250,8484,8487],{"className":8485,"code":8486,"language":255},[253],"a（name，标签）\n   │ reference\n   ▼\n[1, 2, 3]（object，内存中真实的数据）\n",[60,8488,8486],{"__ignoreMap":258},[114,8490,8491],{},[118,8492,8493],{},"赋值只是贴标签，永远不复制对象。",[161,8495,8497],{"id":8496},"二object对象","二、object（对象）",[114,8499,8500],{},"Python 中一切数据都是对象。每个对象有三个属性：",[166,8502,8503,8516],{},[169,8504,8505],{},[172,8506,8507,8510,8513],{},[175,8508,8509],{},"属性",[175,8511,8512],{},"获取方式",[175,8514,8515],{},"特点",[185,8517,8518,8531,8543],{},[172,8519,8520,8523,8528],{},[190,8521,8522],{},"身份 id",[190,8524,8525],{},[60,8526,8527],{},"id(x)",[190,8529,8530],{},"唯一，对象存活期内不变",[172,8532,8533,8535,8540],{},[190,8534,2093],{},[190,8536,8537],{},[60,8538,8539],{},"type(x)",[190,8541,8542],{},"决定对象能做什么操作",[172,8544,8545,8548,8554],{},[190,8546,8547],{},"值",[190,8549,8550,8553],{},[60,8551,8552],{},"x"," 本身",[190,8555,8556],{},"mutable 可改 \u002F immutable 不可改",[8558,8559,8561],"h4",{"id":8560},"mutable-vs-immutable","mutable vs immutable",[473,8563,8564,8574],{},[476,8565,8566,8569,8570,8573],{},[118,8567,8568],{},"mutable（可变）","：list、dict、set → 可以",[118,8571,8572],{},"原地修改","，id 不变",[476,8575,8576,8579,8580,8583],{},[118,8577,8578],{},"immutable（不可变）","：int、str、tuple → 不能原地修改，任何“修改”\n都会",[118,8581,8582],{},"创建新对象","，让名字指向新地方",[250,8585,8587],{"className":288,"code":8586,"language":290,"meta":258,"style":258},"a = [1, 2, 3]\na[0] = 99      # 原地改，id(a) 不变\n\ns = \"hello\"\ns = s + \"!\"    # 不是原地改！创建了新字符串 \"hello!\"，s 改指向它\n               # 旧的 \"hello\" 没人引用了，被垃圾回收\n",[60,8588,8589,8593,8598,8602,8607,8612],{"__ignoreMap":258},[294,8590,8591],{"class":296,"line":297},[294,8592,8451],{},[294,8594,8595],{"class":296,"line":303},[294,8596,8597],{},"a[0] = 99      # 原地改，id(a) 不变\n",[294,8599,8600],{"class":296,"line":34},[294,8601,317],{"emptyLinePlaceholder":30},[294,8603,8604],{"class":296,"line":314},[294,8605,8606],{},"s = \"hello\"\n",[294,8608,8609],{"class":296,"line":320},[294,8610,8611],{},"s = s + \"!\"    # 不是原地改！创建了新字符串 \"hello!\"，s 改指向它\n",[294,8613,8614],{"class":296,"line":326},[294,8615,8616],{},"               # 旧的 \"hello\" 没人引用了，被垃圾回收\n",[161,8618,8620],{"id":8619},"三name-与-reference","三、name 与 reference",[473,8622,8623,8629],{},[476,8624,8625,8626],{},"name（变量名）不“装”数据，只是指向对象的",[118,8627,8628],{},"标签",[476,8630,8631,8634,8635,8637],{},[60,8632,8633],{},"b = a"," 复制的是 ",[118,8636,8481],{},"，不是 object：",[250,8639,8641],{"className":288,"code":8640,"language":290,"meta":258,"style":258},"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,8642,8643,8647,8652,8657,8662],{"__ignoreMap":258},[294,8644,8645],{"class":296,"line":297},[294,8646,8451],{},[294,8648,8649],{"class":296,"line":303},[294,8650,8651],{},"b = a              # b 和 a 两个标签，指向同一个对象\n",[294,8653,8654],{"class":296,"line":34},[294,8655,8656],{},"b.append(4)\n",[294,8658,8659],{"class":296,"line":314},[294,8660,8661],{},"print(a)           # [1, 2, 3, 4] ← a 也“变”了（其实是同一个对象变了）\n",[294,8663,8664],{"class":296,"line":320},[294,8665,8666],{},"print(a is b)      # True：同一个对象\n",[8558,8668,8670],{"id":8669},"vs-is","== vs is",[166,8672,8673,8683],{},[169,8674,8675],{},[172,8676,8677,8680],{},[175,8678,8679],{},"运算符",[175,8681,8682],{},"比较的是什么",[185,8684,8685,8697],{},[172,8686,8687,8692],{},[190,8688,8689],{},[60,8690,8691],{},"==",[190,8693,8694,8695],{},"对象的",[118,8696,8547],{},[172,8698,8699,8704],{},[190,8700,8701],{},[60,8702,8703],{},"is",[190,8705,8694,8706,8708],{},[118,8707,6156],{},"（是否同一个对象）",[250,8710,8712],{"className":288,"code":8711,"language":290,"meta":258,"style":258},"a = [1, 2, 3]\nb = [1, 2, 3]\na == b   # True  ：值一样\na is b   # False ：两个不同的对象\n",[60,8713,8714,8718,8723,8728],{"__ignoreMap":258},[294,8715,8716],{"class":296,"line":297},[294,8717,8451],{},[294,8719,8720],{"class":296,"line":303},[294,8721,8722],{},"b = [1, 2, 3]\n",[294,8724,8725],{"class":296,"line":34},[294,8726,8727],{},"a == b   # True  ：值一样\n",[294,8729,8730],{"class":296,"line":314},[294,8731,8732],{},"a is b   # False ：两个不同的对象\n",[8558,8734,8735],{"id":8735},"拷贝三级表",[166,8737,8738,8747],{},[169,8739,8740],{},[172,8741,8742,8744],{},[175,8743,384],{},[175,8745,8746],{},"效果",[185,8748,8749,8758,8768],{},[172,8750,8751,8755],{},[190,8752,8753],{},[60,8754,8633],{},[190,8756,8757],{},"只复制引用，完全共享",[172,8759,8760,8765],{},[190,8761,8762],{},[60,8763,8764],{},"copy.copy(a)",[190,8766,8767],{},"浅拷贝：只拷最外层，内层仍共享",[172,8769,8770,8775],{},[190,8771,8772],{},[60,8773,8774],{},"copy.deepcopy(a)",[190,8776,8777],{},"深拷贝：所有层级全拷，完全独立",[114,8779,8780,8781,8784],{},"浅拷贝对内层",[118,8782,8783],{},"一律只传引用","（不管可变不可变）。\n共享 immutable 对象无害（改不了）；共享 mutable 对象才有坑——\n这就是浅拷贝问题只出现在嵌套可变对象身上的原因。",[161,8786,8788],{"id":8787},"四scope作用域","四、scope（作用域）",[114,8790,8791],{},"name 通过 reference 连接 object，而**“这个名字指向哪个对象”由 scope 决定**。",[8558,8793,8795],{"id":8794},"legb-查找顺序读取变量时","LEGB 查找顺序（读取变量时）",[166,8797,8798,8808],{},[169,8799,8800],{},[172,8801,8802,8805],{},[175,8803,8804],{},"层",[175,8806,8807],{},"含义",[185,8809,8810,8821,8832,8843],{},[172,8811,8812,8818],{},[190,8813,8814,8817],{},[118,8815,8816],{},"L"," Local",[190,8819,8820],{},"当前函数内部",[172,8822,8823,8829],{},[190,8824,8825,8828],{},[118,8826,8827],{},"E"," Enclosing",[190,8830,8831],{},"外层嵌套函数",[172,8833,8834,8840],{},[190,8835,8836,8839],{},[118,8837,8838],{},"G"," Global",[190,8841,8842],{},"模块顶层",[172,8844,8845,8851],{},[190,8846,8847,8850],{},[118,8848,8849],{},"B"," Built-in",[190,8852,8853],{},"内置（print、len…）",[114,8855,8856,8857,1032],{},"从 L 往外逐层找，",[118,8858,8859],{},"找到即停",[8558,8861,8863],{"id":8862},"最重要的一条规则赋值即局部","⭐ 最重要的一条规则：赋值即局部",[473,8865,8866,8872],{},[476,8867,8868,8871],{},[118,8869,8870],{},"读取","一个名字：按 LEGB 向外查找",[476,8873,8874,8877,8878],{},[118,8875,8876],{},"赋值","一个名字：直接在 L 层创建局部变量，",[118,8879,8880],{},"不向外查找",[250,8882,8884],{"className":288,"code":8883,"language":290,"meta":258,"style":258},"x = 10\ndef func():\n    print(x)   # 读取 → LEGB 找到全局 x → 10\ndef func2():\n    x = 20     # 赋值 → 在函数内新建局部 x，全局 x 不受影响\n",[60,8885,8886,8891,8896,8901,8906],{"__ignoreMap":258},[294,8887,8888],{"class":296,"line":297},[294,8889,8890],{},"x = 10\n",[294,8892,8893],{"class":296,"line":303},[294,8894,8895],{},"def func():\n",[294,8897,8898],{"class":296,"line":34},[294,8899,8900],{},"    print(x)   # 读取 → LEGB 找到全局 x → 10\n",[294,8902,8903],{"class":296,"line":314},[294,8904,8905],{},"def func2():\n",[294,8907,8908],{"class":296,"line":320},[294,8909,8910],{},"    x = 20     # 赋值 → 在函数内新建局部 x，全局 x 不受影响\n",[8558,8912,8914],{"id":8913},"global-nonlocal覆盖默认规则","global \u002F nonlocal：覆盖默认规则",[166,8916,8917,8927],{},[169,8918,8919],{},[172,8920,8921,8924],{},[175,8922,8923],{},"关键字",[175,8925,8926],{},"声明效果",[185,8928,8929,8939],{},[172,8930,8931,8936],{},[190,8932,8933],{},[60,8934,8935],{},"global x",[190,8937,8938],{},"x 属于全局 → 函数内赋值改的是全局变量",[172,8940,8941,8946],{},[190,8942,8943],{},[60,8944,8945],{},"nonlocal x",[190,8947,8948],{},"x 属于外层函数 → 函数内赋值改的是外层变量",[250,8950,8952],{"className":288,"code":8951,"language":290,"meta":258,"style":258},"x = 10\ndef change():\n    global x    # 不再“赋值即局部”，直接改全局的 x\n    x = 20\nchange()\nprint(x)        # 20\n",[60,8953,8954,8958,8963,8968,8973,8978],{"__ignoreMap":258},[294,8955,8956],{"class":296,"line":297},[294,8957,8890],{},[294,8959,8960],{"class":296,"line":303},[294,8961,8962],{},"def change():\n",[294,8964,8965],{"class":296,"line":34},[294,8966,8967],{},"    global x    # 不再“赋值即局部”，直接改全局的 x\n",[294,8969,8970],{"class":296,"line":314},[294,8971,8972],{},"    x = 20\n",[294,8974,8975],{"class":296,"line":320},[294,8976,8977],{},"change()\n",[294,8979,8980],{"class":296,"line":326},[294,8981,8982],{},"print(x)        # 20\n",[114,8984,8985,8986,8989],{},"不加声明却赋值，又想读写外层\u002F全局变量 → 报\n",[60,8987,8988],{},"UnboundLocalError","（因为赋值让名字变局部，局部还没赋值就被读取了）。",[161,8991,8993],{"id":8992},"五一句话总结","五、一句话总结",[7389,8995,8996],{},[114,8997,8998],{},[118,8999,9000],{},"对象活在内存里（object），名字只是标签；\n赋值复制的是引用；\n名字指向哪个对象，由作用域规则决定——\n读取走 LEGB，赋值即局部（除非 global \u002F nonlocal）。",[148,9002,9004],{"id":9003},"day-4-成果数据结构实战","Day 4 成果｜数据结构实战",[114,9006,9007,133,9009],{},[118,9008,156],{},[123,9010,9012],{"href":9011},"\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｜数据结构实战",[114,9014,9015],{},"三道任务的完整解法、对比和当时写下的总结。",[161,9017,9019],{"id":9018},"数据去重list-版-vs-set-版","数据去重：list 版 vs set 版",[114,9021,9022,133],{},[118,9023,9024],{},"list 版本",[250,9026,9028],{"className":288,"code":9027,"language":290,"meta":258,"style":258},"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,9029,9030,9035,9040,9045,9050,9055],{"__ignoreMap":258},[294,9031,9032],{"class":296,"line":297},[294,9033,9034],{},"def drop_duplicate_list(data):\n",[294,9036,9037],{"class":296,"line":303},[294,9038,9039],{},"    results = []\n",[294,9041,9042],{"class":296,"line":34},[294,9043,9044],{},"    for i in data:\n",[294,9046,9047],{"class":296,"line":314},[294,9048,9049],{},"        if i not in results:  # 每次判断最坏要比对所有元素 → O(n)\n",[294,9051,9052],{"class":296,"line":320},[294,9053,9054],{},"            results.append(i)\n",[294,9056,9057],{"class":296,"line":326},[294,9058,9059],{},"    return results\n",[114,9061,9062,133],{},[118,9063,9064],{},"set\u002Fdict 版本",[250,9066,9068],{"className":288,"code":9067,"language":290,"meta":258,"style":258},"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,9069,9070,9075,9079,9084,9088,9093,9098,9102,9106,9110],{"__ignoreMap":258},[294,9071,9072],{"class":296,"line":297},[294,9073,9074],{},"def drop_duplicate_set(data):\n",[294,9076,9077],{"class":296,"line":303},[294,9078,9039],{},[294,9080,9081],{"class":296,"line":34},[294,9082,9083],{},"    seen = set()\n",[294,9085,9086],{"class":296,"line":314},[294,9087,9044],{},[294,9089,9090],{"class":296,"line":320},[294,9091,9092],{},"        if i['id'] not in seen:  # 哈希查找 → O(1)\n",[294,9094,9095],{"class":296,"line":326},[294,9096,9097],{},"            seen.add(i['id'])\n",[294,9099,9100],{"class":296,"line":332},[294,9101,9054],{},[294,9103,9104],{"class":296,"line":338},[294,9105,9059],{},[294,9107,9108],{"class":296,"line":343},[294,9109,317],{"emptyLinePlaceholder":30},[294,9111,9112],{"class":296,"line":349},[294,9113,9114],{},"print(drop_duplicate_set(users))\n",[114,9116,9117,133],{},[118,9118,9119],{},"复杂度比较（任务要求）",[166,9121,9122,9142],{},[169,9123,9124],{},[172,9125,9126,9129,9136,9139],{},[175,9127,9128],{},"版本",[175,9130,9131,9132,9135],{},"单次 ",[60,9133,9134],{},"in"," 判断",[175,9137,9138],{},"总复杂度",[175,9140,9141],{},"数据量 100 万时",[185,9143,9144,9160],{},[172,9145,9146,9149,9152,9157],{},[190,9147,9148],{},"list 版",[190,9150,9151],{},"最坏 O(n)，挨个翻",[190,9153,9154],{},[118,9155,9156],{},"O(n²)",[190,9158,9159],{},"10¹² 次操作 😱",[172,9161,9162,9165,9168,9172],{},[190,9163,9164],{},"set 版",[190,9166,9167],{},"O(1)，哈希直达",[190,9169,9170],{},[118,9171,8276],{},[190,9173,9174],{},"10⁶ 次操作 ✅",[114,9176,9177,133],{},[118,9178,9179],{},"学习总结",[473,9181,9182,9188,9213],{},[476,9183,9184,9187],{},[118,9185,9186],{},"去重 = 构建新列表，不是删除原数据","。边遍历边 remove 会让元素前移导致漏检（经典 bug）；正确做法是原数据不动，把“第一次出现的”收集到新列表。",[476,9189,9190,9193,9194,9197,9198,9201,9202],{},[118,9191,9192],{},"可哈希规则","（踩坑：",[60,9195,9196],{},"set.add({\"id\":1})"," 报 ",[60,9199,9200],{},"unhashable type: 'dict'","）：\n",[473,9203,9204,9207,9210],{},[476,9205,9206],{},"可哈希（能进 set \u002F 当 dict 键）：int、str、float、bool、tuple",[476,9208,9209],{},"不可哈希：list、dict、set",[476,9211,9212],{},"原因：set 靠哈希值当“门牌号”定位元素，可变对象内容会变，门牌号会失效",[476,9214,9215,133,9218,9221,9222,9225,9226,1032],{},[118,9216,9217],{},"set 与 list 分工模式",[60,9219,9220],{},"seen","（set）只存 id 负责快速判断；",[60,9223,9224],{},"result","（list）存完整 dict 负责输出。",[118,9227,9228],{},"set 管判断，list 管输出",[161,9230,9232],{"id":9231},"频率统计手写-dict-vs-counter","频率统计：手写 dict vs Counter",[114,9234,9235,133],{},[118,9236,9237],{},"不使用 Counter",[250,9239,9241],{"className":288,"code":9240,"language":290,"meta":258,"style":258},"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,9242,9243,9248,9253,9258,9263,9268,9272,9276],{"__ignoreMap":258},[294,9244,9245],{"class":296,"line":297},[294,9246,9247],{},"def count_fruit(data):\n",[294,9249,9250],{"class":296,"line":303},[294,9251,9252],{},"    results = dict()\n",[294,9254,9255],{"class":296,"line":34},[294,9256,9257],{},"    for w in data:\n",[294,9259,9260],{"class":296,"line":314},[294,9261,9262],{},"        results[w] = results.get(w, 0) + 1\n",[294,9264,9265],{"class":296,"line":320},[294,9266,9267],{},"        # 键存在 → 取值+1；不存在 → 默认 0 再+1（地道写法 ⭐）\n",[294,9269,9270],{"class":296,"line":326},[294,9271,9059],{},[294,9273,9274],{"class":296,"line":332},[294,9275,317],{"emptyLinePlaceholder":30},[294,9277,9278],{"class":296,"line":338},[294,9279,9280],{},"print(count_fruit(words))\n",[114,9282,9283,133],{},[118,9284,9285],{},"使用 Counter",[250,9287,9289],{"className":288,"code":9288,"language":290,"meta":258,"style":258},"from collections import Counter\nprint(Counter(words), Counter(words).most_common(2))\n",[60,9290,9291,9296],{"__ignoreMap":258},[294,9292,9293],{"class":296,"line":297},[294,9294,9295],{},"from collections import Counter\n",[294,9297,9298],{"class":296,"line":303},[294,9299,9300],{},"print(Counter(words), Counter(words).most_common(2))\n",[114,9302,9303,133],{},[118,9304,9305],{},"两种方案比较（任务要求）",[166,9307,9308,9321],{},[169,9309,9310],{},[172,9311,9312,9315,9318],{},[175,9313,9314],{},"能力",[175,9316,9317],{},"手写 dict",[175,9319,9320],{},"Counter",[185,9322,9323,9334,9345,9359],{},[172,9324,9325,9328,9331],{},[190,9326,9327],{},"统计",[190,9329,9330],{},"手写循环",[190,9332,9333],{},"✅ 自动",[172,9335,9336,9339,9342],{},[190,9337,9338],{},"缺键",[190,9340,9341],{},"💥 KeyError",[190,9343,9344],{},"✅ 返回 0 不报错",[172,9346,9347,9350,9353],{},[190,9348,9349],{},"排名",[190,9351,9352],{},"手写 sorted 一长串",[190,9354,2466,9355,9358],{},[60,9356,9357],{},"most_common(n)"," 一行",[172,9360,9361,9364,9367],{},[190,9362,9363],{},"运算",[190,9365,9366],{},"自己想办法",[190,9368,2466,9369,1332,9372],{},[60,9370,9371],{},"c1 + c2",[60,9373,9374],{},"c1 - c2",[114,9376,9377,133],{},[118,9378,9179],{},[473,9380,9381,9388,9404,9417,9423],{},[476,9382,9383,9384,9387],{},"演进过三种写法：①两遍扫描（先全部登记为 0 再累加）→ ②if\u002Felse 一遍扫描 → ③ ",[60,9385,9386],{},"results[w] = results.get(w, 0) + 1"," 一行搞定。三种都对，get 版最地道。",[476,9389,9390,133,9393,9396,9397,9400,9401,1032],{},[118,9391,9392],{},"dict 的 in 查的是键",[60,9394,9395],{},"x in d"," ≡ ",[60,9398,9399],{},"x in d.keys()","；查值要 ",[60,9402,9403],{},"x in d.values()",[476,9405,9406,9409,9410,1889,9413,9416],{},[60,9407,9408],{},"most_common(2)"," 返回的是",[118,9411,9412],{},"元组的列表",[60,9414,9415],{},"[('apple', 3), ('banana', 2)]","，不是 dict，要按格式打印得再遍历。",[476,9418,9419,9422],{},[118,9420,9421],{},"启示","：遇到常见套路，先查标准库有没有现成工具（Counter 就是“为频率统计量身定做的 dict 子类”）。",[476,9424,9425,9428,9429,9431],{},[60,9426,9427],{},"\"\"\"...\"\"\""," 不是注释，是没被赋值的字符串；多行注释用每行 ",[60,9430,76],{},"（编辑器 Ctrl+\u002F）。",[161,9433,9435],{"id":9434},"多字段排序取负法-vs-stable-sort","多字段排序：取负法 vs stable sort",[114,9437,9438],{},[118,9439,9440],{},"武器 1：取负法（一次排序）",[250,9442,9444],{"className":288,"code":9443,"language":290,"meta":258,"style":258},"sorted(users, key=lambda u: (-u['score'], u['age'], u['name']))\n",[60,9445,9446],{"__ignoreMap":258},[294,9447,9448],{"class":296,"line":297},[294,9449,9443],{},[114,9451,9452],{},[118,9453,9454],{},"武器 2：stable sort（三次排序）",[250,9456,9458],{"className":288,"code":9457,"language":290,"meta":258,"style":258},"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,9459,9460,9465,9470,9475],{"__ignoreMap":258},[294,9461,9462],{"class":296,"line":297},[294,9463,9464],{},"user = sorted(user, key=lambda u: u[\"name\"])                 # 先排第三优先级\n",[294,9466,9467],{"class":296,"line":303},[294,9468,9469],{},"user = sorted(user, key=lambda u: u[\"age\"])                  # 再排第二优先级\n",[294,9471,9472],{"class":296,"line":34},[294,9473,9474],{},"user = sorted(user, key=lambda u: u[\"score\"], reverse=True)  # 最后排主键\n",[294,9476,9477],{"class":296,"line":314},[294,9478,9479],{},"print(user)\n",[114,9481,9482,133],{},[118,9483,9484],{},"sorted 与 Sorting HOW TO 的理解（任务要求必须自己理解）",[473,9486,9487,9528,9538],{},[476,9488,9489,9492],{},[118,9490,9491],{},"key 为什么是函数而不是直接写键名？",[473,9493,9494,9501,9504,9514,9525],{},[476,9495,9496,9497,9500],{},"dict 之间没有定义 ",[60,9498,9499],{},"\u003C"," 比较，且 Python 不知道你想按哪个字段排",[476,9502,9503],{},"key 是“提取比较依据”的机器：每个元素先被 key 函数加工，sorted 拿返回值排队",[476,9505,9506,9507,1332,9510,9513],{},"数据不动、规则随传随换（",[60,9508,9509],{},"len",[60,9511,9512],{},"str.lower","、lambda 都能当 key）",[476,9515,9516,9517,9520,9521,9524],{},"⚠️ ",[60,9518,9519],{},"key=\"age\""," 报错 ",[60,9522,9523],{},"'str' object is not callable","——key 必须是 callable",[476,9526,9527],{},"函数是对象（Day 3）的直接应用：把 lambda 当参数传进去",[476,9529,9530,9533,9534,9537],{},[118,9531,9532],{},"元组优先级","：key 返回元组时从左到右比，",[118,9535,9536],{},"第 1 位 = 主键","，平手才比第 2 位。元组顺序必须和要求的优先级一字不差（踩坑：曾错写成 score, name, age，数据碰巧掩盖了错误）。取负法局限：字符串不能取负。",[476,9539,9540,9543,9544,9547],{},[118,9541,9542],{},"stable sort（稳定排序）","：值相等的元素排序后保持原相对顺序。所以",[118,9545,9546],{},"从最次要的键开始排","——主键最后排，主键并列者保持的是上一轮排好的次要键顺序。越重要的键越晚排，越晚的越权威。",[114,9549,9550,133],{},[118,9551,9552],{},"两种武器的统一心智模型",[250,9554,9558],{"className":9555,"code":9556,"language":9557,"meta":258,"style":258},"language-plain shiki shiki-themes github-light github-dark","取负法：[主键 | 次键 | 最次] ← 空间上从左到右，第 1 位最权威\n三连排：最次 → 次键 → 主键 ← 时间上从早到晚，最后排的最权威\n","plain",[60,9559,9560,9565],{"__ignoreMap":258},[294,9561,9562],{"class":296,"line":297},[294,9563,9564],{},"取负法：[主键 | 次键 | 最次] ← 空间上从左到右，第 1 位最权威\n",[294,9566,9567],{"class":296,"line":303},[294,9568,9569],{},"三连排：最次 → 次键 → 主键 ← 时间上从早到晚，最后排的最权威\n",[114,9571,9572,9573,9576],{},"正确输出（两种方法一致）：",[118,9574,9575],{},"C(19,95) → B(20,95) → A(20,90)","（score 并列的 B、C 由 age 升序决定）",[161,9578,9579],{"id":9579},"可复用模式与待改进",[250,9581,9583],{"className":288,"code":9582,"language":290,"meta":258,"style":258},"# 模式 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,9584,9585,9590,9595,9600,9605,9610,9614,9619,9624,9628,9632,9637,9642,9647,9651,9656,9662,9668,9674],{"__ignoreMap":258},[294,9586,9587],{"class":296,"line":297},[294,9588,9589],{},"# 模式 1：过滤收集（读旧的、造新的）\n",[294,9591,9592],{"class":296,"line":303},[294,9593,9594],{},"result = []\n",[294,9596,9597],{"class":296,"line":34},[294,9598,9599],{},"for item in data:\n",[294,9601,9602],{"class":296,"line":314},[294,9603,9604],{},"    if 条件(item):\n",[294,9606,9607],{"class":296,"line":320},[294,9608,9609],{},"        result.append(item)\n",[294,9611,9612],{"class":296,"line":326},[294,9613,317],{"emptyLinePlaceholder":30},[294,9615,9616],{"class":296,"line":332},[294,9617,9618],{},"# 模式 2：set 辅助去重\u002F查重\n",[294,9620,9621],{"class":296,"line":338},[294,9622,9623],{},"seen = set()\n",[294,9625,9626],{"class":296,"line":343},[294,9627,9594],{},[294,9629,9630],{"class":296,"line":349},[294,9631,9599],{},[294,9633,9634],{"class":296,"line":355},[294,9635,9636],{},"    key = item[\"id\"]\n",[294,9638,9639],{"class":296,"line":360},[294,9640,9641],{},"    if key not in seen:\n",[294,9643,9644],{"class":296,"line":366},[294,9645,9646],{},"        seen.add(key)\n",[294,9648,9649],{"class":296,"line":372},[294,9650,9609],{},[294,9652,9654],{"class":296,"line":9653},15,[294,9655,317],{"emptyLinePlaceholder":30},[294,9657,9659],{"class":296,"line":9658},16,[294,9660,9661],{},"# 模式 3：get 频率统计\n",[294,9663,9665],{"class":296,"line":9664},17,[294,9666,9667],{},"counts = {}\n",[294,9669,9671],{"class":296,"line":9670},18,[294,9672,9673],{},"for w in words:\n",[294,9675,9677],{"class":296,"line":9676},19,[294,9678,9679],{},"    counts[w] = counts.get(w, 0) + 1\n",[114,9681,9682,9685,9686,1032],{},[118,9683,9684],{},"待改进","：原理理解快但代码落地慢，明日规矩——",[118,9687,9688],{},"每个概念学完 10 分钟内跑第一行代码",[148,9690,2534],{"id":2534},[473,9692,9693,9701,9708,9715,9722,9729,9736,9743,9750,9757,9764],{},[476,9694,9695,9700],{},[123,9696,9699],{"href":9697,"rel":9698},"https:\u002F\u002Fdocs.python.org\u002F3\u002F",[2543],"Python 官方文档","：教程与标准库入口",[476,9702,9703],{},[123,9704,9707],{"href":9705,"rel":9706},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fcopy.html",[2543],"copy 模块（浅拷贝 \u002F 深拷贝）",[476,9709,9710],{},[123,9711,9714],{"href":9712,"rel":9713},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fdatastructures.html",[2543],"数据结构教程（list \u002F tuple \u002F dict \u002F set）",[476,9716,9717],{},[123,9718,9721],{"href":9719,"rel":9720},"https:\u002F\u002Fdocs.python.org\u002F3\u002Fglossary.html#term-hashable",[2543],"Glossary：hashable",[476,9723,9724],{},[123,9725,9728],{"href":9726,"rel":9727},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHash_table",[2543],"Hash table（哈希表）",[476,9730,9731],{},[123,9732,9735],{"href":9733,"rel":9734},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FBig_O_notation",[2543],"Big-O notation（大 O 记号）",[476,9737,9738],{},[123,9739,9742],{"href":9740,"rel":9741},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fclasses.html#python-scopes-and-namespaces",[2543],"Python 作用域与命名空间（LEGB）",[476,9744,9745],{},[123,9746,9749],{"href":9747,"rel":9748},"https:\u002F\u002Fdocs.python.org\u002F3\u002Fhowto\u002Fsorting.html",[2543],"Sorting HOW TO（sorted \u002F key \u002F lambda \u002F stable sort）",[476,9751,9752],{},[123,9753,9756],{"href":9754,"rel":9755},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fcollections.html#collections.Counter",[2543],"collections.Counter（频率统计）",[476,9758,9759],{},[123,9760,9763],{"href":9761,"rel":9762},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Ftimeit.html",[2543],"timeit（基准测试计时）",[476,9765,9766],{},[123,9767,9770],{"href":9768,"rel":9769},"https:\u002F\u002Fmatplotlib.org\u002F",[2543],"matplotlib（画图）",[2580,9772,7308],{},{"title":258,"searchDepth":303,"depth":303,"links":9774},[9775,9776,9783,9789],{"id":7944,"depth":303,"text":7945},{"id":8424,"depth":303,"text":8425,"children":9777},[9778,9779,9780,9781,9782],{"id":8447,"depth":34,"text":8448},{"id":8496,"depth":34,"text":8497},{"id":8619,"depth":34,"text":8620},{"id":8787,"depth":34,"text":8788},{"id":8992,"depth":34,"text":8993},{"id":9003,"depth":303,"text":9004,"children":9784},[9785,9786,9787,9788],{"id":9018,"depth":34,"text":9019},{"id":9231,"depth":34,"text":9232},{"id":9434,"depth":34,"text":9435},{"id":9579,"depth":34,"text":9579},{"id":2534,"depth":303,"text":2534},"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":7913,"description":9791},"week1-learning-outcomes","writing\u002F2026-09-09-week1-learning-outcomes",[2636,9799,9800,9801,9802],"data-structures","big-o","benchmark","learning-outcomes","week-1","hhMm0AOcKTiy50nLWeyG9P8yVR0FDbmcc-eWs5mq8vw",{"id":9806,"title":9807,"body":9808,"category":2624,"date":11070,"description":11071,"extension":2627,"hidden":2628,"kind":7361,"meta":11072,"navigation":30,"path":11073,"publishedAt":11074,"seo":11075,"series":101,"slug":11076,"stem":11077,"tags":11078,"unit":5123,"updated":5123,"__hash__":11083},"writing\u002Fwriting\u002F2026-08-31-one-year-data-ai-plan.md","学习计划：从  Python开始到认识Data\u002FAI",{"type":111,"value":9809,"toc":11049},[9810,9813,9833,9836,9839,10027,10030,10034,10037,10057,10060,10063,10067,10071,10076,10087,10092,10103,10108,10119,10124,10135,10139,10144,10155,10160,10171,10176,10187,10192,10203,10207,10212,10223,10228,10239,10244,10255,10260,10271,10275,10280,10291,10296,10307,10312,10323,10328,10339,10343,10348,10359,10364,10375,10380,10391,10396,10407,10411,10416,10427,10432,10443,10448,10459,10464,10475,10479,10484,10495,10500,10511,10516,10527,10532,10543,10547,10552,10563,10568,10579,10584,10595,10600,10611,10615,10620,10631,10636,10647,10652,10663,10668,10679,10683,10688,10699,10704,10715,10720,10731,10736,10747,10751,10756,10767,10772,10783,10788,10799,10804,10815,10819,10822,10825,10830,10841,10846,10854,10859,10867,10872,10880,10885,10893,10898,10906,10911,10919,10924,10929,10932,10935,10978,10981,10984,11021,11023],[114,9811,9812],{},"我的情况是工作之余学习，所以整个方案围绕三个词：",[473,9814,9815,9821,9827],{},[476,9816,9817,9820],{},[118,9818,9819],{},"宽数学","：理解、推导、实验、应用。",[476,9822,9823,9826],{},[118,9824,9825],{},"深工程","：真正能独立构建系统。",[476,9828,9829,9832],{},[118,9830,9831],{},"懂 AI","：理解核心原理，会用，会判断，不钻研究细节。",[114,9834,9835],{},"不照搬任何课程，只取与目标匹配的部分：数据库部分参考 CMU 15-445 的方向（Buffer Pool、B+Tree、MVCC、Query Planner），机器学习参考斯坦福 CS229 的主线，语言模型参考 CS336 的 from-scratch 思路，砍掉与目标不匹配的内容。",[148,9837,9838],{"id":9838},"全年结构",[166,9840,9841,9857],{},[169,9842,9843],{},[172,9844,9845,9848,9851,9854],{},[175,9846,9847],{},"阶段",[175,9849,9850],{},"周数",[175,9852,9853],{},"核心",[175,9855,9856],{},"阶段成果",[185,9858,9859,9873,9887,9901,9915,9929,9943,9957,9971,9985,9999,10013],{},[172,9860,9861,9864,9867,9870],{},[190,9862,9863],{},"I",[190,9865,9866],{},"1–4",[190,9868,9869],{},"Python 深化 + 编程思维",[190,9871,9872],{},"Python CLI 工具",[172,9874,9875,9878,9881,9884],{},[190,9876,9877],{},"II",[190,9879,9880],{},"5–8",[190,9882,9883],{},"SQL + PostgreSQL",[190,9885,9886],{},"SQL 数据分析系统",[172,9888,9889,9892,9895,9898],{},[190,9890,9891],{},"III",[190,9893,9894],{},"9–12",[190,9896,9897],{},"数据库原理 + 数据建模",[190,9899,9900],{},"完整业务数据库",[172,9902,9903,9906,9909,9912],{},[190,9904,9905],{},"IV",[190,9907,9908],{},"13–16",[190,9910,9911],{},"数据工程",[190,9913,9914],{},"ETL Pipeline",[172,9916,9917,9920,9923,9926],{},[190,9918,9919],{},"V",[190,9921,9922],{},"17–20",[190,9924,9925],{},"概率统计 + 数学实验",[190,9927,9928],{},"统计分析项目",[172,9930,9931,9934,9937,9940],{},[190,9932,9933],{},"VI",[190,9935,9936],{},"21–24",[190,9938,9939],{},"线代 + 微积分 + 优化",[190,9941,9942],{},"数学\u002FML 实验库",[172,9944,9945,9948,9951,9954],{},[190,9946,9947],{},"VII",[190,9949,9950],{},"25–28",[190,9952,9953],{},"Machine Learning",[190,9955,9956],{},"手写 ML 项目",[172,9958,9959,9962,9965,9968],{},[190,9960,9961],{},"VIII",[190,9963,9964],{},"29–32",[190,9966,9967],{},"数据工程深化 + 分布式",[190,9969,9970],{},"完整数据 Pipeline",[172,9972,9973,9976,9979,9982],{},[190,9974,9975],{},"IX",[190,9977,9978],{},"33–36",[190,9980,9981],{},"Transformer + LLM",[190,9983,9984],{},"Mini Transformer",[172,9986,9987,9990,9993,9996],{},[190,9988,9989],{},"X",[190,9991,9992],{},"37–40",[190,9994,9995],{},"RAG + AI 应用",[190,9997,9998],{},"RAG 系统",[172,10000,10001,10004,10007,10010],{},[190,10002,10003],{},"XI",[190,10005,10006],{},"41–44",[190,10008,10009],{},"Agent + AI System",[190,10011,10012],{},"Agent 系统",[172,10014,10015,10018,10021,10024],{},[190,10016,10017],{},"XII",[190,10019,10020],{},"45–52",[190,10022,10023],{},"综合项目",[190,10025,10026],{},"完整 Data\u002FAI 系统",[114,10028,10029],{},"一句话压缩：1–16 周建立真正的工程与数据基础；17–28 周用数学建立 ML 能力；29–32 周把数据工程推向完整系统；33–44 周理解并应用 LLM；45–52 周把全部能力合成一个真正的 Data\u002FAI 系统。",[148,10031,10033],{"id":10032},"执行标准每周三级任务","执行标准：每周三级任务",[114,10035,10036],{},"每一周都按同一个标准执行：",[473,10038,10039,10045,10051],{},[476,10040,10041,10044],{},[118,10042,10043],{},"Level 1 — Know","：知道是什么。能解释、能使用、能写简单代码、能完成标准题。",[476,10046,10047,10050],{},[118,10048,10049],{},"Level 2 — Build","：自己做出来。不看教程，组合多个知识点，解决真实问题，能 Debug、能测试。",[476,10052,10053,10056],{},[118,10054,10055],{},"Level 3 — Think","：自己发现问题并解决。没有标准答案，自己设计方案、做实验、比较方案、分析 trade-off、找性能瓶颈、解释失败原因。",[114,10058,10059],{},"真正决定一年后能力的是 Level 3。",[114,10061,10062],{},"计划按任务验收，不按时间。不写\"周三学两小时数据库索引\"，而写\"周三理解 B+Tree，给 100 万条数据建不同索引，用 EXPLAIN ANALYZE 比较查询计划，解释为什么联合索引在这个 Query 上有效\"。前者是时间管理，后者才是能力训练。",[148,10064,10066],{"id":10065},"_52-周明细","52 周明细",[161,10068,10070],{"id":10069},"阶段-i-pythonweek-14-python-cli-工具","阶段 I · Python（Week 1–4）→ Python CLI 工具",[114,10072,10073],{},[118,10074,10075],{},"Week 1 · 对象模型与数据结构",[473,10077,10078,10081,10084],{},[476,10079,10080],{},"L1：list\u002Ftuple\u002Fdict\u002Fset、可变性、引用、浅拷贝\u002F深拷贝、函数、作用域。",[476,10082,10083],{},"L2：实现 LRU Cache、数据去重、频率统计、多字段排序。",[476,10085,10086],{},"L3：设计内存 Key-Value Store（set\u002Fget\u002Fdelete\u002Fexists\u002Fkeys），分析不同数据结构的时间复杂度。验收：解释 dict 平均 O(1)，以及这个结论什么时候失效。",[114,10088,10089],{},[118,10090,10091],{},"Week 2 · 函数、抽象与模块化",[473,10093,10094,10097,10100],{},[476,10095,10096],{},"L1：函数、模块、包、异常、dataclass、type hint。",[476,10098,10099],{},"L2：把 Week 1 项目拆成 storage\u002Fcore\u002Fcli\u002Fconfig\u002Ftests，加类型检查、logging、配置。",[476,10101,10102],{},"L3：设计插件式架构：CSV\u002FJSON\u002FExcel Reader → 统一接口 → Data Processor，新增数据源不修改核心逻辑。重点是设计接口，不是写代码。",[114,10104,10105],{},[118,10106,10107],{},"Week 3 · 测试与调试",[473,10109,10110,10113,10116],{},[476,10111,10112],{},"L1：pytest、fixture、mock、异常测试。",[476,10114,10115],{},"L2：给之前项目补单元、边界、异常、回归测试。",[476,10117,10118],{},"L3：故意制造逻辑、性能、数据、并发四类 bug，只允许用日志 + debugger + test 定位，写 Bug→假设→实验→定位→修复 记录。",[114,10120,10121],{},[118,10122,10123],{},"Week 4 · 算法与性能",[473,10125,10126,10129,10132],{},[476,10127,10128],{},"L1：Big-O、Hash、Heap、二分、BFS\u002FDFS。",[476,10130,10131],{},"L2：实现 Top-K、二分、BFS、DFS、LRU 并 benchmark。",[476,10133,10134],{},"L3：百万级数据处理任务，比较 Python loop \u002F Pandas \u002F NumPy \u002F Generator \u002F Batch Processing，分析 CPU、内存、IO 和时间复杂度。",[161,10136,10138],{"id":10137},"阶段-ii-sql-postgresqlweek-58-sql-数据分析系统","阶段 II · SQL + PostgreSQL（Week 5–8）→ SQL 数据分析系统",[114,10140,10141],{},[118,10142,10143],{},"Week 5 · SQL 核心",[473,10145,10146,10149,10152],{},[476,10147,10148],{},"L1：SELECT、WHERE、GROUP BY、HAVING、JOIN、CASE、NULL。",[476,10150,10151],{},"L2：真实业务数据集（客户\u002F合同\u002F订单\u002F商品\u002F销售）完成 20 个分析问题。",[476,10153,10154],{},"L3：不用 Pandas，全部用 SQL 完成月度销售趋势、客户复购、Top-N、同比、环比、异常订单。",[114,10156,10157],{},[118,10158,10159],{},"Week 6 · 高级 SQL",[473,10161,10162,10165,10168],{},[476,10163,10164],{},"L1：CTE、窗口函数、子查询。",[476,10166,10167],{},"L2：Ranking、Running Total、Moving Average、Cohort、Retention。",[476,10169,10170],{},"L3：沿销售人员→客户→合同→订单→回款链路只用 SQL 分析：哪些客户增长最快、哪些正在流失、哪些合同异常。",[114,10172,10173],{},[118,10174,10175],{},"Week 7 · 数据建模",[473,10177,10178,10181,10184],{},[476,10179,10180],{},"L1：实体、关系、主键、外键、范式。",[476,10182,10183],{},"L2：设计钢贸业务数据库：Customer、Product、Contract、Order、Delivery、Payment、Price。",[476,10185,10186],{},"L3：故意制造数据冗余和更新\u002F删除异常，用 1NF→2NF→3NF 解决，再讨论实际系统为什么有时要反范式。",[114,10188,10189],{},[118,10190,10191],{},"Week 8 · PostgreSQL 与查询优化",[473,10193,10194,10197,10200],{},[476,10195,10196],{},"L1：Index、EXPLAIN、Transaction、View。",[476,10198,10199],{},"L2：造 100 万\u002F1000 万数据，比较无索引、单列索引、联合索引。",[476,10201,10202],{},"L3：制造一个慢查询，EXPLAIN ANALYZE → 找瓶颈 → 改索引\u002F查询 → benchmark。",[161,10204,10206],{"id":10205},"阶段-iii-数据库系统week-912-完整业务数据库","阶段 III · 数据库系统（Week 9–12）→ 完整业务数据库",[114,10208,10209],{},[118,10210,10211],{},"Week 9 · 存储内部结构",[473,10213,10214,10217,10220],{},[476,10215,10216],{},"L1：Page、Tuple、Heap、Buffer、磁盘与内存。",[476,10218,10219],{},"L2：用 Python 实现简单 Page Storage：insert\u002Fread\u002Fupdate\u002Fdelete。",[476,10221,10222],{},"L3：模拟内存有限时数据库如何管理 Page，实现一个简单 LRU Buffer Pool。对应 CMU 15-445 的 Buffer Pool Manager 方向。",[114,10224,10225],{},[118,10226,10227],{},"Week 10 · 索引",[473,10229,10230,10233,10236],{},[476,10231,10232],{},"L1：B-Tree、B+Tree、Hash Index。",[476,10234,10235],{},"L2：手写简化 B+Tree：insert、search、range query。",[476,10237,10238],{},"L3：比较 Linear Search、Hash、B+Tree，实验数据规模扩大 100 倍时性能如何变化。",[114,10240,10241],{},[118,10242,10243],{},"Week 11 · 事务与 MVCC",[473,10245,10246,10249,10252],{},[476,10247,10248],{},"L1：ACID、隔离级别、脏读、不可重复读、幻读。",[476,10250,10251],{},"L2：用 PostgreSQL 制造两个并发事务，观察不同 Isolation Level 的行为。",[476,10253,10254],{},"L3：实现简化 MVCC 模拟器：transaction ID、version、visibility、commit、rollback。",[114,10256,10257],{},[118,10258,10259],{},"Week 12 · 查询计划与恢复",[473,10261,10262,10265,10268],{},[476,10263,10264],{},"L1：Query Planner、Join 算法、Sort、Aggregate、WAL、Checkpoint。",[476,10266,10267],{},"L2：用 PostgreSQL 优化 5 个慢查询。",[476,10269,10270],{},"L3：设计简化 Query Planner：对 A JOIN B 比较 Nested Loop 和 Hash Join，解释数据库为什么选择这个执行计划。",[161,10272,10274],{"id":10273},"阶段-iv-数据工程week-1316-自动化-etl-pipeline","阶段 IV · 数据工程（Week 13–16）→ 自动化 ETL Pipeline",[114,10276,10277],{},[118,10278,10279],{},"Week 13 · ETL",[473,10281,10282,10285,10288],{},[476,10283,10284],{},"L1：Extract、Transform、Load、Schema、Validation。",[476,10286,10287],{},"L2：构建 CSV\u002FAPI\u002FExcel → Python → PostgreSQL。",[476,10289,10290],{},"L3：Pipeline 支持数据异常检测、类型错误、重复数据、缺失数据、错误日志。",[114,10292,10293],{},[118,10294,10295],{},"Week 14 · 增量管道",[473,10297,10298,10301,10304],{},[476,10299,10300],{},"L1：Full Load、Incremental Load、Upsert。",[476,10302,10303],{},"L2：每天只处理新增\u002F变化数据。",[476,10305,10306],{},"L3：保证幂等性——Pipeline 重复运行不产生重复数据。",[114,10308,10309],{},[118,10310,10311],{},"Week 15 · Airflow",[473,10313,10314,10317,10320],{},[476,10315,10316],{},"L1：DAG、Task、依赖、Scheduler、Retry。Airflow 的本质是用 DAG 表达任务及其依赖，由调度器按依赖触发 Task。",[476,10318,10319],{},"L2：把 Week 13–14 的管道改成 Airflow：Extract→Clean→Validate→Load→Quality Check。",[476,10321,10322],{},"L3：加 Retry、Timeout、Alert、Backfill、Failure Recovery。",[114,10324,10325],{},[118,10326,10327],{},"Week 16 · 数据质量",[473,10329,10330,10333,10336],{},[476,10331,10332],{},"L1：Completeness、Accuracy、Consistency、Uniqueness、Validity。",[476,10334,10335],{},"L2：建立 Data Quality Framework。",[476,10337,10338],{},"L3：设计数据异常自动检测系统，例如今天订单量突然下降 80% 自动报警。",[161,10340,10342],{"id":10341},"阶段-v-概率统计week-1720-统计分析项目","阶段 V · 概率统计（Week 17–20）→ 统计分析项目",[114,10344,10345],{},[118,10346,10347],{},"Week 17 · 概率基础",[473,10349,10350,10353,10356],{},[476,10351,10352],{},"L1：条件概率、独立性、Bayes。",[476,10354,10355],{},"L2：Python 模拟 Monty Hall、生日问题、Bayesian updating。",[476,10357,10358],{},"L3：自己设计模拟实验验证 Bayes 定理，对比理论概率与实验概率。",[114,10360,10361],{},[118,10362,10363],{},"Week 18 · 随机变量与分布",[473,10365,10366,10369,10372],{},[476,10367,10368],{},"L1：PMF、PDF、CDF、期望、方差。",[476,10370,10371],{},"L2：实现 Bernoulli、Binomial、Poisson、Gaussian。",[476,10373,10374],{},"L3：Monte Carlo 估计 π、积分、概率，分析样本数量与误差的关系。",[114,10376,10377],{},[118,10378,10379],{},"Week 19 · MLE 与贝叶斯",[473,10381,10382,10385,10388],{},[476,10383,10384],{},"L1：Likelihood、MLE、MAP、Prior、Posterior。",[476,10386,10387],{},"L2：推导 Gaussian MLE，用 Python 验证。",[476,10389,10390],{},"L3：设计 Bayesian A\u002FB Test，回答\"新方案真的比旧方案好吗\"。",[114,10392,10393],{},[118,10394,10395],{},"Week 20 · 统计推断",[473,10397,10398,10401,10404],{},[476,10399,10400],{},"L1：抽样、置信区间、假设检验、p-value。",[476,10402,10403],{},"L2：完成 A\u002FB Test 项目。",[476,10405,10406],{},"L3：加入 Bootstrap、Statistical Power、Multiple Testing。",[161,10408,10410],{"id":10409},"阶段-vi-数学week-2124-数学ml-实验库","阶段 VI · 数学（Week 21–24）→ 数学\u002FML 实验库",[114,10412,10413],{},[118,10414,10415],{},"Week 21 · 向量空间",[473,10417,10418,10421,10424],{},[476,10419,10420],{},"L1：向量、矩阵、线性变换、Rank、Basis。",[476,10422,10423],{},"L2：NumPy 实现 Projection、Least Squares。",[476,10425,10426],{},"L3：从几何角度解释 Linear Regression 为什么是一个 Projection 问题。",[114,10428,10429],{},[118,10430,10431],{},"Week 22 · 特征值 + SVD + PCA",[473,10433,10434,10437,10440],{},[476,10435,10436],{},"L1：特征值、特征向量、SVD。",[476,10438,10439],{},"L2：自己实现 PCA。",[476,10441,10442],{},"L3：对真实高维数据做 PCA 降维→可视化→信息损失分析。",[114,10444,10445],{},[118,10446,10447],{},"Week 23 · 梯度与反向传播",[473,10449,10450,10453,10456],{},[476,10451,10452],{},"L1：偏导、梯度、Jacobian、链式法则。",[476,10454,10455],{},"L2：手推两层神经网络的 Backpropagation。",[476,10457,10458],{},"L3：只用 NumPy 手写一个 MLP，不能使用自动求导。",[114,10460,10461],{},[118,10462,10463],{},"Week 24 · 优化",[473,10465,10466,10469,10472],{},[476,10467,10468],{},"L1：Gradient Descent、SGD、Momentum、Adam。",[476,10470,10471],{},"L2：比较不同优化器。",[476,10473,10474],{},"L3：研究 Learning Rate 对收敛的影响：在 Rosenbrock 等函数上比较 SGD、Momentum、Adam、Newton 的收敛速度、稳定性和最终误差。",[161,10476,10478],{"id":10477},"阶段-vii-machine-learningweek-2528-手写-ml-项目","阶段 VII · Machine Learning（Week 25–28）→ 手写 ML 项目",[114,10480,10481],{},[118,10482,10483],{},"Week 25 · Linear Regression",[473,10485,10486,10489,10492],{},[476,10487,10488],{},"L1：Least Squares、Normal Equation、Gradient Descent。",[476,10490,10491],{},"L2：NumPy 手写。",[476,10493,10494],{},"L3：比较 Normal Equation 与 Gradient Descent 在数据规模、维度、数值稳定性上的差异。",[114,10496,10497],{},[118,10498,10499],{},"Week 26 · Logistic Regression 与正则化",[473,10501,10502,10505,10508],{},[476,10503,10504],{},"L1：Sigmoid、Log Loss、MLE。",[476,10506,10507],{},"L2：手写 Logistic Regression。",[476,10509,10510],{},"L3：加入 L1\u002FL2，实验正则化强度与模型泛化的关系。",[114,10512,10513],{},[118,10514,10515],{},"Week 27 · 树与集成",[473,10517,10518,10521,10524],{},[476,10519,10520],{},"L1：Decision Tree、Entropy、Gini、Random Forest。",[476,10522,10523],{},"L2：实现简化 Decision Tree。",[476,10525,10526],{},"L3：比较 Logistic、Tree、Random Forest、XGBoost——重点不是准确率，而是为什么这个数据集上某模型更好。",[114,10528,10529],{},[118,10530,10531],{},"Week 28 · ML 完整项目",[473,10533,10534,10537,10540],{},[476,10535,10536],{},"L1：Data→Cleaning→Feature→Train→Validation→Test 走完一遍。",[476,10538,10539],{},"L2：加 Cross Validation、调参、Error Analysis。",[476,10541,10542],{},"L3：故意引入 Data Leakage \u002F Distribution Shift，让自己发现问题。",[161,10544,10546],{"id":10545},"阶段-viii-数据工程深化week-2932-batch-streaming-数据系统","阶段 VIII · 数据工程深化（Week 29–32）→ Batch + Streaming 数据系统",[114,10548,10549],{},[118,10550,10551],{},"Week 29 · 数据仓库",[473,10553,10554,10557,10560],{},[476,10555,10556],{},"L1：OLTP\u002FOLAP、Fact、Dimension、Star Schema。",[476,10558,10559],{},"L2：设计企业销售数仓。",[476,10561,10562],{},"L3：实现 ODS→DWD→DWS→ADS，解释每一层为什么存在。",[114,10564,10565],{},[118,10566,10567],{},"Week 30 · Parquet 与列式存储",[473,10569,10570,10573,10576],{},[476,10571,10572],{},"L1：Row vs Column、压缩、分区。",[476,10574,10575],{},"L2：比较 CSV 与 Parquet。",[476,10577,10578],{},"L3：构建 Partitioned Data Lake，测试数据规模 × 查询性能 × 存储空间。",[114,10580,10581],{},[118,10582,10583],{},"Week 31 · Spark",[473,10585,10586,10589,10592],{},[476,10587,10588],{},"L1：DataFrame、Transformation、Action、Lazy Evaluation。",[476,10590,10591],{},"L2：把 Pandas Pipeline 迁移到 Spark。",[476,10593,10594],{},"L3：制造数据倾斜 \u002F Shuffle，分析 Spark 为什么突然变慢。",[114,10596,10597],{},[118,10598,10599],{},"Week 32 · Kafka 与流处理",[473,10601,10602,10605,10608],{},[476,10603,10604],{},"L1：Producer、Consumer、Topic、Partition、Offset。",[476,10606,10607],{},"L2：实现 Producer→Kafka→Consumer→PostgreSQL。",[476,10609,10610],{},"L3：处理 Retry、Duplicate、Ordering、Consumer Failure，想清楚 At-least-once \u002F At-most-once \u002F Exactly-once 到底意味着什么。",[161,10612,10614],{"id":10613},"阶段-ix-transformerweek-3336-mini-transformer-mini-llm","阶段 IX · Transformer（Week 33–36）→ Mini Transformer \u002F Mini LLM",[114,10616,10617],{},[118,10618,10619],{},"Week 33 · Tokenization 与 Embedding",[473,10621,10622,10625,10628],{},[476,10623,10624],{},"L1：Token、Vocabulary、BPE、Embedding。",[476,10626,10627],{},"L2：实现一个简单 Tokenizer。",[476,10629,10630],{},"L3：分析不同 Tokenizer 对中文\u002F英文\u002F代码的影响。",[114,10632,10633],{},[118,10634,10635],{},"Week 34 · Attention",[473,10637,10638,10641,10644],{},[476,10639,10640],{},"L1：Q、K、V、Attention Score、Softmax。",[476,10642,10643],{},"L2：NumPy 手写 Self-Attention。",[476,10645,10646],{},"L3：可视化 Attention Matrix，分析不同 Token 之间为什么产生不同权重。",[114,10648,10649],{},[118,10650,10651],{},"Week 35 · Transformer",[473,10653,10654,10657,10660],{},[476,10655,10656],{},"L1：Multi-Head Attention、FFN、Residual、LayerNorm、Positional Encoding。",[476,10658,10659],{},"L2：PyTorch 实现 Mini Transformer。",[476,10661,10662],{},"L3：不用现成 Transformer 库，自己搭完整 forward。",[114,10664,10665],{},[118,10666,10667],{},"Week 36 · Mini 语言模型",[473,10669,10670,10673,10676],{},[476,10671,10672],{},"L1：Next Token Prediction、Cross Entropy、Training Loop。",[476,10674,10675],{},"L2：训练一个小型 Language Model。",[476,10677,10678],{},"L3：做 Context Length、Embedding Size、层数、Learning Rate 实验，观察模型容量与 Loss 的关系。",[161,10680,10682],{"id":10681},"阶段-x-ragweek-3740-可评价的-rag-系统","阶段 X · RAG（Week 37–40）→ 可评价的 RAG 系统",[114,10684,10685],{},[118,10686,10687],{},"Week 37 · Embedding 与向量检索",[473,10689,10690,10693,10696],{},[476,10691,10692],{},"L1：Embedding、Cosine Similarity、Vector DB。",[476,10694,10695],{},"L2：实现 Document→Chunk→Embedding→Vector Search。",[476,10697,10698],{},"L3：比较不同 Embedding 模型，建立自己的 Evaluation Dataset。",[114,10700,10701],{},[118,10702,10703],{},"Week 38 · 检索",[473,10705,10706,10709,10712],{},[476,10707,10708],{},"L1：Dense Retrieval、BM25、Hybrid Search。",[476,10710,10711],{},"L2：实现 BM25 + Vector。",[476,10713,10714],{},"L3：加入 Reranker，比较 Vector、BM25、Hybrid、Hybrid + Rerank。",[114,10716,10717],{},[118,10718,10719],{},"Week 39 · RAG 评估",[473,10721,10722,10725,10728],{},[476,10723,10724],{},"L1：Recall@K、Precision@K、MRR、NDCG。",[476,10726,10727],{},"L2：建立 100–300 条 Ground Truth Query。",[476,10729,10730],{},"L3：系统比较 Chunking \u002F Embedding \u002F Retrieval \u002F Reranking，回答到底哪个 RAG 方案最好、为什么。",[114,10732,10733],{},[118,10734,10735],{},"Week 40 · 生产级 RAG",[473,10737,10738,10741,10744],{},[476,10739,10740],{},"L1：Metadata、Filtering、Query Rewrite、Citation。",[476,10742,10743],{},"L2：构建完整链路：Document→Parser→Chunk→Embedding→Vector DB→Retriever→Reranker→LLM。",[476,10745,10746],{},"L3：故意制造无答案问题、相似问题、长文档、错误文档、冲突信息，测试幻觉率、Recall、Faithfulness。",[161,10748,10750],{"id":10749},"阶段-xi-agent-ai-系统week-4144-agent-系统","阶段 XI · Agent + AI 系统（Week 41–44）→ Agent 系统",[114,10752,10753],{},[118,10754,10755],{},"Week 41 · Tool Calling",[473,10757,10758,10761,10764],{},[476,10759,10760],{},"L1：Function Calling、Structured Output。",[476,10762,10763],{},"L2：让 LLM 调用 SQL、Calculator、Search、API。",[476,10765,10766],{},"L3：设计 LLM→判断→工具→结果→再判断的循环，并处理工具失败。",[114,10768,10769],{},[118,10770,10771],{},"Week 42 · Agent Workflow",[473,10773,10774,10777,10780],{},[476,10775,10776],{},"L1：State、Workflow、Agent Loop。",[476,10778,10779],{},"L2：实现 SQL Agent：用户问题→SQL→执行→解释。",[476,10781,10782],{},"L3：限制 Agent 不允许随意执行危险 SQL，设计 Permission、Validation、Human Approval。",[114,10784,10785],{},[118,10786,10787],{},"Week 43 · 评估与可观测性",[473,10789,10790,10793,10796],{},[476,10791,10792],{},"L1：Logging、Tracing、Latency、Cost。",[476,10794,10795],{},"L2：记录 Prompt→Model→Tool→Latency→Token→Cost→Answer。",[476,10797,10798],{},"L3：建立 Evaluation Dashboard：哪个环节最慢、哪个模型最贵、哪类问题最容易失败。",[114,10800,10801],{},[118,10802,10803],{},"Week 44 · AI 可靠性",[473,10805,10806,10809,10812],{},[476,10807,10808],{},"L1：Retry、Timeout、Fallback、Cache。",[476,10810,10811],{},"L2：加入 Model fallback、API failure、Tool failure 处理。",[476,10813,10814],{},"L3：攻击自己的系统：Prompt Injection、Data Leakage、Tool Abuse、Hallucination，然后修复。",[161,10816,10818],{"id":10817},"阶段-xii-综合项目week-4552-完整-dataai-系统","阶段 XII · 综合项目（Week 45–52）→ 完整 Data\u002FAI 系统",[114,10820,10821],{},"从这里开始不再学习新知识，把前 44 周的能力压成一个真正的系统：企业数据智能系统，围绕熟悉的业务，使用脱敏\u002F公开\u002F模拟数据。架构：",[114,10823,10824],{},"数据源 → ETL → PostgreSQL → Data Warehouse → ML\u002FAnalytics → Embedding → RAG → LLM → Agent → API",[114,10826,10827],{},[118,10828,10829],{},"Week 45 · 项目设计",[473,10831,10832,10835,10838],{},[476,10833,10834],{},"L1：系统设计图。",[476,10836,10837],{},"L2：Database Schema + Data Pipeline 设计。",[476,10839,10840],{},"L3：写 Architecture Decision Record，解释为什么这么设计。",[114,10842,10843],{},[118,10844,10845],{},"Week 46 · Data Layer",[473,10847,10848,10851],{},[476,10849,10850],{},"Raw Data→ETL→Validation→PostgreSQL→Warehouse。",[476,10852,10853],{},"必须支持 Incremental、Idempotency、Data Quality、Backfill。",[114,10855,10856],{},[118,10857,10858],{},"Week 47 · Analytics \u002F ML Layer",[473,10860,10861,10864],{},[476,10862,10863],{},"SQL Analytics、Feature Engineering、ML Model。",[476,10865,10866],{},"建立自动化 Model Evaluation。",[114,10868,10869],{},[118,10870,10871],{},"Week 48 · RAG Layer",[473,10873,10874,10877],{},[476,10875,10876],{},"Documents→Embedding→Vector DB→Hybrid Retrieval→Reranker→LLM。",[476,10878,10879],{},"建立 RAG Evaluation Dataset。",[114,10881,10882],{},[118,10883,10884],{},"Week 49 · Agent Layer",[473,10886,10887,10890],{},[476,10888,10889],{},"LLM→Intent→Tool（SQL \u002F Search \u002F Analytics \u002F Knowledge Base）。",[476,10891,10892],{},"实现 Permission + Validation + Human Approval。",[114,10894,10895],{},[118,10896,10897],{},"Week 50 · 工程化",[473,10899,10900,10903],{},[476,10901,10902],{},"FastAPI、Docker、Logging、Testing、Config、Monitoring，部署整个系统。",[476,10904,10905],{},"挑战：模拟数据库挂掉、API 挂掉、LLM API 失败、Pipeline 失败，系统必须能够恢复。",[114,10907,10908],{},[118,10909,10910],{},"Week 51 · 性能与评估",[473,10912,10913,10916],{},[476,10914,10915],{},"Data：Pipeline 速度、Query 速度、Data Quality；ML：Accuracy、Precision、Recall、Error Analysis；RAG：Recall@K、MRR、Faithfulness；AI：Latency、Token、Cost、Failure Rate。",[476,10917,10918],{},"找出至少 3 个系统瓶颈并优化。这是整个项目最重要的一周。",[114,10920,10921],{},[118,10922,10923],{},"Week 52 · 最终答辩",[473,10925,10926],{},[476,10927,10928],{},"不再大量写代码，做一次真正的 Technical Review，回答：Python 为什么这样设计；为什么这个 Index 有效；Pipeline 失败怎么办；为什么出现 Overfitting；Gradient Descent 为什么能工作；Attention 到底在做什么；召回结果为什么不好；这个任务为什么需要 Agent；数据量扩大 10 倍怎么办。",[148,10930,10931],{"id":10931},"一年结束时的产出",[114,10933,10934],{},"不是 52 个 Notebook，而是 7 个东西：",[770,10936,10937,10943,10949,10955,10961,10966,10972],{},[476,10938,10939,10942],{},[118,10940,10941],{},"Python 工程项目","：真正可用的 CLI\u002F自动化工具。",[476,10944,10945,10948],{},[118,10946,10947],{},"数据库项目","：PostgreSQL + 数据建模 + SQL 优化 + Transaction。",[476,10950,10951,10954],{},[118,10952,10953],{},"数据工程项目","：API\u002FFile → ETL → Quality → Airflow → PostgreSQL → Spark\u002FKafka。",[476,10956,10957,10960],{},[118,10958,10959],{},"ML 项目","：数学→NumPy→ML from Scratch→sklearn→真实数据集→Error Analysis。",[476,10962,10963,10965],{},[118,10964,9984],{},"：Tokenizer→Embedding→Attention→Transformer→Training→Inference。",[476,10967,10968,10971],{},[118,10969,10970],{},"RAG \u002F Agent 系统","：Knowledge→Retrieval→Reranking→LLM→Tools→Agent→Evaluation。",[476,10973,10974,10977],{},[118,10975,10976],{},"最终项目","：把以上全部串起来的完整 Data\u002FAI 系统。",[114,10979,10980],{},"最终得到的不是证书和 Demo，而是一个能解释每个设计决策的系统。",[148,10982,10983],{"id":10983},"贯穿全年的原则",[770,10985,10986,10991,10997,11003,11009,11015],{},[476,10987,10988],{},[118,10989,10990],{},"不追求学完，追求能力闭环。",[476,10992,10993,10996],{},[118,10994,10995],{},"每周必须有产出","：代码、实验、数据、Benchmark、图表、文档、系统、推导，但不能只有看视频、看书、做笔记。",[476,10998,10999,11002],{},[118,11000,11001],{},"每 4 周必须形成一个阶段成果","，不能 52 周一直\"学习\"。",[476,11004,11005,11008],{},[118,11006,11007],{},"数学必须落到代码","：SVD→PCA→NumPy→数据降维，而不是看完线代教材。",[476,11010,11011,11014],{},[118,11012,11013],{},"工程必须落到系统","：ETL→Airflow→Retry→Backfill→Data Quality，而不是\"我学过 Airflow\"。",[476,11016,11017,11020],{},[118,11018,11019],{},"AI 必须落到理解和使用","：Attention→Mini Transformer→RAG→Agent，而不是\"我会调用 GPT API\"。",[148,11022,2534],{"id":2534},[473,11024,11025,11033,11041],{},[476,11026,11027,11032],{},[123,11028,11031],{"href":11029,"rel":11030},"https:\u002F\u002F15445.courses.cs.cmu.edu\u002F",[2543],"CMU 15-445 Intro to Database Systems"," — 阶段 III 的 Buffer Pool、B+Tree、MVCC、Query Planner 方向",[476,11034,11035,11040],{},[123,11036,11039],{"href":11037,"rel":11038},"https:\u002F\u002Fcs229.stanford.edu\u002F",[2543],"Stanford CS229 Machine Learning"," — 阶段 V–VII 的数学与 ML 主线",[476,11042,11043,11048],{},[123,11044,11047],{"href":11045,"rel":11046},"https:\u002F\u002Fstanford-cs336.github.io\u002F",[2543],"Stanford CS336 Language Modeling from Scratch"," — 阶段 IX 的 from-scratch 语言模型思路",{"title":258,"searchDepth":303,"depth":303,"links":11050},[11051,11052,11053,11067,11068,11069],{"id":9838,"depth":303,"text":9838},{"id":10032,"depth":303,"text":10033},{"id":10065,"depth":303,"text":10066,"children":11054},[11055,11056,11057,11058,11059,11060,11061,11062,11063,11064,11065,11066],{"id":10069,"depth":34,"text":10070},{"id":10137,"depth":34,"text":10138},{"id":10205,"depth":34,"text":10206},{"id":10273,"depth":34,"text":10274},{"id":10341,"depth":34,"text":10342},{"id":10409,"depth":34,"text":10410},{"id":10477,"depth":34,"text":10478},{"id":10545,"depth":34,"text":10546},{"id":10613,"depth":34,"text":10614},{"id":10681,"depth":34,"text":10682},{"id":10749,"depth":34,"text":10750},{"id":10817,"depth":34,"text":10818},{"id":10931,"depth":303,"text":10931},{"id":10983,"depth":303,"text":10983},{"id":2534,"depth":303,"text":2534},"2026-08-31","以 12 个阶段、每周 Know\u002FBuild\u002FThink 三级任务和阶段成果组织的 52 周学习计划：从 Python、SQL、数据工程到机器学习、Transformer、RAG 与 Agent，最终交付一个完整 Data\u002FAI 系统。",{},"\u002Fwriting\u002F2026-08-31-one-year-data-ai-plan","2026-08-31T15:02:31+08:00",{"title":9807,"description":11071},"one-year-data-ai-plan","writing\u002F2026-08-31-one-year-data-ai-plan",[126,2636,11079,9911,11080,11081,11082],"SQL","机器学习","RAG","Agent","NTJe96oUio_a3SpJEBMVsrXCbDyAZXn49eORvwwppzA",{"id":11085,"title":11086,"body":11087,"category":2624,"date":12533,"description":12534,"extension":2627,"hidden":2628,"kind":7361,"meta":12535,"navigation":30,"path":7924,"publishedAt":12536,"seo":12537,"series":101,"slug":12538,"stem":12539,"tags":12540,"unit":9803,"updated":9790,"__hash__":12543},"writing\u002Fwriting\u002F2026-08-25-week1-python-object-model-data-structures.md","Week 1 学习计划｜Python 对象模型与数据结构",{"type":111,"value":11088,"toc":12489},[11089,11092,11106,11117,11151,11155,11157,11159,11161,11177,11179,11208,11210,11216,11254,11256,11281,11293,11295,11297,11318,11321,11328,11332,11350,11356,11373,11377,11380,11391,11394,11398,11404,11407,11414,11416,11418,11463,11467,11469,11492,11495,11498,11524,11527,11556,11562,11566,11573,11611,11616,11619,11626,11630,11633,11662,11664,11670,11673,11677,11682,11684,11704,11711,11715,11718,11746,11749,11766,11770,11773,11777,11796,11800,11812,11816,11819,11823,11836,11845,11848,11852,11855,11859,11862,11866,11883,11886,11900,11904,11914,11974,11977,11981,11983,12003,12005,12040,12043,12047,12050,12054,12057,12077,12080,12084,12087,12091,12103,12107,12113,12117,12140,12145,12162,12167,12178,12183,12199,12203,12206,12209,12273,12279,12282,12285,12288,12378,12381,12401,12407,12410,12412,12487],[114,11090,11091],{},"这份计划是我和 AI 讨论后定下的第一周安排。",[114,11093,11094,11097,11098,11101,11102,11105],{},[118,11095,11096],{},"总目标","：从“会用 Python”提升到“理解 Python + 能独立设计数据结构”。\n",[118,11099,11100],{},"总投入","：约 14–16 小时。\n",[118,11103,11104],{},"原则","：先自己做，卡住再查资料；核心代码不让 AI 直接生成。",[114,11107,11108,11110,11111,11113,11114,1032],{},[118,11109,7361],{},"与",[118,11112,132],{},"已拆成两篇：本篇只保留当时的任务与要求，实际产出和实测数据见",[123,11115,11116],{"href":9793},"学习成果篇",[114,11118,11119,133,11121,138,11124,138,11127,138,11130,138,11133,138,11136,138,11139,138,11142,11146,11148,11149],{},[118,11120,7361],{},[123,11122,5181],{"href":11123},"#day-1%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%E5%8F%AF%E5%8F%98%E6%80%A7",[123,11125,5192],{"href":11126},"#day-2list-dict-set-big-o",[123,11128,5202],{"href":11129},"#day-3function-scope-copy",[123,11131,5211],{"href":11132},"#day-4%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%AE%9E%E6%88%98",[123,11134,5221],{"href":11135},"#day-5lru-cache",[123,11137,5232],{"href":11138},"#day-6level-3mini-key-value-store",[123,11140,5242],{"href":11141},"#day-7benchmark-%E6%9C%80%E7%BB%88%E9%AA%8C%E6%94%B6",[123,11143,11145],{"href":11144},"#%E7%AC%AC%E4%B8%80%E5%91%A8%E6%9C%80%E7%BB%88%E4%BA%A7%E5%87%BA","最终产出",[118,11147,132],{},"：见",[123,11150,11116],{"href":9793},[148,11152,11154],{"id":11153},"day-1对象引用可变性","Day 1｜对象、引用、可变性",[114,11156,5270],{},[161,11158,5274],{"id":5273},[114,11160,5277],{},[473,11162,11163,11171],{},[476,11164,11165,11166,2276],{},"Python Tutorial：Objects \u002F Names \u002F Assignment（",[123,11167,11170],{"href":11168,"rel":11169},"https:\u002F\u002Fdocs.python.org\u002F3\u002Freference\u002Fexecutionmodel.html#naming-and-binding",[2543],"Naming and binding，名字与绑定",[476,11172,11173],{},[123,11174,11176],{"href":9705,"rel":11175},[2543],"copy 官方文档（浅拷贝 \u002F 深拷贝）",[114,11178,5299],{},[473,11180,11181,11183,11186,11188,11191,11196,11202,11205],{},[476,11182,8468],{},[476,11184,11185],{},"variable \u002F name",[476,11187,8481],{},[476,11189,11190],{},"assignment",[476,11192,11193],{},[60,11194,11195],{},"id()",[476,11197,11198,566,11200],{},[60,11199,8703],{},[60,11201,8691],{},[476,11203,11204],{},"mutable \u002F immutable",[476,11206,11207],{},"aliasing",[161,11209,5352],{"id":5351},[114,11211,11212,11213,5359],{},"自己创建 ",[60,11214,11215],{},"object_demo.py",[473,11217,11218,11223,11231,11234,11237,11240,11243,11248,11251],{},[476,11219,11220],{},[60,11221,11222],{},"a = b",[476,11224,11225,2058,11228],{},[60,11226,11227],{},"a == b",[60,11229,11230],{},"a is b",[476,11232,11233],{},"list alias",[476,11235,11236],{},"string immutable",[476,11238,11239],{},"tuple immutable",[476,11241,11242],{},"list mutable",[476,11244,11245,11247],{},[60,11246,11195],{}," 变化实验",[476,11249,11250],{},"shallow copy",[476,11252,11253],{},"deep copy",[114,11255,915],{},[250,11257,11259],{"className":288,"code":11258,"language":290,"meta":258,"style":258},"a = [1, 2, [3, 4]]\nb = a\nc = a.copy()\nc[2].append(5)\n",[60,11260,11261,11266,11271,11276],{"__ignoreMap":258},[294,11262,11263],{"class":296,"line":297},[294,11264,11265],{},"a = [1, 2, [3, 4]]\n",[294,11267,11268],{"class":296,"line":303},[294,11269,11270],{},"b = a\n",[294,11272,11273],{"class":296,"line":34},[294,11274,11275],{},"c = a.copy()\n",[294,11277,11278],{"class":296,"line":314},[294,11279,11280],{},"c[2].append(5)\n",[114,11282,11283,11284,1332,11286,1332,11289,11292],{},"写出 ",[60,11285,123],{},[60,11287,11288],{},"b",[60,11290,11291],{},"c"," 并解释原因。",[161,11294,5536],{"id":5535},[114,11296,5539],{},[473,11298,11299,11305,11312,11315],{},[476,11300,11301,11304],{},[60,11302,11303],{},"="," 到底做了什么？",[476,11306,11307,1098,11309,11311],{},[60,11308,8703],{},[60,11310,8691],{}," 区别？",[476,11313,11314],{},"mutable \u002F immutable？",[476,11316,11317],{},"shallow \u002F deep copy？",[148,11319,7953],{"id":11320},"day-2list-dict-set-big-o",[114,11322,11323,11324],{},"时间：1.5–2h ｜ ✅ 已完成（delete 与 1,000,000 未测）｜ 成果：",[123,11325,11327],{"href":11326},"\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","复杂度实测与对照表",[161,11329,11331],{"id":11330},"学习-40min","学习 40min",[114,11333,5299,11334,1332,11338,1332,11342,1332,11346,1032],{},[123,11335,11337],{"href":9712,"rel":11336},[2543],"list、tuple、dict、set",[123,11339,11341],{"href":9719,"rel":11340},[2543],"hash",[123,11343,11345],{"href":9726,"rel":11344},[2543],"hash table",[123,11347,11349],{"href":9733,"rel":11348},[2543],"Big-O",[114,11351,11352,11353,133],{},"带着这些问题去学，答案和实测数据见",[123,11354,11355],{"href":11326},"成果篇",[473,11357,11358,11361,11364,11367,11370],{},[476,11359,11360],{},"list 为什么随机访问 O(1)？",[476,11362,11363],{},"list 为什么头部插入 O(n)？",[476,11365,11366],{},"dict 为什么平均查找 O(1)？",[476,11368,11369],{},"set 为什么 membership 平均 O(1)？",[476,11371,11372],{},"为什么“平均 O(1)”不等于“永远 O(1)”？",[161,11374,11376],{"id":11375},"实验-50min","实验 50min",[114,11378,11379],{},"自己测试：",[473,11381,11382,11385,11388],{},[476,11383,11384],{},"list：index、append、insert(0)、search",[476,11386,11387],{},"dict：get、set、membership",[476,11389,11390],{},"set：add、membership",[114,11392,11393],{},"数据规模：1,000 \u002F 10,000 \u002F 100,000 \u002F 1,000,000（1,000,000 视时间补测）。",[161,11395,11397],{"id":11396},"输出-30min","输出 30min",[114,11399,11400,11401,11403],{},"写 ",[60,11402,8228],{}," 复杂度对照表：list \u002F dict \u002F set 在访问、查找、插入、删除、membership 上的复杂度，实测过的标 ✅，没测的如实标注。",[148,11405,8433],{"id":11406},"day-3function-scope-copy",[114,11408,11409,11410],{},"时间：1.5–2h ｜ ✅ 已完成 ｜ 成果：",[123,11411,11413],{"href":11412},"\u002Fwriting\u002F2026-09-09-week1-learning-outcomes#day-3-%E6%88%90%E6%9E%9Cobject_modelmd","object_model.md 全文",[161,11415,5274],{"id":5570},[114,11417,5299],{},[473,11419,11420,11423,11426,11428,11435,11446,11449,11455,11457,11459,11461],{},[476,11421,11422],{},"function object",[476,11424,11425],{},"parameter \u002F argument",[476,11427,1250],{},[476,11429,11430],{},[123,11431,11434],{"href":11432,"rel":11433},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fcontrolflow.html#default-argument-values",[2543],"default argument（默认参数）",[476,11436,11437],{},[123,11438,11441,2058,11443,11445],{"href":11439,"rel":11440},"https:\u002F\u002Fdocs.python.org\u002F3\u002Ftutorial\u002Fcontrolflow.html#arbitrary-argument-lists",[2543],[60,11442,5309],{},[60,11444,5312],{},"（任意参数列表）",[476,11447,11448],{},"namespace",[476,11450,11451],{},[123,11452,11454],{"href":9740,"rel":11453},[2543],"LEGB（作用域与命名空间）",[476,11456,5323],{},[476,11458,5326],{},[476,11460,11250],{},[476,11462,11253],{},[161,11464,11466],{"id":11465},"编程实验-45min","编程实验 45min",[114,11468,5655],{},[250,11470,11472],{"className":288,"code":11471,"language":290,"meta":258,"style":258},"def add(a, b):\n    ...\n\nf = add\n",[60,11473,11474,11479,11483,11487],{"__ignoreMap":258},[294,11475,11476],{"class":296,"line":297},[294,11477,11478],{},"def add(a, b):\n",[294,11480,11481],{"class":296,"line":303},[294,11482,1390],{},[294,11484,11485],{"class":296,"line":34},[294,11486,317],{"emptyLinePlaceholder":30},[294,11488,11489],{"class":296,"line":314},[294,11490,11491],{},"f = add\n",[114,11493,11494],{},"验证：函数是不是对象？",[114,11496,11497],{},"然后完成：",[250,11499,11501],{"className":288,"code":11500,"language":290,"meta":258,"style":258},"x = 10\n\ndef test():\n    x = 20\n    print(x)\n",[60,11502,11503,11507,11511,11515,11519],{"__ignoreMap":258},[294,11504,11505],{"class":296,"line":297},[294,11506,8890],{},[294,11508,11509],{"class":296,"line":303},[294,11510,317],{"emptyLinePlaceholder":30},[294,11512,11513],{"class":296,"line":34},[294,11514,6002],{},[294,11516,11517],{"class":296,"line":314},[294,11518,8972],{},[294,11520,11521],{"class":296,"line":320},[294,11522,11523],{},"    print(x)\n",[114,11525,11526],{},"以及：",[250,11528,11530],{"className":288,"code":11529,"language":290,"meta":258,"style":258},"funcs = []\nfor i in range(3):\n    funcs.append(lambda: i)\n\nprint([f() for f in funcs])\n",[60,11531,11532,11537,11542,11547,11551],{"__ignoreMap":258},[294,11533,11534],{"class":296,"line":297},[294,11535,11536],{},"funcs = []\n",[294,11538,11539],{"class":296,"line":303},[294,11540,11541],{},"for i in range(3):\n",[294,11543,11544],{"class":296,"line":34},[294,11545,11546],{},"    funcs.append(lambda: i)\n",[294,11548,11549],{"class":296,"line":314},[294,11550,317],{"emptyLinePlaceholder":30},[294,11552,11553],{"class":296,"line":320},[294,11554,11555],{},"print([f() for f in funcs])\n",[114,11557,11558,11559,1032],{},"解释为什么结果不是 ",[60,11560,11561],{},"[0, 1, 2]",[161,11563,11565],{"id":11564},"验收-20min","验收 20min",[114,11567,11568,11569,11572],{},"写一页 ",[60,11570,11571],{},"object_model.md","，必须解释：",[250,11574,11576],{"className":9555,"code":11575,"language":9557,"meta":258,"style":258},"name\n↓\nobject\n↓\nreference\n↓\nscope\n",[60,11577,11578,11583,11588,11593,11597,11602,11606],{"__ignoreMap":258},[294,11579,11580],{"class":296,"line":297},[294,11581,11582],{},"name\n",[294,11584,11585],{"class":296,"line":303},[294,11586,11587],{},"↓\n",[294,11589,11590],{"class":296,"line":34},[294,11591,11592],{},"object\n",[294,11594,11595],{"class":296,"line":314},[294,11596,11587],{},[294,11598,11599],{"class":296,"line":320},[294,11600,11601],{},"reference\n",[294,11603,11604],{"class":296,"line":326},[294,11605,11587],{},[294,11607,11608],{"class":296,"line":332},[294,11609,11610],{},"scope\n",[114,11612,11613,11614,1032],{},"成果全文见",[123,11615,11355],{"href":11412},[148,11617,9012],{"id":11618},"day-4数据结构实战",[114,11620,11621,11622],{},"时间：2h。今天不看教程，直接写。✅ 已完成（2026-09-04）｜ 成果：",[123,11623,11625],{"href":11624},"\u002Fwriting\u002F2026-09-09-week1-learning-outcomes#day-4-%E6%88%90%E6%9E%9C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%AE%9E%E6%88%98","解法与总结",[161,11627,11629],{"id":11628},"task-1数据去重","Task 1：数据去重",[114,11631,11632],{},"输入：",[250,11634,11636],{"className":288,"code":11635,"language":290,"meta":258,"style":258},"users = [\n    {\"id\": 1, \"name\": \"A\"},\n    {\"id\": 2, \"name\": \"B\"},\n    {\"id\": 1, \"name\": \"A\"},\n]\n",[60,11637,11638,11643,11648,11653,11657],{"__ignoreMap":258},[294,11639,11640],{"class":296,"line":297},[294,11641,11642],{},"users = [\n",[294,11644,11645],{"class":296,"line":303},[294,11646,11647],{},"    {\"id\": 1, \"name\": \"A\"},\n",[294,11649,11650],{"class":296,"line":34},[294,11651,11652],{},"    {\"id\": 2, \"name\": \"B\"},\n",[294,11654,11655],{"class":296,"line":314},[294,11656,11647],{},[294,11658,11659],{"class":296,"line":320},[294,11660,11661],{},"]\n",[114,11663,5448],{},[473,11665,11666,11668],{},[476,11667,9024],{},[476,11669,9064],{},[114,11671,11672],{},"要求：比较两个版本复杂度。",[161,11674,11676],{"id":11675},"task-2频率统计","Task 2：频率统计",[114,11678,11632,11679],{},[60,11680,11681],{},"apple banana apple orange banana apple",[114,11683,990],{},[250,11685,11687],{"className":9555,"code":11686,"language":9557,"meta":258,"style":258},"apple: 3\nbanana: 2\norange: 1\n",[60,11688,11689,11694,11699],{"__ignoreMap":258},[294,11690,11691],{"class":296,"line":297},[294,11692,11693],{},"apple: 3\n",[294,11695,11696],{"class":296,"line":303},[294,11697,11698],{},"banana: 2\n",[294,11700,11701],{"class":296,"line":34},[294,11702,11703],{},"orange: 1\n",[114,11705,11706,11707,11710],{},"要求：不使用 ",[123,11708,9320],{"href":9754,"rel":11709},[2543],"，再使用 Counter，比较两种方案。",[161,11712,11714],{"id":11713},"task-3多字段排序","Task 3：多字段排序",[114,11716,11717],{},"数据：",[250,11719,11721],{"className":288,"code":11720,"language":290,"meta":258,"style":258},"users = [\n    {\"name\": \"A\", \"age\": 20, \"score\": 90},\n    {\"name\": \"B\", \"age\": 20, \"score\": 95},\n    {\"name\": \"C\", \"age\": 19, \"score\": 95},\n]\n",[60,11722,11723,11727,11732,11737,11742],{"__ignoreMap":258},[294,11724,11725],{"class":296,"line":297},[294,11726,11642],{},[294,11728,11729],{"class":296,"line":303},[294,11730,11731],{},"    {\"name\": \"A\", \"age\": 20, \"score\": 90},\n",[294,11733,11734],{"class":296,"line":34},[294,11735,11736],{},"    {\"name\": \"B\", \"age\": 20, \"score\": 95},\n",[294,11738,11739],{"class":296,"line":314},[294,11740,11741],{},"    {\"name\": \"C\", \"age\": 19, \"score\": 95},\n",[294,11743,11744],{"class":296,"line":320},[294,11745,11661],{},[114,11747,11748],{},"要求：score 降序 → age 升序 → name 升序。",[114,11750,11751,11752,2545,11757,1032],{},"必须自己理解：",[123,11753,11756],{"href":11754,"rel":11755},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Ffunctions.html#sorted",[2543],"sorted",[123,11758,11760,11761,1332,11763,11765],{"href":9747,"rel":11759},[2543],"Sorting HOW TO（",[60,11762,1511],{},[60,11764,5335],{},"、stable sort）",[148,11767,11769],{"id":11768},"day-5lru-cache","Day 5｜LRU Cache",[114,11771,11772],{},"时间：2–2.5h",[161,11774,11776],{"id":11775},"理解-lru-20min","理解 LRU 20min",[114,11778,11779,11780,11783,11784,11789,11790,11795],{},"容量：3。访问序列：",[60,11781,11782],{},"A B C A D","。自己推演最终缓存（参考 ",[123,11785,11788],{"href":11786,"rel":11787},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCache_replacement_policies#Least-recently_used_(LRU)",[2543],"LRU 缓存淘汰策略","，以及标准库的 ",[123,11791,11794],{"href":11792,"rel":11793},"https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Ffunctools.html#functools.lru_cache",[2543],"functools.lru_cache","）。",[161,11797,11799],{"id":11798},"第一版-40min","第一版 40min",[114,11801,6588,11802,6387,11805,1332,11808,11811],{},[60,11803,11804],{},"dict + list",[60,11806,11807],{},"get(key)",[60,11809,11810],{},"put(key, value)","，允许 O(n)。",[161,11813,11815],{"id":11814},"分析-20min","分析 20min",[114,11817,11818],{},"回答：为什么这个实现不能做到 O(1)？",[161,11820,11822],{"id":11821},"第二版-60min","第二版 60min",[114,11824,11825,11826,11830,11831,1032],{},"设计：",[123,11827,11829],{"href":9726,"rel":11828},[2543],"HashMap"," + ",[123,11832,11835],{"href":11833,"rel":11834},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDoubly_linked_list",[2543],"Doubly Linked List",[114,11837,5448,11838,1332,11841,1332,11843,1032],{},[60,11839,11840],{},"LRUCache(capacity)",[60,11842,11807],{},[60,11844,11810],{},[114,11846,11847],{},"目标：get \u002F put 平均 O(1)。",[161,11849,11851],{"id":11850},"测试-20min","测试 20min",[114,11853,11854],{},"测试：正常读取、重复读取、覆盖、淘汰、capacity = 1、capacity = 0、不存在 key。",[148,11856,11858],{"id":11857},"day-6level-3mini-key-value-store","Day 6｜Level 3：Mini Key-Value Store",[114,11860,11861],{},"时间：3–4h。这是本周核心任务。",[161,11863,11865],{"id":11864},"设计-30min","设计 30min",[114,11867,11868,11869,1332,11872,1332,11874,1332,11877,1332,11880,1032],{},"自己写需求：",[60,11870,11871],{},"set(key, value)",[60,11873,11807],{},[60,11875,11876],{},"delete(key)",[60,11878,11879],{},"exists(key)",[60,11881,11882],{},"keys()",[114,11884,11885],{},"确定：",[473,11887,11888,11891,11894,11897],{},[476,11889,11890],{},"key 不存在怎么办？",[476,11892,11893],{},"delete 不存在怎么办？",[476,11895,11896],{},"set 已存在 key 怎么办？",[476,11898,11899],{},"value 可以是什么类型？",[161,11901,11903],{"id":11902},"设计数据结构-30min","设计数据结构 30min",[114,11905,11906,11907,11910,11911,11913],{},"比较方案 A ",[60,11908,11909],{},"list[(key, value)]"," 与方案 B ",[60,11912,8244],{},"，写出：",[166,11915,11916,11928],{},[169,11917,11918],{},[172,11919,11920,11922,11925],{},[175,11921,7992],{},[175,11923,11924],{},"List",[175,11926,11927],{},"Dict",[185,11929,11930,11938,11947,11956,11965],{},[172,11931,11932,11934,11936],{},[190,11933,8247],{},[190,11935],{},[190,11937],{},[172,11939,11940,11943,11945],{},[190,11941,11942],{},"get",[190,11944],{},[190,11946],{},[172,11948,11949,11952,11954],{},[190,11950,11951],{},"delete",[190,11953],{},[190,11955],{},[172,11957,11958,11961,11963],{},[190,11959,11960],{},"exists",[190,11962],{},[190,11964],{},[172,11966,11967,11970,11972],{},[190,11968,11969],{},"keys",[190,11971],{},[190,11973],{},[114,11975,11976],{},"最终解释为什么选择 dict。",[161,11978,11980],{"id":11979},"独立实现-90min","独立实现 90min",[114,11982,6341],{},[250,11984,11986],{"className":9555,"code":11985,"language":9557,"meta":258,"style":258},"kv_store\u002F\n├── store.py\n└── test_store.py\n",[60,11987,11988,11993,11998],{"__ignoreMap":258},[294,11989,11990],{"class":296,"line":297},[294,11991,11992],{},"kv_store\u002F\n",[294,11994,11995],{"class":296,"line":303},[294,11996,11997],{},"├── store.py\n",[294,11999,12000],{"class":296,"line":34},[294,12001,12002],{},"└── test_store.py\n",[114,12004,5448],{},[250,12006,12008],{"className":288,"code":12007,"language":290,"meta":258,"style":258},"class KeyValueStore:\n    def set(...)\n    def get(...)\n    def delete(...)\n    def exists(...)\n    def keys(...)\n",[60,12009,12010,12015,12020,12025,12030,12035],{"__ignoreMap":258},[294,12011,12012],{"class":296,"line":297},[294,12013,12014],{},"class KeyValueStore:\n",[294,12016,12017],{"class":296,"line":303},[294,12018,12019],{},"    def set(...)\n",[294,12021,12022],{"class":296,"line":34},[294,12023,12024],{},"    def get(...)\n",[294,12026,12027],{"class":296,"line":314},[294,12028,12029],{},"    def delete(...)\n",[294,12031,12032],{"class":296,"line":320},[294,12033,12034],{},"    def exists(...)\n",[294,12036,12037],{"class":296,"line":326},[294,12038,12039],{},"    def keys(...)\n",[114,12041,12042],{},"这一部分禁止 AI 直接生成。",[161,12044,12046],{"id":12045},"测试-30min","测试 30min",[114,12048,12049],{},"必须测试：空 Store、set\u002Fget、覆盖、delete、不存在 key、多个 key、重复 delete、None、list \u002F dict 作为 value。",[161,12051,12053],{"id":12052},"思考-30min","思考 30min",[114,12055,12056],{},"如果 value 是 list，用户修改返回值，会不会影响 Store 内部数据？例如：",[250,12058,12060],{"className":288,"code":12059,"language":290,"meta":258,"style":258},"store.set(\"x\", [1, 2, 3])\ndata = store.get(\"x\")\ndata.append(4)\n",[60,12061,12062,12067,12072],{"__ignoreMap":258},[294,12063,12064],{"class":296,"line":297},[294,12065,12066],{},"store.set(\"x\", [1, 2, 3])\n",[294,12068,12069],{"class":296,"line":303},[294,12070,12071],{},"data = store.get(\"x\")\n",[294,12073,12074],{"class":296,"line":34},[294,12075,12076],{},"data.append(4)\n",[114,12078,12079],{},"判断是否应该允许这种行为。不用马上解决，写下你的设计选择。",[148,12081,12083],{"id":12082},"day-7benchmark-最终验收","Day 7｜Benchmark + 最终验收",[114,12085,12086],{},"时间：3h",[161,12088,12090],{"id":12089},"part-1benchmark-60min","Part 1：Benchmark 60min",[114,12092,12093,12094,12098,12099,11795],{},"比较 List KV Store vs Dict KV Store，数据量 1,000 \u002F 10,000 \u002F 100,000 \u002F 1,000,000，测试 get、set、delete、exists，记录数据量与运行时间（计时用 ",[123,12095,12097],{"href":9761,"rel":12096},[2543],"timeit","），画一张图（如 ",[123,12100,12102],{"href":9768,"rel":12101},[2543],"matplotlib",[161,12104,12106],{"id":12105},"part-2最终技术总结-45min","Part 2：最终技术总结 45min",[114,12108,11400,12109,12112],{},[60,12110,12111],{},"week01_review.md","，回答：",[114,12114,12115],{},[118,12116,2636],{},[473,12118,12119,12122,12127,12133,12135,12137],{},[476,12120,12121],{},"变量是什么？",[476,12123,12124,12126],{},[60,12125,11303],{}," 是复制吗？",[476,12128,12129,2058,12131,6456],{},[60,12130,8703],{},[60,12132,8691],{},[476,12134,11314],{},[476,12136,11317],{},[476,12138,12139],{},"LEGB？",[114,12141,12142],{},[118,12143,12144],{},"数据结构",[473,12146,12147,12150,12153,12156,12159],{},[476,12148,12149],{},"list 随机访问为什么 O(1)？",[476,12151,12152],{},"list 头部插入为什么 O(n)？",[476,12154,12155],{},"dict 为什么平均 O(1)？",[476,12157,12158],{},"dict 什么情况下会退化？",[476,12160,12161],{},"set 为什么适合 membership？",[114,12163,12164],{},[118,12165,12166],{},"LRU",[473,12168,12169,12172,12175],{},[476,12170,12171],{},"为什么 dict 不够？",[476,12173,12174],{},"为什么 list 不够？",[476,12176,12177],{},"HashMap + Doubly Linked List 为什么可以 O(1)？",[114,12179,12180],{},[118,12181,12182],{},"KV Store",[473,12184,12185,12188,12191,12196],{},[476,12186,12187],{},"为什么最终选择 dict？",[476,12189,12190],{},"每个操作复杂度是多少？",[476,12192,12193,12195],{},[60,12194,11882],{}," 为什么是 O(n)？",[476,12197,12198],{},"数据量扩大 100 倍会怎样？",[161,12200,12202],{"id":12201},"day-7-最后-1-小时无-ai-答辩","Day 7 最后 1 小时｜无 AI 答辩",[114,12204,12205],{},"关闭 AI、关闭资料，拿纸回答：",[114,12207,12208],{},"为什么 dict 平均 O(1)，但不能说 dict 永远 O(1)？必须自己讲完整：",[250,12210,12212],{"className":9555,"code":12211,"language":9557,"meta":258,"style":258},"key\n↓\nhash()\n↓\nhash value\n↓\nhash table\n↓\ncollision\n↓\naverage case\n↓\nworst case\n",[60,12213,12214,12219,12223,12228,12232,12237,12241,12246,12250,12255,12259,12264,12268],{"__ignoreMap":258},[294,12215,12216],{"class":296,"line":297},[294,12217,12218],{},"key\n",[294,12220,12221],{"class":296,"line":303},[294,12222,11587],{},[294,12224,12225],{"class":296,"line":34},[294,12226,12227],{},"hash()\n",[294,12229,12230],{"class":296,"line":314},[294,12231,11587],{},[294,12233,12234],{"class":296,"line":320},[294,12235,12236],{},"hash value\n",[294,12238,12239],{"class":296,"line":326},[294,12240,11587],{},[294,12242,12243],{"class":296,"line":332},[294,12244,12245],{},"hash table\n",[294,12247,12248],{"class":296,"line":338},[294,12249,11587],{},[294,12251,12252],{"class":296,"line":343},[294,12253,12254],{},"collision\n",[294,12256,12257],{"class":296,"line":349},[294,12258,11587],{},[294,12260,12261],{"class":296,"line":355},[294,12262,12263],{},"average case\n",[294,12265,12266],{"class":296,"line":360},[294,12267,11587],{},[294,12269,12270],{"class":296,"line":366},[294,12271,12272],{},"worst case\n",[114,12274,12275,12276,12278],{},"然后随机抽查自己：shallow copy 是什么？为什么 ",[60,12277,8633],{}," 不是复制？LRU 为什么需要两个数据结构？为什么 list lookup 是 O(n)？为什么 dict lookup 平均 O(1)？MiniKV 为什么使用 dict？",[114,12280,12281],{},"如果能够脱离资料讲清楚，Week 1 完成。",[148,12283,12284],{"id":12284},"第一周最终产出",[114,12286,12287],{},"周末必须有：",[250,12289,12291],{"className":9555,"code":12290,"language":9557,"meta":258,"style":258},"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,12292,12293,12298,12303,12308,12313,12318,12323,12328,12333,12338,12343,12348,12353,12358,12363,12368,12373],{"__ignoreMap":258},[294,12294,12295],{"class":296,"line":297},[294,12296,12297],{},"week01\u002F\n",[294,12299,12300],{"class":296,"line":303},[294,12301,12302],{},"├── exercises\u002F\n",[294,12304,12305],{"class":296,"line":34},[294,12306,12307],{},"│   ├── dedup.py\n",[294,12309,12310],{"class":296,"line":314},[294,12311,12312],{},"│   ├── frequency.py\n",[294,12314,12315],{"class":296,"line":320},[294,12316,12317],{},"│   └── sorting.py\n",[294,12319,12320],{"class":296,"line":326},[294,12321,12322],{},"├── lru\u002F\n",[294,12324,12325],{"class":296,"line":332},[294,12326,12327],{},"│   ├── lru_cache.py\n",[294,12329,12330],{"class":296,"line":338},[294,12331,12332],{},"│   └── test_lru.py\n",[294,12334,12335],{"class":296,"line":343},[294,12336,12337],{},"├── kv_store\u002F\n",[294,12339,12340],{"class":296,"line":349},[294,12341,12342],{},"│   ├── store.py\n",[294,12344,12345],{"class":296,"line":355},[294,12346,12347],{},"│   └── test_store.py\n",[294,12349,12350],{"class":296,"line":360},[294,12351,12352],{},"├── benchmarks\u002F\n",[294,12354,12355],{"class":296,"line":366},[294,12356,12357],{},"│   └── benchmark.py\n",[294,12359,12360],{"class":296,"line":372},[294,12361,12362],{},"├── notes\u002F\n",[294,12364,12365],{"class":296,"line":9653},[294,12366,12367],{},"│   ├── object_model.md\n",[294,12369,12370],{"class":296,"line":9658},[294,12371,12372],{},"│   └── complexity.md\n",[294,12374,12375],{"class":296,"line":9664},[294,12376,12377],{},"└── week01_review.md\n",[148,12379,12380],{"id":12380},"最终标准",[473,12382,12383,12389,12395],{},[476,12384,12385,12388],{},[118,12386,12387],{},"Level 1","：能解释 Python 对象、引用、可变性、作用域、常见数据结构。",[476,12390,12391,12394],{},[118,12392,12393],{},"Level 2","：不看教程独立完成去重、频率统计、排序、LRU。",[476,12396,12397,12400],{},[118,12398,12399],{},"Level 3","：自己设计并实现 Mini Key-Value Store，并用 Benchmark 验证自己的复杂度判断。",[114,12402,12403,12404,1032],{},"时间不够时的优先级：",[118,12405,12406],{},"MiniKV > LRU > 数据结构实验 > 理论阅读 > 笔记美化",[114,12408,12409],{},"这周不要追求“把 Python 学完”。把这几个问题真正吃透，比看完一门 Python 课程价值高得多。",[148,12411,2534],{"id":2534},[473,12413,12414,12419,12425,12430,12435,12440,12445,12450,12455,12461,12466,12471,12477,12482],{},[476,12415,12416,9700],{},[123,12417,9699],{"href":9697,"rel":12418},[2543],[476,12420,12421],{},[123,12422,12424],{"href":11168,"rel":12423},[2543],"Naming and binding（名字、引用与赋值）",[476,12426,12427],{},[123,12428,9707],{"href":9705,"rel":12429},[2543],[476,12431,12432],{},[123,12433,9714],{"href":9712,"rel":12434},[2543],[476,12436,12437],{},[123,12438,9721],{"href":9719,"rel":12439},[2543],[476,12441,12442],{},[123,12443,9728],{"href":9726,"rel":12444},[2543],[476,12446,12447],{},[123,12448,9735],{"href":9733,"rel":12449},[2543],[476,12451,12452],{},[123,12453,9742],{"href":9740,"rel":12454},[2543],[476,12456,12457],{},[123,12458,12460],{"href":5284,"rel":12459},[2543],"函数定义详解（默认参数、任意参数列表）",[476,12462,12463],{},[123,12464,9749],{"href":9747,"rel":12465},[2543],[476,12467,12468],{},[123,12469,9756],{"href":9754,"rel":12470},[2543],[476,12472,12473],{},[123,12474,12476],{"href":11792,"rel":12475},[2543],"functools.lru_cache（LRU 参考实现）",[476,12478,12479],{},[123,12480,9763],{"href":9761,"rel":12481},[2543],[476,12483,12484],{},[123,12485,9770],{"href":9768,"rel":12486},[2543],[2580,12488,7308],{},{"title":258,"searchDepth":303,"depth":303,"links":12490},[12491,12496,12501,12506,12511,12518,12525,12530,12531,12532],{"id":11153,"depth":303,"text":11154,"children":12492},[12493,12494,12495],{"id":5273,"depth":34,"text":5274},{"id":5351,"depth":34,"text":5352},{"id":5535,"depth":34,"text":5536},{"id":11320,"depth":303,"text":7953,"children":12497},[12498,12499,12500],{"id":11330,"depth":34,"text":11331},{"id":11375,"depth":34,"text":11376},{"id":11396,"depth":34,"text":11397},{"id":11406,"depth":303,"text":8433,"children":12502},[12503,12504,12505],{"id":5570,"depth":34,"text":5274},{"id":11465,"depth":34,"text":11466},{"id":11564,"depth":34,"text":11565},{"id":11618,"depth":303,"text":9012,"children":12507},[12508,12509,12510],{"id":11628,"depth":34,"text":11629},{"id":11675,"depth":34,"text":11676},{"id":11713,"depth":34,"text":11714},{"id":11768,"depth":303,"text":11769,"children":12512},[12513,12514,12515,12516,12517],{"id":11775,"depth":34,"text":11776},{"id":11798,"depth":34,"text":11799},{"id":11814,"depth":34,"text":11815},{"id":11821,"depth":34,"text":11822},{"id":11850,"depth":34,"text":11851},{"id":11857,"depth":303,"text":11858,"children":12519},[12520,12521,12522,12523,12524],{"id":11864,"depth":34,"text":11865},{"id":11902,"depth":34,"text":11903},{"id":11979,"depth":34,"text":11980},{"id":12045,"depth":34,"text":12046},{"id":12052,"depth":34,"text":12053},{"id":12082,"depth":303,"text":12083,"children":12526},[12527,12528,12529],{"id":12089,"depth":34,"text":12090},{"id":12105,"depth":34,"text":12106},{"id":12201,"depth":34,"text":12202},{"id":12284,"depth":303,"text":12284},{"id":12380,"depth":303,"text":12380},{"id":2534,"depth":303,"text":2534},"2026-08-25","Week 1 学习计划：7 天约 14–16 小时，从对象模型与数据结构出发，目标独立设计并验证一个 Mini Key-Value Store。",{},"2026-08-25T13:49:05+08:00",{"title":11086,"description":12534},"week1-python-object-model-data-structures","writing\u002F2026-08-25-week1-python-object-model-data-structures",[2636,9799,9800,12541,12542],"lru-cache","learning-plan","l0sP51xQPPMlVI3lh-ScmQKEV3_PQ-QE-iPWLa6B21g",{"id":12545,"title":12546,"body":12547,"category":12985,"date":12986,"description":12987,"extension":2627,"hidden":2628,"kind":5123,"meta":12988,"navigation":30,"path":12989,"publishedAt":12990,"seo":12991,"series":5123,"slug":12992,"stem":12993,"tags":12994,"unit":5123,"updated":12986,"__hash__":12999},"writing\u002Fwriting\u002F2026-08-17-personal-investment-framework.md","个人财务规划与长期投资体系",{"type":111,"value":12548,"toc":12968},[12549,12552,12555,12559,12562,12582,12586,12612,12615,12619,12622,12625,12636,12639,12643,12646,12669,12672,12692,12696,12699,12702,12716,12719,12723,12726,12740,12743,12747,12750,12754,12757,12760,12763,12767,12770,12799,12802,12806,12809,12813,12816,12833,12836,12840,12843,12856,12859,12863,12869,12875,12881,12887,12893,12899,12902,12906,12912,12918,12921,12925,12965],[114,12550,12551],{},"我已经进入工作阶段，有相对稳定的工资现金流，有一笔利率较低的低息贷款，目前主要承担利息成本。没有必要因为低利率就优先大规模提前偿还——保留流动性和投资能力更重要。未来购房等重大资产大概率主要靠自己，所以个人长期现金流、储蓄能力和资产积累是基本盘。",[114,12553,12554],{},"投资方面有约两年交易经验，主要用ETF，小仓位配置个股，自认为已经形成一定风控意识。偏好ETF，重视风险控制，倾向三笔建仓，建仓速度相对较快，习惯设止损。对宏观、行业、估值和市场结构有较强兴趣，不想完全采用机械无脑定投。",[148,12556,12558],{"id":12557},"一财务方案","一、财务方案",[114,12560,12561],{},"核心思路是把部分现金转入券商账户，同时保留一部分高流动性现金作为生活和应急资金。",[473,12563,12564,12570,12576],{},[476,12565,12566,12569],{},[118,12567,12568],{},"券商账户","：定位为长期资本账户，权益仓位大致维持在60%～80%，中枢约70%，剩余资金留在券商作为等待机会和未来加仓的现金。",[476,12571,12572,12575],{},[118,12573,12574],{},"银行账户","：保持一笔随时可用的安全垫，覆盖房租、水电、日常消费和短期突发支出。",[476,12577,12578,12581],{},[118,12579,12580],{},"工资现金流","：工资到账后先覆盖生活成本，补充安全垫，超过安全垫需求的资金再逐步进入投资账户。因为工资持续产生现金流，不需要把当前所有资金一次性投入市场。",[148,12583,12585],{"id":12584},"二资产配置框架","二、资产配置框架",[473,12587,12588,12594,12600,12606],{},[476,12589,12590,12593],{},[118,12591,12592],{},"核心指数 约30%","——负责长期Beta",[476,12595,12596,12599],{},[118,12597,12598],{},"行业ETF 约30%","——体现主动判断",[476,12601,12602,12605],{},[118,12603,12604],{},"个股 约10%～20%","——有限Alpha探索，后续倾向控制在更小比例",[476,12607,12608,12611],{},[118,12609,12610],{},"现金 约20%～40%","——流动性和等待机会",[114,12613,12614],{},"整体权益仓位保持在60%～80%。核心资产负责长期Beta，行业ETF体现主动判断，个股承担有限的Alpha探索，现金承担流动性和等待机会的功能。",[148,12616,12618],{"id":12617},"三定投改造有纪律的分批建仓","三、定投改造：有纪律的分批建仓",[114,12620,12621],{},"我不习惯长期、机械、固定日期定投，更习惯判断行情后快速分三笔建仓并设止损。这里有一个核心冲突：短中期交易思维关注买点、止损和方向；长期投资关注长期资产质量、估值、仓位、回撤和时间。",[114,12623,12624],{},"结论是没有必要强迫自己变成传统意义上的无脑定投者。替代方案是把定投改造成有纪律的分批建仓：",[770,12626,12627,12630,12633],{},[476,12628,12629],{},"第一笔建立基础仓",[476,12631,12632],{},"第二笔等待估值改善、市场回撤或其他客观条件",[476,12634,12635],{},"第三笔等待进一步风险释放并出现一定右侧确认",[114,12637,12638],{},"时间尺度从几天扩大到数周、数月甚至更长。可以择时，但择时主要用于控制仓位，而不是试图准确预测市场顶部和底部。",[148,12640,12642],{"id":12641},"四指数投资的真正难点","四、指数投资的真正难点",[114,12644,12645],{},"指数长期投资并非简单到完全不需要思考，而是将难点从选股转移到了估值、仓位、风险管理和行为纪律：",[473,12647,12648,12651,12654,12657,12660,12663,12666],{},[476,12649,12650],{},"估值判断",[476,12652,12653],{},"回撤承受能力",[476,12655,12656],{},"风险判断",[476,12658,12659],{},"仓位管理",[476,12661,12662],{},"长期持有",[476,12664,12665],{},"避免情绪化交易",[476,12667,12668],{},"避免因为短期波动改变长期策略",[114,12670,12671],{},"长期主义不是\"买入后十年完全不动\"，而是使用较长时间尺度评价资产，同时允许基于估值、资产质量、风险和个人财务状况进行合理调整。三个需要区分的概念：",[473,12673,12674,12680,12686],{},[476,12675,12676,12679],{},[118,12677,12678],{},"长期主义","：长期持有长期逻辑成立、风险可接受的资产",[476,12681,12682,12685],{},[118,12683,12684],{},"死拿","：无论估值、基本面和个人财务状况如何都不卖",[476,12687,12688,12691],{},[118,12689,12690],{},"投机","：依赖短期价格波动、频繁猜测方向、缺乏长期正期望和风险边界",[148,12693,12695],{"id":12694},"五沪深300","五、沪深300",[114,12697,12698],{},"最初对沪深300存在明显心理障碍：担心A股历史上的制度问题、内幕交易、制度变化风险，担心长期持有存在较长时间低回报甚至长期被套的可能，担心买沪深300等同于押注中国国运。",[114,12700,12701],{},"后来逐渐接受沪深300可以被视为中国大型上市公司Beta的重要代表，确实可以算作中国较优秀的核心资产之一，但不意味着任何价格都值得买。几个修正后的认识：",[473,12703,12704,12707,12710,12713],{},[476,12705,12706],{},"沪深300是中国大型核心资产的重要Beta工具，但不宜简单绝对化为\"中国最优秀资产\"",[476,12708,12709],{},"判断一个资产是否适合持有，必须结合估值、时间区间、回撤、盈利、分红和个人风险承受能力",[476,12711,12712],{},"不需要证明沪深300绝对优秀，只需要判断它是否值得在个人组合中占一定比例",[476,12714,12715],{},"核心指数仓位可以逐步建立，不要求一次性完成",[114,12717,12718],{},"历史上即使是优质指数也可能出现非常长的回撤和低回报阶段，\"长期持有\"不等于\"任何价格买入都能获得良好体验\"。认可资产不代表认可任何价格，长期看好不代表短期必须买入。",[148,12720,12722],{"id":12721},"六纳斯达克","六、纳斯达克",[114,12724,12725],{},"认可纳斯达克长期优质资产属性，但担心当前估值偏高，担心历史上互联网泡沫期间的极端回撤，尤其关注境内场内相关ETF可能出现的溢价问题：",[473,12727,12728,12731,12734,12737],{},[476,12729,12730],{},"优秀资产不等于任何价格都值得买",[476,12732,12733],{},"历史上的极端泡沫说明估值和买入价格非常重要",[476,12735,12736],{},"如果境内ETF存在明显场内溢价，实际买入价格可能与底层资产价值存在偏离",[476,12738,12739],{},"需要同时判断底层指数估值和投资工具本身的交易条件",[114,12741,12742],{},"当前不急于配置，等待估值、溢价和风险收益比变得更合理。",[148,12744,12746],{"id":12745},"七海外资产","七、海外资产",[114,12748,12749],{},"曾提出通过海外宽基降低单一中国市场风险，后来认为目前没有必要强行加入海外资产。认可中国资产与海外资产的大类逻辑确实不同，但暂时更希望先把自己真正理解的资产研究透。海外资产不是必须立即配置的任务，不能为了所谓分散化而强迫自己购买一个尚未形成明确判断的资产。如果未来个人资产规模扩大，全球资产配置仍然值得重新研究。",[148,12751,12753],{"id":12752},"八择时","八、择时",[114,12755,12756],{},"不能把长期投资建立在预测顶部和底部的能力之上。择时的合理定义是用于决定仓位大小，而不是决定是否永远持有或彻底退出。",[114,12758,12759],{},"可以参考的因素：估值分位、PE、PB、股息率、历史回撤、距离近期高点的回撤幅度、回撤持续时间、盈利增长、基本面是否发生结构性变化、趋势和右侧确认。",[114,12761,12762],{},"不建议的行为：试图精准抄底、因为等待最低点而长期完全不投资、因为短期上涨而追涨、因为短期下跌而机械止损长期核心指数。",[148,12764,12766],{"id":12765},"九最大回撤与持有体验","九、最大回撤与持有体验",[114,12768,12769],{},"研究指数不能只看长期年化收益，还必须看投资者真实持有过程：",[473,12771,12772,12775,12778,12781,12784,12787,12790,12793,12796],{},[476,12773,12774],{},"最大回撤",[476,12776,12777],{},"回撤持续时间",[476,12779,12780],{},"创新高所需时间",[476,12782,12783],{},"历史不同估值区间买入后的3年、5年、10年收益",[476,12785,12786],{},"盈利增速",[476,12788,12789],{},"股息率",[476,12791,12792],{},"估值分位",[476,12794,12795],{},"指数成分股变化",[476,12797,12798],{},"实际投资者持有体验",[114,12800,12801],{},"一个指数最终能够创新高，不代表投资者在中途一定能够舒服地长期持有。",[148,12803,12805],{"id":12804},"十核心指数与止损","十、核心指数与止损",[114,12807,12808],{},"核心指数不适合直接复制个股的紧密价格止损机制。指数本身可能出现较大的正常周期波动，过于紧密的止损容易造成\"下跌止损—反弹追涨—再次下跌\"的循环。更适合的方式是使用仓位管理、估值判断和长期基本面判断控制风险。不设机械价格止损不等于永远不能卖；如果长期逻辑、资产质量、个人财务状况或资金需求发生重大变化，仍然可以调整或退出。",[148,12810,12812],{"id":12811},"十一主动判断与仓位上限","十一、主动判断与仓位上限",[114,12814,12815],{},"我容易产生一些个人小巧思，认为自己可能通过判断市场、行业和个股获得优势。原则是允许自己聪明，但给聪明设置仓位上限：",[473,12817,12818,12821,12824,12827,12830],{},[476,12819,12820],{},"核心指数部分机械化",[476,12822,12823],{},"行业ETF部分允许主动判断",[476,12825,12826],{},"个股部分允许寻找Alpha",[476,12828,12829],{},"整体组合保留现金",[476,12831,12832],{},"不允许一次判断错误毁掉整个资产组合",[114,12834,12835],{},"理解市场不等于能够战胜市场，两年交易经验也不能证明长期Alpha能力已经被验证。",[148,12837,12839],{"id":12838},"十二主动投资与投机的区分","十二、主动投资与投机的区分",[114,12841,12842],{},"可以择时，可以机械死板，但绝对不能有投机心理。需要区分的是：",[473,12844,12845,12851],{},[476,12846,12847,12850],{},[118,12848,12849],{},"主动投资","：研究行业周期、研究企业盈利、研究估值、根据基本面配置行业ETF或个股",[476,12852,12853,12855],{},[118,12854,12690],{},"：主要依赖短期价格涨跌、频繁猜测方向、高频进出、没有明确正期望、依靠情绪和赌博式仓位",[114,12857,12858],{},"真正需要避免的不是主动判断，而是没有正期望、没有风险边界、依赖短期价格波动的赌博行为。",[148,12860,12862],{"id":12861},"十三投资框架","十三、投资框架",[114,12864,12865,12868],{},[118,12866,12867],{},"第一层·个人财务安全："," 保持生活现金安全垫，维持稳定工资现金流，低息贷款正常管理，避免为了投资牺牲基本生活流动性。",[114,12870,12871,12874],{},[118,12872,12873],{},"第二层·长期资本："," 建立一个独立的券商投资账户，将其视为未来长期资产，而不是短期必须赚钱的账户。",[114,12876,12877,12880],{},[118,12878,12879],{},"第三层·仓位："," 权益仓位保持在可承受区间，参考60%～80%，中枢约70%。上限避免市场狂热时无限追涨，下限避免短期恐慌时完全退出。",[114,12882,12883,12886],{},[118,12884,12885],{},"第四层·资产结构："," 核心指数约30%，行业ETF约30%，个股约10%，现金约30%。",[114,12888,12889,12892],{},[118,12890,12891],{},"第五层·行为纪律："," 可以择时但不依赖精准择时，可以主动判断但限制比例，可以分批建仓但不试图精准抄底，核心指数不用过紧止损，重大逻辑变化时可重新评估，不因短期波动改变长期配置原则。",[114,12894,12895,12898],{},[118,12896,12897],{},"第六层·长期主义："," 用5～10年的尺度评价资产，在可承受的风险范围内长期持有，允许基于估值、资产质量和个人财务状况进行调整。",[114,12900,12901],{},"六层框架形成一个完整循环：工资 → 储蓄 → 投资 → 权益上涨 → 部分兑现 → 改善生活 → 剩余继续投资。权益资产出现明显增值后，适当兑现一小部分用于改善生活——吃顿好的、按摩休闲、旅行、兴趣爱好，剩余资产继续长期复利。这不是短线止盈，而是长期资产配置体系中的财富兑现机制，同时实现两个目标：改善当下生活，避免为了积累财富长期压抑消费；降低风险暴露，把部分账面财富转化为现实财富。本金负责长期增值，收益负责适当改善生活。",[148,12903,12905],{"id":12904},"十四下一步研究","十四、下一步研究",[114,12907,12908,12911],{},[118,12909,12910],{},"沪深300："," 过去15～20年PE\u002FPB历史分位、股息率、盈利增长、最大回撤、回撤持续时间、创新高时间、不同估值分位买入后的3年\u002F5年\u002F10年收益、不同分批建仓策略的结果。",[114,12913,12914,12917],{},[118,12915,12916],{},"纳斯达克100："," 长期年化收益、互联网泡沫时期最大回撤、历史最大回撤、回撤恢复时间、PE和Forward PE历史分位、盈利增长、当前估值、境内相关ETF场内溢价、不同估值区间买入后的长期收益。",[114,12919,12920],{},"最终目的不是寻找\"现在是不是最低点\"，而是建立一套自己真正认可的规则：什么条件下值得建立第一笔仓位，什么时候加仓，什么时候停止加仓，什么时候降低仓位。",[148,12922,12924],{"id":12923},"十五核心原则","十五、核心原则",[770,12926,12927,12930,12933,12936,12938,12941,12944,12947,12950,12953,12956,12959,12962],{},[476,12928,12929],{},"资产配置优先于预测市场",[476,12931,12932],{},"仓位管理优先于精准择时",[476,12934,12935],{},"长期主义不等于死拿",[476,12937,12730],{},[476,12939,12940],{},"可以主动判断，但不能让主动判断毁掉组合",[476,12942,12943],{},"可以择时，但不能依赖择时才能赚钱",[476,12945,12946],{},"可以机械，但不能赌博",[476,12948,12949],{},"可以聪明，但必须给聪明设置仓位上限",[476,12951,12952],{},"不需要证明自己一定正确，只需要保证错误不会毁掉自己的财富积累",[476,12954,12955],{},"不投资也是一种决策",[476,12957,12958],{},"等待本身也是投资能力",[476,12960,12961],{},"本金负责长期增值，收益负责适当改善生活",[476,12963,12964],{},"真正的长期投资不是预测未来，而是在不知道未来的情况下控制风险、保持暴露并让时间发挥作用",[114,12966,12967],{},"目前最适合自己的方向不是强迫自己成为无脑定投者，而是建立\"稳定现金流 + 安全垫 + 核心指数长期资本 + 有限主动配置 + 明确仓位上限 + 估值和风险管理 + 长期主义\"的个人投资系统。其中最重要的不是证明自己聪明，而是确保自己的错误不会毁掉长期财富积累。",{"title":258,"searchDepth":303,"depth":303,"links":12969},[12970,12971,12972,12973,12974,12975,12976,12977,12978,12979,12980,12981,12982,12983,12984],{"id":12557,"depth":303,"text":12558},{"id":12584,"depth":303,"text":12585},{"id":12617,"depth":303,"text":12618},{"id":12641,"depth":303,"text":12642},{"id":12694,"depth":303,"text":12695},{"id":12721,"depth":303,"text":12722},{"id":12745,"depth":303,"text":12746},{"id":12752,"depth":303,"text":12753},{"id":12765,"depth":303,"text":12766},{"id":12804,"depth":303,"text":12805},{"id":12811,"depth":303,"text":12812},{"id":12838,"depth":303,"text":12839},{"id":12861,"depth":303,"text":12862},{"id":12904,"depth":303,"text":12905},{"id":12923,"depth":303,"text":12924},"交易","2026-08-17","在稳定工资现金流和低息贷款背景下，建立一套兼顾安全性、长期财富积累和有限主动判断的个人投资框架。",{},"\u002Fwriting\u002F2026-08-17-personal-investment-framework","2026-08-17T00:30:10+08:00",{"title":12546,"description":12987},"personal-investment-framework","writing\u002F2026-08-17-personal-investment-framework",[12995,12996,12659,12997,12998],"资产配置","ETF","长期投资","风险控制","IWRVZNvihzkK6SoKYZen1vrDYTsfB84Z7I8AQRq8NMU",{"id":13001,"title":13002,"body":13003,"category":5120,"date":13203,"description":13204,"extension":2627,"hidden":2628,"kind":5123,"meta":13205,"navigation":30,"path":13206,"publishedAt":13207,"seo":13208,"series":5123,"slug":13209,"stem":13210,"tags":13211,"unit":5123,"updated":13216,"__hash__":13217},"writing\u002Fwriting\u002F2026-08-13-want-everything-got-nothing.md","有很多想做的 但是没有行动力 也没有经费",{"type":111,"value":13004,"toc":13196},[13005,13008,13011,13014,13017,13020,13024,13027,13033,13039,13042,13045,13048,13051,13054,13057,13060,13064,13067,13070,13073,13076,13082,13085,13088,13092,13095,13102,13105,13111,13117,13123,13129,13132,13135,13161,13168,13172,13175,13178,13181,13184,13187,13193],[114,13006,13007],{},"看到别人去旅游、去不同的地方玩，会羡慕。但一想到自己没钱、又是一个人，就觉得不去也罢。",[114,13009,13010],{},"看到别人事业有成、在台上分享经验，会羡慕。但一想到自己的能力和职位，又觉得自卑，同时又忍不住想：我是不是也能去试试？",[114,13012,13013],{},"看到别人出国，会想自己也出去看看。还是没钱，放弃。",[114,13015,13016],{},"看到别人考研、读研、读博，也想过卷一卷、走学术。但没考上，现在已经在工作、在培训了。",[114,13018,13019],{},"最后我发现：自己不是没有欲望，而是欲望很多，却没有一个被选成第一优先级。每一个欲望冒出来，都被现实条件压了回去。最后剩下的感觉不是\"我不想要\"，而是——我好像什么都想要，但什么都没真正得到。",[148,13021,13023],{"id":13022},"一欲望很多但它们不是同一种东西","一、欲望很多，但它们不是同一种东西",[114,13025,13026],{},"把想做的事情列出来，其实是一张很长的清单。但仔细一看，这些欲望其实分成两种完全不同的东西。",[114,13028,13029,13032],{},[118,13030,13031],{},"一种是即时享受。"," 踢球、打羽毛球、和同学游泳、去足浴、打麻将、玩游戏、旅游——这些事拿起就能做，做完就开心，成本主要是钱和时间。它们的快乐来得直接，去得也快。",[114,13034,13035,13038],{},[118,13036,13037],{},"另一种是长期投入。"," 想在工作上做出成绩、想卷学历、想考研读博读研——这些不是\"去做一次\"就能获得体验的事。它们需要持续几个月甚至几年的投入，成本不只是钱和时间，还有机会成本、精力，以及放弃其他可能性的代价。",[114,13040,13041],{},"我之前把这两种欲望混在一起想，结果就是：既觉得什么都想做，又觉得什么都做不了。即时享受的事觉得\"没时间、没钱、没人陪\"；长期投入的事觉得\"风险太大、不确定值不值得\"。两种阻力叠在一起，最后什么都没动。",[114,13043,13044],{},"但它们需要的行动方式完全不同。即时享受的事，门槛低、风险小，真正的问题只是\"敢不敢迈出第一步\"。长期投入的事，门槛高、需要持续投入，真正的问题是\"有没有在积累选择权，让自己将来能选\"。",[114,13046,13047],{},"分清楚之后，很多事情就想通了。",[114,13049,13050],{},"即时享受的事有一个共同特点：边际效用递减。很久没做的时候跃跃欲试，觉得特别想；真的去做了，又没那么想继续；但做的时候，确实又很开心。一开始我怀疑这是不是说明\"我其实没那么想要\"。后来想明白了：边际效用递减不等于不想要。吃完一顿好饭不想马上再吃，不代表那顿饭不好吃。大脑对重复刺激会适应，这是正常现象。",[114,13052,13053],{},"我真正喜欢的，可能不是某一件永远不变的事，而是周期性地获得体验：今天踢球，下周游泳，过几天打游戏，周末去足浴或者打麻将，偶尔出去旅行一次。兴趣不需要固定成一个项目，也不需要现在就给自己贴上\"事业型\"\"享乐型\"\"学术型\"的标签——我还没充分体验过这些事，贴标签太早了。",[114,13055,13056],{},"而长期投入的事，不需要现在就选定一个方向。它们的关键不是\"今天就要决定走哪条路\"，而是\"现在就要开始积累，让自己将来有资格选\"。",[114,13058,13059],{},"这里还有一条容易踩的坑：不要把\"别人拥有\"直接解释成\"我也一定想拥有\"。看到别人的生活而羡慕，是正常的情绪反应；但羡慕不等于自己真正需要。区分的方法很简单——如果一件事连续在脑子里冒出来几次，那更可能是自己的真实欲望，而不只是被别人的朋友圈激活的一时冲动。",[148,13061,13063],{"id":13062},"二想做却不敢迈出第一步","二、想做，却不敢迈出第一步",[114,13065,13066],{},"即时享受类的事情，按理说门槛最低，但偏偏最难迈出第一步。明明想去游泳、去足浴、去谈恋爱，却迟迟不动。真正要行动的时候会害怕，一个人的时候这种感觉更明显。",[114,13068,13069],{},"这大概不是欲望不足，而是行动成本、陌生感、独处感，以及对失败和尴尬的预期在挡路。不需要要求自己突然变成一个很勇敢的人，只需要把第一步缩小到极低成本：",[114,13071,13072],{},"想游泳，不用要求自己\"培养一个游泳的爱好\"，只去一次、游二十分钟，不舒服就回来。想去足浴，不用要求自己\"开始享受生活\"，找一家店体验一次就行。想谈恋爱，不用要求自己\"找到女朋友\"，先认识一个新的异性，看看自己喜不喜欢恋爱这件事。",[114,13074,13075],{},"第一次行动的目的不是坚持，而是拿到真实数据——让大脑发现，现实通常没有想象中那么可怕。很多\"我到底喜不喜欢\"的问题，其实都可以通过一次低成本行动获得答案，而不是靠长期脑补。",[114,13077,13078,13079],{},"我后来意识到自己有一个循环：在脑子里想象生活→羡慕、兴奋→想到现实成本→害怕→不行动→继续羡慕。要打破的不是\"找到人生目标\"，而是这个循环本身。所以我给自己定了一条原则：",[118,13080,13081],{},"连续想了三次以上、风险又很低的事情，就实际去试一次。",[114,13083,13084],{},"这个原则主要针对即时享受类的事。长期投入的事不能靠\"想了几次就冲\"来决定，因为成本和风险完全不同。但即时享受的事——去游一次泳、去一次足浴、约人打一场球——失败了也就损失几十块钱和一个下午，没有任何长期后果。对这种事，想了三次还在想，就说明不是一时冲动，该去了。",[114,13086,13087],{},"一个人的问题也是现实。独立不等于所有事情都必须一个人完成。可以主动找朋友、同学、同事发起低成本的活动：约一场羽毛球、游一次泳、吃顿饭。有人陪着，很多第一步会容易很多。",[148,13089,13091],{"id":13090},"三把自己搭成一套生活系统","三、把自己搭成一套生活系统",[114,13093,13094],{},"想了一圈，我最后没有找到什么\"人生使命\"，反而想明白一件事：我不需要一个终极答案，我需要一套能持续运行、可以调整的生活系统。",[114,13096,13097,13098,13101],{},"自由可以理解成两部分：",[118,13099,13100],{},"当下的享受，加上未来的选择权。"," 年轻最大的资产就是时间，所以可以大胆做低成本、低风险的试错；但必须有一条长期能力积累线，还要有钱作为第二层保险。不要为了未来把现在全牺牲掉，也不要走另一个极端——把所有时间变成工作、学习和存钱，最后得到资源却没有生活。",[114,13103,13104],{},"这套系统我把它拆成四个账户：",[114,13106,13107,13110],{},[118,13108,13109],{},"生活账户。"," 工资的一部分用于正常生活、娱乐、运动、朋友和探索。想做就做，但在预算范围内。踢球、游泳、足浴、游戏、旅行、恋爱——这些即时享受都是生活本身，不是浪费时间。它们对应的是\"当下的享受\"那一半的自由。",[114,13112,13113,13116],{},[118,13114,13115],{},"安全账户。"," 固定储蓄，为失业、考研、换工作、突发事件留缓冲。储蓄的核心价值不是数字，而是\"可以说不\"的能力：可以离开不合适的工作，可以随时准备考研、转型或者应对突发情况。钱是第二层保险，第一层是能力。",[114,13118,13119,13122],{},[118,13120,13121],{},"能力账户。"," 每天或每周持续投入，形成至少一套真正能带走的能力。它可以来自现在的工作，也可以来自工作之外：数据、编程、业务理解、英语、学历，什么都行。这个账户对应的是那些\"长期投入\"类的欲望——想卷学历、想在工作上做出成绩、想考研读博，本质上都是在往这个账户里存东西。关键是无论未来留在国企、跳槽私企，还是回去读研，这套能力都能用上。如果方向还不确定，那就先提高储蓄率、降低风险，边走边看。",[114,13124,13125,13128],{},[118,13126,13127],{},"身体账户。"," 持续运动、睡眠、饮食。身体是未来几十年所有选择的基础设施，这个\"硬件\"得自己负责。",[114,13130,13131],{},"即时享受的事归生活账户管，低成本就去试；长期投入的事归能力账户和安全账户管，需要持续积累。两种欲望不再混在一起互相打架，而是各有各的位置。",[114,13133,13134],{},"配套的规则也不复杂：",[473,13136,13137,13140,13143,13146,13149,13152,13155,13158],{},[476,13138,13139],{},"低成本、低风险、连续想了几次的事，主动尝试一次。",[476,13141,13142],{},"短期兴趣可以随心变化，不要求一个爱好终身坚持。",[476,13144,13145],{},"长期能力和身体要有最低投入，不能完全看当天心情。",[476,13147,13148],{},"当前消费可以有，但不能为了即时欲望破坏未来选择权。",[476,13150,13151],{},"不要把\"现在做不到\"解释成\"我其实不想要\"。",[476,13153,13154],{},"不要把\"别人拥有\"直接解释成\"我也一定想拥有\"。",[476,13156,13157],{},"不要因为边际效用递减，就否定曾经带来快乐的事。",[476,13159,13160],{},"不追求极端自律，设置一个低但稳定、不能长期突破的最低投入。",[114,13162,13163,13164,13167],{},"原则可以概括成一句：",[118,13165,13166],{},"兴趣随心，底线固定，方向可以变。"," 身体每周有固定运动底线，具体形式随便换；能力每周固定投入时间，具体方向可以调整；财务每月固定存一部分，剩下的在预算内自由花。",[148,13169,13171],{"id":13170},"四关于未来不需要现在知道答案","四、关于未来：不需要现在知道答案",[114,13173,13174],{},"现在不需要决定留在现在的公司、去总部、跳私企、考研、读博、出国哪个是唯一正确答案。二十多岁，合理的状态不是\"已经知道我要成为什么人\"，而是\"我有能力继续探索，而且探索失败也不会把自己搞崩\"。",[114,13176,13177],{},"真正需要做的是在未来两三年持续积累工作经验、可迁移能力、储蓄和身体，让未来的问题从\"我有没有资格选择\"变成\"我想选择哪一个\"。",[114,13179,13180],{},"也不需要每天审判自己。半年复盘一次就够：钱有没有增加？能力有没有比半年前更强？身体状态有没有改善？过去半年有没有真正体验过一些东西？如果这些方面总体都在向前，说明人生轨道并没有问题。",[114,13182,13183],{},"最重要的目标可以是一个状态，而不是一个终点：把自己建设成一个有钱、有能力、有身体、有选择权、敢于行动的人。未来想留在国企、去总部、跳私企、考研、读博、出国，都可以在有选择权的基础上重新决定。",[148,13185,13186],{"id":13186},"写在最后",[114,13188,13189,13190],{},"如果非要给这套生活一个概括，大概是这句：",[118,13191,13192],{},"我愿意用一部分当下的自由，换取未来更大的自由；剩下的时间，好好享受现在。",[114,13194,13195],{},"现在的人生任务，不是找到唯一正确的人生，而是在认真积累的同时真正去生活：保持身体、持续学习、固定储蓄、勇敢试错。让自己越来越有能力、越来越有选择权——同时允许自己，享受当下。",{"title":258,"searchDepth":303,"depth":303,"links":13197},[13198,13199,13200,13201,13202],{"id":13022,"depth":303,"text":13023},{"id":13062,"depth":303,"text":13063},{"id":13090,"depth":303,"text":13091},{"id":13170,"depth":303,"text":13171},{"id":13186,"depth":303,"text":13186},"2026-08-13","从\"什么都想要\"到四个账户的生活系统：身体、能力、储蓄与当下的享受",{},"\u002Fwriting\u002F2026-08-13-want-everything-got-nothing","2026-08-13T23:25:16+08:00",{"title":13002,"description":13204},"want-everything-got-nothing","writing\u002F2026-08-13-want-everything-got-nothing",[13212,13213,13214,13215],"自我认知","生活系统","行动力","储蓄","2026-08-15","PMPrSEXNtdbY5gyAMANwqVMLNaWYmiTMN8tFovf_tIk",{"id":13219,"title":13220,"body":13221,"category":7897,"date":13464,"description":13465,"extension":2627,"hidden":2628,"kind":5123,"meta":13466,"navigation":30,"path":13467,"publishedAt":13468,"seo":13469,"series":5123,"slug":13470,"stem":13471,"tags":13472,"unit":5123,"updated":13203,"__hash__":13478},"writing\u002Fwriting\u002F2026-08-04-steel-industry-profit-chain-analysis.md","上下游产业链的利润分配与长期趋势：从净利率对比说起",{"type":111,"value":13222,"toc":13452},[13223,13228,13232,13235,13238,13241,13244,13248,13251,13257,13263,13266,13270,13273,13276,13279,13282,13288,13292,13295,13298,13301,13305,13308,13314,13320,13323,13330,13334,13337,13344,13347,13354,13358,13361,13367,13373,13379,13382,13386,13389,13392,13395,13399,13402,13408,13411,13417,13423,13430,13434,13440,13446],[7389,13224,13225],{},[114,13226,13227],{},"上汽净利率2.7%，头部钢企3.59%。一家造车的和一家炼钢的，利润率居然差不多。这个看似荒诞的对比，引出了一系列追问：钢厂的利润被谁拿走了？上游矿山为什么这么赚钱？头部央企的突围能不能改变格局？钢铁行业的未来到底是周期波动还是结构性下行？本文沿着这些问题逐层展开，试图给出一个理性的判断。",[148,13229,13231],{"id":13230},"一两条利润率曲线的交汇","一、两条利润率曲线的交汇",[114,13233,13234],{},"2025年上汽集团净利率2.70%，宝钢股份3.59%。十年前这个对比不可想象--整车厂曾是高利润的代名词，钢厂则是微利的苦生意。现在两者的利润率交汇了，但原因各不相同。",[114,13236,13237],{},"整车厂的利润率被三层力量压缩：价格战（新能源车降价均幅3万元）、上游成本侵蚀（碳酸锂涨132%、存储芯片从20元涨到近100元）、以及转型期的高强度研发投入。2026年上半年整车制造利润率仅1.5%，卖一辆10万元的车，整车厂到手约1500元。",[114,13239,13240],{},"钢厂的利润率则长期低迷。2026年上半年行业平均销售利润率0.86%，247家样本钢企中近三分之二亏损。一季度重点企业钢铁主业利润同比暴跌85.6%。",[114,13242,13243],{},"两者利润率交汇的真正含义是：**\"制造\"环节的利润正在被系统性挤压，利润沿产业链向上游资源端和技术端转移。** 电池厂（宁德时代利润率17%）拿走了电动车的利润，铁矿石巨头（利润率30-40%+）拿走了钢铁的利润。留在中间环节的企业，无论造车还是炼钢，都只能赚加工辛苦费。",[148,13245,13247],{"id":13246},"二钢厂的夹心困境","二、钢厂的\"夹心\"困境",[114,13249,13250],{},"钢厂的微利不是经营不善，而是产业链位置决定的。",[114,13252,13253,13256],{},[118,13254,13255],{},"上游：铁矿石80%以上依赖进口，被四大矿山（淡水河谷、力拓、必和必拓、FMG）垄断。"," 这四家控制着全球70%以上的铁矿石贸易量，开采成本仅15-25美元\u002F吨。国内钢厂在每年铁矿石谈判中几乎没有话语权。2026年上半年上游煤炭企业利润同比增长41.1%，而中游钢企利润下降了25%--利润在向上游集中。",[114,13258,13259,13262],{},[118,13260,13261],{},"下游：房地产需求持续萎缩（6月同比降25%），传统用钢领域需求疲软。"," 钢价涨不动，成本又传不下去。2026年上半年螺纹钢期货高低价差仅293元\u002F吨，波动幅度远小于往年。",[114,13264,13265],{},"上游拿走资源暴利，下游握着买方议价权，中间的钢厂两头受气。这个格局不是周期性的，而是结构性的--只要铁矿石供应格局不变、房地产需求不复苏，\"夹心\"状态就会持续。",[148,13267,13269],{"id":13268},"三需求见顶一个结构性的转折","三、需求见顶：一个结构性的转折",[114,13271,13272],{},"判断一个行业的长期趋势，需求比供给更重要。钢铁行业的需求端正在经历一个历史性转折。",[114,13274,13275],{},"中国是全球最大的钢铁消费国，粗钢产量占全球一半以上。历史上，中国钢铁需求的最大驱动力是房地产和基建。2020年中国粗钢产量达到约10.65亿吨的峰值，此后开始回落。世界钢铁协会数据显示，2026年上半年中国粗钢产量500.0 Mt，同比下降3.0%。",[114,13277,13278],{},"需求结构的变化更具决定性：2025年制造业用钢占比达到53%，首次超过建筑业。这不是周期性切换--房地产新开工面积从2020年峰值下滑超过60%，而人口结构（劳动年龄人口2012年见顶、总人口开始负增长）决定了这种下滑不可逆。制造业用钢（汽车、家电、新能源）虽然在增长，但体量上不足以完全填补房地产留下的缺口。",[114,13280,13281],{},"全球来看，2026年上半年全球粗钢产量931.5 Mt，同比下降0.7%。需求增量几乎全部来自印度（同比增长7.1%）和越南（同比增长26.9%），但这些新兴市场绝对体量仍小，无法抵消中国的结构性回落。",[114,13283,13284,13287],{},[118,13285,13286],{},"钢铁行业的总需求曲线已经从\"增长\"转为\"横盘至缓降\"。"," 在一个总需求不再增长的市场里，所有企业的竞争本质上都是存量博弈。",[148,13289,13291],{"id":13290},"四供给收缩政策在倒逼但能否对冲","四、供给收缩：政策在倒逼，但能否对冲？",[114,13293,13294],{},"面对需求回落，政策在主动压减产能。2026年版《钢铁行业产能置换实施办法》要求1.5:1减量置换，同时钢铁行业纳入全国碳市场，碳价逼近94元\u002F吨。",[114,13296,13297],{},"供给收缩能否对冲需求下滑？2016年供给侧改革曾短暂推高钢价和利润，但那次是\"行政限产+地产周期上行\"的双轮驱动。现在地产周期已经下行，单纯压产能的效果存疑。2026年中国钢材需求量预计8亿吨，粗钢产量预测9.35亿吨，供需缺口超过1.35亿吨。",[114,13299,13300],{},"更关键的是，产能收缩导致的价格回升，受益最大的是成本最低的龙头企业，而非全行业。行业整合加速，头部央企等龙头份额提升，但这是\"龙头拿走更大蛋糕\"的零和博弈，不是行业整体利润池的扩大。",[148,13302,13304],{"id":13303},"五尝试突围方向正确但不足以逆转趋势","五、尝试突围：方向正确，但不足以逆转趋势",[114,13306,13307],{},"宝钢正在做两件事来改变自己的产业链位置。",[114,13309,13310,13313],{},[118,13311,13312],{},"向上游走。"," 铁矿石自给率目前约36%，目标50%以上，核心抓手是几内亚西芒杜铁矿（品位65%+，总产能1.2亿吨\u002F年，2025年11月投产）。自给率超过50%是一个质变点：铁矿石涨价时自有矿山利润暴涨，对外采购需求减半，谈判筹码增加。",[114,13315,13316,13319],{},[118,13317,13318],{},"向高端走。"," 该企业2025年研发投入近200亿元，0.16mm超薄取向硅钢全球唯一量产（纯利润200元\u002F吨以上，普通钢材仅20-100元\u002F吨），新一代汽车高强钢在燃油车身中占比超八成。",[114,13321,13322],{},"但两件事都有局限。西芒杜面临政治、基建和运营风险，实际成本不确定；且这是该企业一家的自给率，行业整体进口依存度依然超80%。高端产品方向对，但取向硅钢总产能120万吨，对比5000万吨年产能占比仅3%左右--技术领先未能改变整体利润结构，5000万吨产能中绝大多数仍是微利的普通钢材。",[114,13324,13325,13326,13329],{},"雪球用户\"四七八二\"在某钢企个股分析中指出：该企业的取向硅钢全球产能500万吨中占120万吨且全为高端，无取向硅钢也专注于高牌号产品。这些产品的技术壁垒很高，但",[118,13327,13328],{},"壁垒高不等于利润厚，因为量太小。"," 从\"有高端产品\"到\"高端产品占比足以扭转利润率\"，中间需要一个量级跳跃。",[148,13331,13333],{"id":13332},"六四大矿山为什么不整合下游","六、四大矿山为什么不整合下游",[114,13335,13336],{},"理解了钢厂的困境，自然会问：四大矿山为什么不把下游也吃下来？",[114,13338,13339,13340,13343],{},"答案很简单：",[118,13341,13342],{},"不需要。"," 矿山核心业务回报率极高，钢铁制造是低利润重资产行业。把高回报的现金流投入低回报的领域，会拉低整体资本回报率。与其买钢厂，不如把钱投向回报率更高的矿产（铜、锂）。",[114,13345,13346],{},"2013年大宗商品价格回落后，巨头们从\"纵向一体化\"转向\"轻资产\"策略--控矿权、轻基建、提回报。不做股权收购，但与核心客户（如头部央企）合资开发项目，锁定长期销售渠道，不承担运营钢厂的风险。",[114,13348,13349,13350,13353],{},"四大矿山站在产业链利润顶端，没有必要向低利润环节扩张。这个事实本身就说明了钢铁产业链的利润分配格局：",[118,13351,13352],{},"上游吃肉，下游喝汤。"," 只要这个格局不变，钢厂的利润天花板就在那里。",[148,13355,13357],{"id":13356},"七全球格局三个世界","七、全球格局：三个世界",[114,13359,13360],{},"世界钢铁协会2026年6月数据显示，全球钢铁业呈现明显分化：",[114,13362,13363,13366],{},[118,13364,13365],{},"需求增长区（印度、东南亚）。"," 印度是全球钢铁需求最强劲的市场，2025财年消费量同比增长8%；越南上半年产量同比增长26.9%。印度JSW Steel每吨利润618元，受益于本土需求红利。",[114,13368,13369,13372],{},[118,13370,13371],{},"需求疲软区（欧洲、日韩）。"," 欧盟过去三年产量小幅下滑，能源成本高企；日韩趋势性收缩，制造业外流。日本制铁靠结构改革（高炉从15座减至10座）和定价权，每吨利润710元全球第一，但这是\"小而美\"模式，不可复制到中国头部钢企的体量。",[114,13374,13375,13378],{},[118,13376,13377],{},"贸易保护区（美国）。"," 靠50%关税壁垒维持高钢价（热轧价格是亚洲的两倍），纽柯钢铁利润大增。但这种保护主义策略以牺牲下游制造业为代价，且不可复制。",[114,13380,13381],{},"中国头部钢企每吨利润仅约221元，约为日本制铁的三分之一。规模全球领先，但\"大而不强\"的特征明显--产品仍以普通钢材为主，上游资源尚未完全掌握。",[148,13383,13385],{"id":13384},"八产业一线的印证","八、产业一线的印证",[114,13387,13388],{},"雪球用户黄建平在一家头部钢企做了8年控制模型工程师，前两年几乎天天加班，后面几年经常半夜被叫去处理问题。他接触过外包协力工--技能水平更低，工作更辛苦，收入也更低。",[114,13390,13391],{},"他的判断很直接：**\"如果我是这家企业的董事长，我也没有更好办法。\"** 行业竞争激烈、周期性强、动辄亏损，资本回报率比较低，利润和股价10多年徘徊。他说\"要靠发展解决问题，没有灵丹妙药\"。",[114,13393,13394],{},"这个判断来自产业内部人的体感，与宏观数据相互印证：钢厂的微利不是管理问题，而是产业链位置和行业基本面决定了天花板。黄建平后来离开那家企业，管理私募基金，跨行业投研20年，现在用 AI agent 搭建投研平台。他的人生选择本身就是一个隐喻--在趋势下行的行业里，个人最优策略是保持迁移能力，去寻找更好的赛道。",[148,13396,13398],{"id":13397},"九理性审视是周期底部还是结构性下行","九、理性审视：是周期底部还是结构性下行？",[114,13400,13401],{},"以上分析指向一个判断：钢铁行业的长期趋势是下行的。但必须对这个判断保持审慎，考虑反方证据：",[114,13403,13404,13407],{},[118,13405,13406],{},"周期波动的可能性。"," 钢铁是强周期行业，2026年Q1行业利润已同比改善，247家钢企盈利率从底部回升。库存周期、产能出清后的价格反弹都是真实存在的。如果将当前视为周期底部，那么未来可能出现利润修复。",[114,13409,13410],{},"但这里需要区分周期性波动和结构性趋势。周期性利润修复是在一个趋势线上的波动。每一次周期高点的利润率，大概率低于上一次--因为总需求曲线在缓降，产能过剩在持续，上游垄断格局未变。如果这个判断成立，那么\"周期底部\"的反弹高度会越来越低。",[114,13412,13413,13416],{},[118,13414,13415],{},"技术变革的可能性。"," 电炉炼钢比例提升可能打破铁矿石垄断。中国电炉钢比例目前约10-15%，远低于美国（70%+）。随着废钢积累量增长，这个方向值得期待。但这是一个5-10年的缓慢过程，短期内不足以改变成本结构。氢冶金等新技术也在探索中，但距离工业化量产尚远。",[114,13418,13419,13422],{},[118,13420,13421],{},"行业整合的可能性。"," 产能出清后龙头份额提升，头部央企等可能获得更强的定价权。但龙头利润率改善是\"份额逻辑\"而非\"行业逻辑\"--全行业利润池在缩小，龙头拿走更大比例，不代表行业整体在变好。",[114,13424,13425,13426,13429],{},"综合来看：",[118,13427,13428],{},"钢铁行业面临的是需求结构性回落+成本格局刚性+技术替代缓慢的三重压力。"," 短期内有周期性反弹的空间，但长期趋势线的斜率大概率是向下的。这个判断不是终局论--技术变革（电炉、氢冶金）和资源整合（西芒杜）可能在5-10年后改变行业格局。但在可见的未来，这是一个需要正视的现实。",[148,13431,13433],{"id":13432},"十对投资者和产业人的含义","十、对投资者和产业人的含义",[114,13435,13436,13439],{},[118,13437,13438],{},"对投资者："," 趋势下行的行业不是不能投，而是不能赌行业整体回暖。这家头部钢企市净率0.62，反映的是市场对行业基本面的悲观定价。如果这家央企能兑现资源整合和产品高端化的战略，存在估值修复的α空间。但这是\"行业β下行+个体α突出\"的投资逻辑，不是\"行业会好转\"的赌注。寻找的是能在下行中扩大份额、改变产业链位置的个体，而非等待行业周期回归的贝塔。",[114,13441,13442,13445],{},[118,13443,13444],{},"对产业中的人："," 黄建平从钢企工程师到跨界投资人的路径提供了一种参考。在趋势下行的行业里，个人的人力资本是最核心的资产。技能的可迁移性、跨行业学习能力、对产业链规律的认知--这些是不受行业周期约束的。他说\"别被专业、出身等限制了发展空间\"，这句话放在钢铁行业的语境下，有特殊的重量。",[114,13447,13448],{},[13449,13450,13451],"em",{},"本文基于一段 AI 辅助对话的思考路径整理改写，参考了世界钢铁协会数据及雪球用户黄建平等的公开文章。涉及的数据和判断请以权威来源为准，不构成投资建议。",{"title":258,"searchDepth":303,"depth":303,"links":13453},[13454,13455,13456,13457,13458,13459,13460,13461,13462,13463],{"id":13230,"depth":303,"text":13231},{"id":13246,"depth":303,"text":13247},{"id":13268,"depth":303,"text":13269},{"id":13290,"depth":303,"text":13291},{"id":13303,"depth":303,"text":13304},{"id":13332,"depth":303,"text":13333},{"id":13356,"depth":303,"text":13357},{"id":13384,"depth":303,"text":13385},{"id":13397,"depth":303,"text":13398},{"id":13432,"depth":303,"text":13433},"2026-08-04","上汽净利率2.7%，宝钢股份净利率3.59%。从这条对比出发，逐层追溯利润沿产业链向上集中的规律，分析钢铁行业需求见顶、成本刚性、技术替代缓慢三重压力，论证长期β下行的结构性原因。",{},"\u002Fwriting\u002F2026-08-04-steel-industry-profit-chain-analysis","2026-08-04T01:06:00+08:00",{"title":13220,"description":13465},"steel-industry-profit-chain-analysis","writing\u002F2026-08-04-steel-industry-profit-chain-analysis",[13473,13474,13475,13476,13477],"钢铁","产业链","央国企","铁矿石","行业分析","UuvtRSoECSuI_NZSCZ-YW281upjuEImA0mcQ3PAkJuQ",{"id":13480,"title":13481,"body":13482,"category":7897,"date":13810,"description":13811,"extension":2627,"hidden":2628,"kind":5123,"meta":13812,"navigation":30,"path":13813,"publishedAt":13814,"seo":13815,"series":5123,"slug":13816,"stem":13817,"tags":13818,"unit":5123,"updated":13203,"__hash__":13824},"writing\u002Fwriting\u002F2026-08-03-yen-depreciation-cognition-drilldown.md","从日元贬值到行为金融：货币、利率与制度",{"type":111,"value":13483,"toc":13799},[13484,13489,13493,13496,13499,13502,13505,13512,13516,13519,13522,13525,13531,13534,13537,13543,13547,13550,13557,13571,13574,13577,13584,13588,13591,13594,13600,13603,13609,13615,13621,13628,13632,13635,13638,13641,13644,13650,13654,13657,13660,13667,13670,13677,13680,13686,13690,13693,13696,13699,13725,13731,13735,13738,13744,13750,13756,13762,13768,13775,13779,13782,13788,13791,13794],[7389,13485,13486],{},[114,13487,13488],{},"一条\"日元贬值\"的新闻，可以被当成财经快讯听完即忘，也可以作为入口，一路下探到货币本质、利率定价、行为金融和国家制度设计。本文记录的是后者--一次通过连续追问凿穿金融概念表层的思考过程。",[148,13490,13492],{"id":13491},"一日元为什么跌表层是利差底层是人口","一、日元为什么跌：表层是利差，底层是人口",[114,13494,13495],{},"2026年7月底，日元跌到近40年最低点，一度突破1美元兑163日元。日本政府单日动用约528亿美元干预，美国也罕见地参与了联合干预--这是2011年以来首次。",[114,13497,13498],{},"直接原因很清楚：美日利差。美国联邦基金利率约3.5%，日本政策利率仅约1%，差了2.5个百分点。资金天然从低息货币流向高息货币--借日元换美元吃利差，这是经典的套息交易（carry trade）。卖日元的人多了，日元就跌。",[114,13500,13501],{},"但\"利差大\"只是把问题往后推了一步。为什么日本不敢加息？因为日本政府债务超过GDP的220%，大幅加息意味着利息支出吞噬财政空间。为什么美国能加息？因为2021-2023年通胀高企，美联储必须收紧。为什么日本通胀来了却不像美国那样加息？因为日本经济长期疲软，加息可能直接把经济打回通缩。",[114,13503,13504],{},"再往下：日本经济为什么疲软？人口老龄化、劳动力萎缩、产业竞争力被中韩追赶。这些是慢变量，不会因为一次干预或一次加息就改变。",[114,13506,13507,13508,13511],{},"所以日元贬值的根因，不在汇率层面，而在人口结构和产业竞争力层面。",[118,13509,13510],{},"金融现象的表层是数字，底层是人口、技术和制度。"," 这条规律不只适用于日元--理解任何资产的长期走势，最终都会落到这几个慢变量上。",[148,13513,13515],{"id":13514},"二汇率是什么从菜市场到欠条","二、汇率是什么：从\"菜市场\"到\"欠条\"",[114,13517,13518],{},"理解了\"为什么跌\"，下一个问题是\"这东西到底是什么\"。",[114,13520,13521],{},"一个有效的具象方式：把货币当成菜市场里的菜。美元是猪肉（全球硬通货），日元是卷心菜（量大、利息低）。\"1美元=163日元\"就是\"1斤猪肉换163斤卷心菜\"。美元加息了（大家都抢美元去存钱），猪肉涨价；或者日本央行大量印钞（卷心菜丰收），菜跌价。兑换比例就这么变了。",[114,13523,13524],{},"这个比喻抓住了供需关系，但有一个需要警惕的边界：它可能让人误以为货币是一种\"商品\"。实际上，货币不是商品，而是一张欠条。",[114,13526,13527,13528],{},"1971年美元与黄金脱钩后，全球货币都不再锚定实物。纸币背后没有金库，电子支付连纸都没有。它的价值来自一个更抽象的东西：",[118,13529,13530],{},"国家信用，具体表现为税收驱动的需求。",[114,13532,13533],{},"想象一家公司不发现金，只发\"内部饭票\"。饭票有价值，是因为公司规定交水电费、租宿舍只收饭票。你为了住宿舍，就必须用劳动去换饭票。国家版的故事一样：法律规定交税只能用本国货币，全国几亿人为了交税和还贷，必须去赚这种\"积分\"。只要国家机器在运转，这张纸就有强制性信用。",[114,13535,13536],{},"如果国家疯狂印饭票而食堂里的鸡没变多，每张饭票换到的鸡丝就变少了--这是对内贬值（通胀）。如果外面的人觉得这家公司未来不行了，不愿意用原价买它的饭票--这是对外贬值（汇率下跌）。",[114,13538,13539,13542],{},[118,13540,13541],{},"对内贬值看货币供给与商品供给的匹配，对外贬值看全球资本对该国未来的信心。两者可以独立发生。"," 日本的情况是：对内通胀终于有所抬头，但对外信心严重不足--因为利率太低，资本不愿意持有日元。",[148,13544,13546],{"id":13545},"三利率的底层不是央行在定是人口和技术在定","三、利率的底层：不是央行在定，是人口和技术在定",[114,13548,13549],{},"汇率短期跟着利率跑。但利率是谁定的？",[114,13551,13552,13553,13556],{},"不是央行拍的板。央行只是跟随一个更底层的\"水位线\"--经济学称之为",[118,13554,13555],{},"自然利率","（natural rate of interest），即既不刺激也不抑制经济的平衡点。这个水位由两个慢变量决定：",[473,13558,13559,13565],{},[476,13560,13561,13564],{},[118,13562,13563],{},"人口结构","：干活的人多不多。劳动力萎缩，经济增长动力不足，自然利率就低。日本是典型--老龄化严重，利率从高位一路降到零甚至负值。",[476,13566,13567,13570],{},[118,13568,13569],{},"生产率","：干活的效率高不高。技术进步快，单位产出提升，经济能承受更高的资金成本，自然利率就高。美国凭借AI和科技革命，至今仍能维持相对较高的利率。",[114,13572,13573],{},"央行在这个水位线上下做微调：通胀高了加息压制需求，经济冷了降息刺激需求。但央行的自由度有限。如果自然利率本身在下行（人口老化、增长放缓），央行长期只能跟着往下走。",[114,13575,13576],{},"这个框架对中国有直接意义。中国正在经历人口结构转型--劳动年龄人口在2012年前后见顶，此后持续下降。如果人口结构决定自然利率的方向，那么中国长期利率大概率会进入下行通道。但中国有一个日本当年没有的缓冲：科技产业升级。电动车、光伏、AI在提高单人产出，用机器替代人。这意味着利率下行的速度可能比日本慢，但方向很难逆转。",[114,13578,13579,13580,13583],{},"这个判断对个人的直接含义：",[118,13581,13582],{},"如果你的房贷利率是3%-4%，在未来利率可能降至1%-2%的环境下，它就是一个\"高息包袱\"。"," 除非你有收益率超过这个利率的投资渠道，否则提前还贷在数学上等价于锁定了一个确定的高收益。",[148,13585,13587],{"id":13586},"四套息交易逻辑越简单容错率越低","四、套息交易：逻辑越简单，容错率越低",[114,13589,13590],{},"套息交易的逻辑简单到一句话：借低息货币，买高息货币，吃利差。",[114,13592,13593],{},"和研究个股相比，它的\"变量数量\"少得多。研究一家公司要看财报、管理层、行业竞争、原材料价格、销售渠道，还要面对信息不对称。而套息交易的核心变量只有一个：两国国债收益率之差。只要这个差在，钱就会流，方向就确定。",[114,13595,13596,13597],{},"但金融市场有一个铁律：",[118,13598,13599],{},"越是逻辑直白的交易，赚起来越考验风控。",[114,13601,13602],{},"原因有三层：",[114,13604,13605,13608],{},[118,13606,13607],{},"杠杆放大波动。"," 套息交易的利差通常只有2-3个百分点，不加杠杆收益微薄。所以参与者几乎都用杠杆，10倍、50倍很常见。日元如果突然反向升值1%，10倍杠杆就是亏10%--吃两年的利差，一天亏完。波动再大就会穿仓，不光亏光本金，还倒欠券商。",[114,13610,13611,13614],{},[118,13612,13613],{},"逻辑透明导致踩踏。"," 所有人都知道\"借日元买美元赚利息\"，全球累积了数万亿美元的套利盘。一旦日本央行突然加息或出现黑天鹅事件，所有人同时平仓：卖掉美元资产，抢回日元还债。日元急速暴涨，套利者两边挨打--美元资产跌了，换回日元还的钱也变多了。这种踩踏在金融史上被称为\"寡妇制造者交易\"（widow-maker trade）。",[114,13616,13617,13620],{},[118,13618,13619],{},"赚的是息差，亏的是本金。"," 股票赚的是企业成长的钱，蛋糕在变大。套利赚的是\"定价偏差\"，市场平稳时像收租，但波动率一上来，那点利息根本不够覆盖汇率亏损。",[114,13622,13623,13624,13627],{},"简言之：",[118,13625,13626],{},"股票像种地--看天看地，收成慢，但地还在；汇率套利像走钢丝--路是直的，一眼看到头，但容错率极低。"," 简单不等于容易。正因为简单，它吸引的人更多，踩踏时也更惨烈。",[148,13629,13631],{"id":13630},"五汇率高不等于国力强","五、汇率高不等于国力强",[114,13633,13634],{},"一个常见的直觉误区：既然1美元能换163日元，那美元\"贵\"，说明美国比日本强。推而广之，哪个国家汇率最高，哪个国家国力最强。",[114,13636,13637],{},"这个推论不成立。科威特第纳尔1个能换约3.3美元，但没人会认为科威特国力是美国的3倍。英镑比美元\"贵\"，也不意味着英国比美国强。",[114,13639,13640],{},"汇率数字的高低，本质上是\"计量单位\"的差异。日元面值大，是因为日本历史上经历过严重通胀，货币单位被刻度得很大。这就像有的东西按克卖，有的按吨卖，不代表按克卖的东西更贵重。",[114,13642,13643],{},"评价国家强弱，看的是\"总购买力\"而非\"单价\"。按购买力平价（PPP）计算，中国的GDP已经超过美国，但人民币兑美元汇率才7点多。制造业大国--日本、德国、中国--没有一个是汇率最高的。高汇率国家往往是资源型经济体（科威特、挪威），产业结构单一，恰恰不是\"国力强\"的表现。",[114,13645,13646,13649],{},[118,13647,13648],{},"汇率反映的不是国力绝对值，而是全球资本对该国未来利息和通胀的预期。"," 对一个国家来说，汇率太高意味着出口商品变贵、竞争力下降。最有利的状态不是\"最高\"，而是\"稳定\"。",[148,13651,13653],{"id":13652},"六为什么人明知正确却选择错误","六、为什么人明知正确，却选择错误",[114,13655,13656],{},"所有长期逻辑都指向\"确定性的正确\"：定投指数、提前还贷、配置黄金、减少高息负债。但大多数散户依然选择追涨杀跌、加杠杆、炒短线。",[114,13658,13659],{},"为什么？",[114,13661,13662,13663,13666],{},"行为经济学给出的一个解释是",[118,13664,13665],{},"双曲贴现","（hyperbolic discounting）：人对\"近期的等待\"比\"远期的等待\"更不耐烦。现在给你100块还是明天给你110块？多数人选现在。365天后给你100块还是366天后给你110块？多数人愿意多等一天。同样是多等一天换10块，但\"现在\"这个时间节点会让人产生断崖式的厌恶。",[114,13668,13669],{},"双曲贴现的核心特征是\"时间不一致性\"：今天的自己和未来的自己偏好不同。今天你决定\"下个月开始存钱\"，但下个月到了，你又决定\"再等一个月\"。这种偏好反转，才是双曲贴现区别于普通\"时间偏好\"的关键。",[114,13671,13672,13673,13676],{},"但这个模型不能解释全部。还有一个更朴素的因素：",[118,13674,13675],{},"人的寿命有限，而\"长期正确\"需要的等待时间可能超过个人的时间预算。"," 一个40岁的人知道定投20年后会有可观回报，但60岁才兑现的收益，对他当下的房贷、孩子学费、父母医疗费毫无帮助。长期正确解决不了短期的现金流压力。",[114,13678,13679],{},"这不是不理性，而是一个约束条件下的理性选择。当流动性紧张时，\"长期最优\"让位于\"短期存活\"是合理的。问题出在：很多人即使不缺流动性，也依然无法执行长期策略。这才是双曲贴现真正起作用的领域--不是因为等不起，而是因为大脑天然不认\"20年后\"的账。",[114,13681,13682,13685],{},[118,13683,13684],{},"应对方式不是\"更自律\"，而是\"减少需要自律的场景\"。"," 自动扣款让钱在到手之前就被锁定，不给大脑的即时反应系统留出决策窗口。把\"20年后退休\"替换为\"给孩子存大学学费\"或\"55岁环游世界\"这样的具体画面，让远期目标变得可感知。这些不是心灵鸡汤，而是针对认知偏差的结构性对策。",[148,13687,13689],{"id":13688},"七制度作为人性的补丁","七、制度作为人性的补丁",[114,13691,13692],{},"如果个体的短视是结构性的，社会的应对也必须是结构性的。",[114,13694,13695],{},"中国的五险一金就是这样一个\"强制性纠偏系统\"。养老保险对抗\"等老了再存\"的拖延，医疗保险对抗\"我身体好\"的盲目，公积金对抗\"月光族\"的生存危机。国家知道你管不住未来的自己，所以在你年轻时强行没收你的\"短视权\"。",[114,13697,13698],{},"全球各国都在做类似的事，但路径不同：",[473,13700,13701,13707,13713,13719],{},[476,13702,13703,13706],{},[118,13704,13705],{},"新加坡","（中央公积金 CPF）：完全个人账户制，钱在自己名下，多存多得，回报率稳定在4%以上。本质是\"对自己负责\"。",[476,13708,13709,13712],{},[118,13710,13711],{},"德国、日本","（现收现付制）：年轻人缴费供养退休者，考验人口结构。本质是\"代际互助\"。",[476,13714,13715,13718],{},[118,13716,13717],{},"美国","（兜底制）：社保缴费比例仅12.4%（雇主雇员各6.2%），是主要国家中最低的，大头靠个人投资和401(k)。本质是\"基本兜底，自求多福\"。",[476,13720,13721,13724],{},[118,13722,13723],{},"中国","：混合体。社会统筹（现收现付）+ 个人账户（积累制），但个人账户长期空账运行--你缴的钱并没有真正存在你名下，而是被用来支付当代退休人员。",[114,13726,13727,13728],{},"这里有一个值得深思的矛盾。制度设计上写了\"你的钱在你账户里\"，实际上账户是空的。这与新加坡CPF的真金白银有本质区别。如果年轻人发现自己缴的钱并没有真正存下来，制度本身的信用就会受损，反而可能加剧短视行为--\"既然钱没存下来，不如现在就花掉\"。",[118,13729,13730],{},"制度对抗短视的有效性，最终取决于它能否维持\"代际契约\"的可信度。",[148,13732,13734],{"id":13733},"八普通人能做什么","八、普通人能做什么",[114,13736,13737],{},"在国内合规框架内，普通人想应对汇率波动，大致有这几条路：",[114,13739,13740,13743],{},[118,13741,13742],{},"QDII基金。"," 用人民币买基金，机构帮你换汇投资海外。人民币贬值时，海外资产换回人民币会多出一笔汇兑收益。门槛低，支付宝就能买。但约半数QDII限购，场内ETF溢价可能超20%，赎回到账需7-10天。",[114,13745,13746,13749],{},[118,13747,13748],{},"结构性存款。"," 保本浮动收益，收益挂钩汇率走势。判断对了拿高息，错了拿低息，但不亏本金。是境内最接近\"押注汇率方向\"的合规工具，通常1万人民币起。",[114,13751,13752,13755],{},[118,13753,13754],{},"黄金。"," 以美元计价，人民币贬值时国内金价通常上涨。适合配置5%-10%作为对冲。",[114,13757,13758,13761],{},[118,13759,13760],{},"外币定存。"," 直接换美元存定期，部分银行利率可达3%-3.6%。但致命风险是汇率反向：如果人民币升值幅度超过利差，利息会被完全吞噬甚至本金亏损。",[114,13763,13764,13767],{},[118,13765,13766],{},"港卡渠道。"," 持有香港银行卡可以接触更多产品（外币联系存款、跨境理财通、美元定存），但2026年开户门槛已大幅提高，且外汇保证金交易在内地仍属灰色地带。",[114,13769,13770,13771,13774],{},"核心原则：",[118,13772,13773],{},"用本来就要花的\"刚需外币\"去做，赚了是白捡，亏了也无所谓。"," 如果判断不了方向，最稳妥的\"套利\"是提前还掉高息负债--它等于一个保本保息、无汇率风险的确定收益。",[148,13776,13778],{"id":13777},"九金融的底层逻辑是时间","九、金融的底层逻辑是时间",[114,13780,13781],{},"回头看这条思考路径：从\"日元为什么跌\"到\"汇率是什么\"到\"货币是什么\"到\"利率谁定的\"到\"人为什么不按长期逻辑行动\"到\"国家如何对抗人性短视\"。每一步都是被\"为什么\"推着往下走的。",[114,13783,13784,13785],{},"这条路径揭示了一件事：",[118,13786,13787],{},"金融市场的底层逻辑只有两个字--\"时间\"。",[114,13789,13790],{},"汇率是两国未来的贴现。利率是人口和技术的影子价格。套息交易赚的是时间差，亏的是时间到了时的剧烈结算。双曲贴现是大脑对时间的非线性估值。五险一金是制度对个人时间偏好的强制纠正。",[114,13792,13793],{},"谁能忍受\"无聊\"和\"等待\"，谁就拿到了那笔被多数人放弃的确定性。这不是鸡汤，而是上述每一层逻辑共同指向的结论。",[114,13795,13796],{},[13449,13797,13798],{},"本文基于一段 AI 辅助对话的思考路径整理改写，涉及的数据和判断请以权威来源为准，不构成投资建议。",{"title":258,"searchDepth":303,"depth":303,"links":13800},[13801,13802,13803,13804,13805,13806,13807,13808,13809],{"id":13491,"depth":303,"text":13492},{"id":13514,"depth":303,"text":13515},{"id":13545,"depth":303,"text":13546},{"id":13586,"depth":303,"text":13587},{"id":13630,"depth":303,"text":13631},{"id":13652,"depth":303,"text":13653},{"id":13688,"depth":303,"text":13689},{"id":13733,"depth":303,"text":13734},{"id":13777,"depth":303,"text":13778},"2026-08-03","从一条日元贬值新闻出发，通过连续追问层层下探到货币本质、利率定价、行为金融和国家制度设计，展示金融概念表层与底层逻辑之间的认知路径。",{},"\u002Fwriting\u002F2026-08-03-yen-depreciation-cognition-drilldown","2026-08-03T22:38:00+08:00",{"title":13481,"description":13811},"yen-depreciation-cognition-drilldown","writing\u002F2026-08-03-yen-depreciation-cognition-drilldown",[13819,13820,13821,13822,13823,13665],"汇率","行为金融","货币","利率","套息交易","gJ3Au9UBjT_EEAz1wCHZCzAywJ-MpYBW_1phLVcvAJI",{"id":13826,"title":13827,"body":13828,"category":2624,"date":14253,"description":14254,"extension":2627,"hidden":2628,"kind":5123,"meta":14255,"navigation":30,"path":14256,"publishedAt":14257,"seo":14258,"series":5123,"slug":14259,"stem":14260,"tags":14261,"unit":5123,"updated":5123,"__hash__":14269},"writing\u002Fwriting\u002F2026-07-29-etf-premium-discount-cost-of-carry.md","从ETF到商品期货：申赎机制、折溢价与持有成本模型",{"type":111,"value":13829,"toc":14232},[13830,13834,13837,13840,13854,13857,13860,13863,13916,13919,13922,13936,13946,13949,13963,13966,13970,13973,13980,13983,14006,14009,14012,14032,14035,14046,14049,14052,14056,14059,14062,14071,14074,14099,14105,14108,14113,14116,14120,14171,14174,14194,14197,14211,14214,14220,14223,14230],[148,13831,13833],{"id":13832},"etf与lof申赎机制决定折溢价","ETF与LOF：申赎机制决定折溢价",[161,13835,13836],{"id":13836},"底层机制差异",[114,13838,13839],{},"ETF和LOF虽然都是场内可交易的开放式基金，但申赎机制截然不同：",[473,13841,13842,13848],{},[476,13843,13844,13847],{},[118,13845,13846],{},"ETF：实物申赎，T+0瞬时套利。"," 机构投资者用一篮子股票换取基金份额，最小申赎单位通常为50万或100万份，门槛在百万级别。每日盘前基金公司公布PCF（Portfolio Composition File）清单，列明当日申赎对应的一篮子股票明细。",[476,13849,13850,13853],{},[118,13851,13852],{},"LOF：现金申赎，跨系统转托管需T+2日。"," 门槛低（1份起），普通投资者即可参与，但套利存在时滞和净值波动风险。",[114,13855,13856],{},"这个差异是两者折溢价收敛速度天差地别的根本原因。",[161,13858,13859],{"id":13859},"现金替代的四种标志",[114,13861,13862],{},"ETF实物申赎中，并非每只成分股都能完美交收。PCF清单对每只股票标注现金替代标志：",[166,13864,13865,13874],{},[169,13866,13867],{},[172,13868,13869,13872],{},[175,13870,13871],{},"标志",[175,13873,8807],{},[185,13875,13876,13886,13896,13906],{},[172,13877,13878,13883],{},[190,13879,13880],{},[118,13881,13882],{},"禁止",[190,13884,13885],{},"必须用股票实物交收",[172,13887,13888,13893],{},[190,13889,13890],{},[118,13891,13892],{},"允许",[190,13894,13895],{},"申购可现金替代，赎回必须给股票，多退少补",[172,13897,13898,13903],{},[190,13899,13900],{},[118,13901,13902],{},"必须",[190,13904,13905],{},"申赎均必须用现金，针对停牌或待剔除股票",[172,13907,13908,13913],{},[190,13909,13910],{},[118,13911,13912],{},"退补",[190,13914,13915],{},"必须现金替代，按基金公司实际买卖成本多退少补",[161,13917,13918],{"id":13918},"折溢价的形成",[114,13920,13921],{},"场内基金有两个价格：",[473,13923,13924,13930],{},[476,13925,13926,13929],{},[118,13927,13928],{},"IOPV（参考净值）","：实时计算的基金份额参考价值",[476,13931,13932,13935],{},[118,13933,13934],{},"市价","：二级市场交易价格",[114,13937,13938,13939,13942,13943,1032],{},"当市价 > IOPV，即为",[118,13940,13941],{},"溢价","；反之则为",[118,13944,13945],{},"折价",[114,13947,13948],{},"套利机制是折溢价收敛的驱动力：",[473,13950,13951,13957],{},[476,13952,13953,13956],{},[118,13954,13955],{},"溢价套利","：T日买入一篮子股票 → 申购ETF份额 → 卖出ETF，全流程T+0完成",[476,13958,13959,13962],{},[118,13960,13961],{},"折价套利","：T日买入ETF → 赎回为一篮子股票 → 卖出股票，同样T+0",[114,13964,13965],{},"正因为ETF套利链条畅通，折溢价通常控制在1%以内。而LOF因T+2转托管时滞，套利者需承担两天净值波动风险，折溢价常达5%以上。",[148,13967,13969],{"id":13968},"跨境etf全现金替代与qdii额度约束","跨境ETF：全现金替代与QDII额度约束",[161,13971,13972],{"id":13972},"一个常见误区",[114,13974,13975,13976,13979],{},"\"我有了一篮子股票，如何申购跨境ETF？\"——这个问题本身存在误区。跨境ETF的成分股在境外，境内投资者无法持有和交收这些股票，因此",[118,13977,13978],{},"跨境ETF不能实物申购，只能全现金替代","。投资者交付现金，由基金公司代为买入境外成分股。",[161,13981,13982],{"id":13982},"现金申购流程",[770,13984,13985,13988,13991,13994,13997,14000,14003],{},[476,13986,13987],{},"确认券商具备跨境ETF申赎资格",[476,13989,13990],{},"T日盘前查阅PCF清单（现金替代标志全为\"退补\"）",[476,13992,13993],{},"核对当日QDII额度与开放状态（可能限购，极端情况单日仅100元）",[476,13995,13996],{},"计算所需现金（现金替代款 + 预估现金差额），最小单位50万份",[476,13998,13999],{},"T日交易时段通过券商端提交委托",[476,14001,14002],{},"T+1日日终份额确认，T+2日可用",[476,14004,14005],{},"基金公司代买完成后，进行现金差额多退少补",[161,14007,14008],{"id":14008},"三层结构与溢价固化",[114,14010,14011],{},"跨境ETF的运作涉及三层主体：",[473,14013,14014,14020,14026],{},[476,14015,14016,14019],{},[118,14017,14018],{},"外汇局","：审批QDII额度（总量约1708亿美元）",[476,14021,14022,14025],{},[118,14023,14024],{},"基金公司","：在额度内换汇，代投资者买入境外成分股",[476,14027,14028,14031],{},[118,14029,14030],{},"境内投资者","：只能通过现金提交申购申请",[114,14033,14034],{},"当额度充裕时，套利机制正常运行。但当额度耗尽时：",[770,14036,14037,14040,14043],{},[476,14038,14039],{},"基金公司暂停或限额申购（单日低至10元）",[476,14041,14042],{},"一级市场申购通道堵死，套利者无法创造新份额",[476,14044,14045],{},"二级市场资金只能抢存量份额，溢价飙升至10%-50%+",[114,14047,14048],{},"此外，现金申赎 + 代买模式下，跨时区汇率波动和境外开盘价差导致退补款不可预知，套利收益无法锁定，进一步抑制套利者进场意愿，固化高溢价。",[114,14050,14051],{},"典型场景：QDII ETF因外汇额度受限，一级市场申购通道关闭，套利链条断裂，溢价长期高达10%-40%。",[148,14053,14055],{"id":14054},"商品期货持有成本模型与期限结构","商品期货：持有成本模型与期限结构",[161,14057,14058],{"id":14058},"持有成本定价公式",[114,14060,14061],{},"商品期货的理论价格由持有成本模型决定：",[250,14063,14065],{"className":9555,"code":14064,"language":9557,"meta":258,"style":258},"F = S · e^((r + u − y) · T)\n",[60,14066,14067],{"__ignoreMap":258},[294,14068,14069],{"class":296,"line":297},[294,14070,14064],{},[114,14072,14073],{},"其中：",[473,14075,14076,14082,14088,14094],{},[476,14077,14078,14081],{},[118,14079,14080],{},"S","：现货价格",[476,14083,14084,14087],{},[118,14085,14086],{},"r","：无风险利率（资金占用成本）",[476,14089,14090,14093],{},[118,14091,14092],{},"u","：仓储保险成本率",[476,14095,14096,14098],{},[118,14097,1065],{},"：便利收益率（convenience yield）",[114,14100,14101,14104],{},[118,14102,14103],{},"便利收益","是持有实物库存的隐性收益——当供应紧张时，手上有货意味着生产不断线、客户不流失，这种\"随时可用\"的价值就是便利收益。它与库存水平成反比：库存越低，便利收益越高。",[161,14106,14107],{"id":14107},"基差及其收敛",[114,14109,14110],{},[118,14111,14112],{},"基差 = 现货价格(S) − 期货价格(F)",[114,14114,14115],{},"随着交割日临近，持仓成本（r+u−y）T逐渐衰减趋零，期货价格向现货价格收敛，基差必然趋近于零。否则将触发无风险套利：买低卖高，交割平仓。",[161,14117,14119],{"id":14118},"期限结构contango与backwardation","期限结构：Contango与Backwardation",[166,14121,14122,14137],{},[169,14123,14124],{},[172,14125,14126,14129,14132,14135],{},[175,14127,14128],{},"结构",[175,14130,14131],{},"形态",[175,14133,14134],{},"成因",[175,14136,8807],{},[185,14138,14139,14155],{},[172,14140,14141,14146,14149,14152],{},[190,14142,14143],{},[118,14144,14145],{},"Contango",[190,14147,14148],{},"S \u003C F近 \u003C F远",[190,14150,14151],{},"r+u > y，库存充足",[190,14153,14154],{},"常态结构，持有成本主导",[172,14156,14157,14162,14165,14168],{},[190,14158,14159],{},[118,14160,14161],{},"Backwardation",[190,14163,14164],{},"S > F近 > F远",[190,14166,14167],{},"r+u \u003C y，库存紧张",[190,14169,14170],{},"供应短缺，现货溢价",[161,14172,14173],{"id":14173},"品种实例",[473,14175,14176,14182,14188],{},[476,14177,14178,14181],{},[118,14179,14180],{},"黄金","：投资性商品，便利收益y≈0，恒为Contango结构",[476,14183,14184,14187],{},[118,14185,14186],{},"原油","：消费性商品，便利收益波动剧烈，常在Contango与Backwardation间切换。紧缺时深度Backwardation，过剩时转Contango",[476,14189,14190,14193],{},[118,14191,14192],{},"农产品","：受季节性收获周期影响，仓储成本u与便利收益y交替主导曲线形态",[161,14195,14196],{"id":14196},"套利操作",[473,14198,14199,14205],{},[476,14200,14201,14204],{},[118,14202,14203],{},"正向套利（Cash and Carry）","：当F实际 > F理论时，借钱买入现货 + 卖出期货，锁定差价",[476,14206,14207,14210],{},[118,14208,14209],{},"反向套利（Reverse Cash and Carry）","：当F实际 \u003C F理论时，卖空现货 + 买入期货。但对消费性商品，卖空现货难以操作",[161,14212,14213],{"id":14213},"远期与期货的凸性调整",[114,14215,14216,14217,1032],{},"期货合约每日盯市（mark-to-market），而远期合约到期才结算。当标的资产价格与利率正相关时，期货价格略高于远期价格，这一差异称为",[118,14218,14219],{},"凸性调整",[148,14221,14222],{"id":14222},"串联",[114,14224,14225,14226,14229],{},"三个主题看似分散，实则共享一条逻辑主线：",[118,14227,14228],{},"机制决定效率，效率决定价格形态","。ETF的T+0实物申赎使折溢价瞬间收敛；跨境ETF的额度瓶颈使套利链条断裂、溢价固化；商品期货的持有成本模型则将仓储、利率、库存预期统一为一个数学框架，解释了曲线形态的成因。理解机制本身，比记住某个具体结论更重要。",[2580,14231,7308],{},{"title":258,"searchDepth":303,"depth":303,"links":14233},[14234,14239,14244,14252],{"id":13832,"depth":303,"text":13833,"children":14235},[14236,14237,14238],{"id":13836,"depth":34,"text":13836},{"id":13859,"depth":34,"text":13859},{"id":13918,"depth":34,"text":13918},{"id":13968,"depth":303,"text":13969,"children":14240},[14241,14242,14243],{"id":13972,"depth":34,"text":13972},{"id":13982,"depth":34,"text":13982},{"id":14008,"depth":34,"text":14008},{"id":14054,"depth":303,"text":14055,"children":14245},[14246,14247,14248,14249,14250,14251],{"id":14058,"depth":34,"text":14058},{"id":14107,"depth":34,"text":14107},{"id":14118,"depth":34,"text":14119},{"id":14173,"depth":34,"text":14173},{"id":14196,"depth":34,"text":14196},{"id":14213,"depth":34,"text":14213},{"id":14222,"depth":303,"text":14222},"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":13827,"description":14254},"etf-premium-discount-cost-of-carry","writing\u002F2026-07-29-etf-premium-discount-cost-of-carry",[12996,14262,14263,14264,14265,14266,14267,14268],"LOF","跨境ETF","QDII","商品期货","持有成本模型","期限结构","折溢价","j8ZREl2DLHpvRrQexWCwAs9Df03Dloc_cBM9d85BgaM",{"id":14271,"title":14272,"body":14273,"category":2624,"date":17678,"description":17679,"extension":2627,"hidden":2628,"kind":5123,"meta":17680,"navigation":30,"path":17681,"publishedAt":17682,"seo":17683,"series":5123,"slug":17684,"stem":17685,"tags":17686,"unit":5123,"updated":17691,"__hash__":17692},"writing\u002Fwriting\u002F2026-07-26-quilibra-complete-technical-handbook.md","Quilibra 个人主页完整技术手册：从页面架构到内容发布与生产部署",{"type":111,"value":14274,"toc":17606},[14275,14278,14281,14300,14304,14307,14310,14316,14326,14340,14343,14347,14350,14370,14373,14376,14380,14383,14477,14480,14486,14492,14508,14512,14515,14521,14524,14556,14570,14574,14580,14615,14633,14644,14650,14694,14701,14705,14708,14772,14791,14798,14833,14846,14850,14856,14891,14909,14944,14947,14962,14965,14969,14972,14995,15014,15033,15036,15040,15043,15071,15074,15078,15085,15140,15151,15154,15180,15183,15189,15195,15199,15210,15216,15223,15233,15256,15260,15270,15273,15292,15295,15334,15340,15344,15355,15358,15361,15375,15396,15402,15426,15436,15440,15451,15454,15481,15492,15495,15510,15514,15517,15533,15536,15542,15556,15563,15581,15585,15594,15601,15604,15618,15632,15639,15657,15661,15672,15678,15686,15692,15707,15726,15733,15737,15740,15821,15824,15830,15857,15880,15893,15903,15907,15916,15919,15941,15944,15952,15958,15964,15967,15974,15988,15992,15999,16002,16025,16044,16047,16051,16058,16064,16073,16086,16090,16093,16099,16102,16108,16111,16117,16120,16126,16132,16138,16144,16150,16182,16192,16198,16222,16225,16244,16248,16254,16260,16273,16290,16299,16309,16312,16318,16322,16358,16423,16429,16432,16438,16449,16460,16464,16528,16537,16540,16544,16547,16553,16560,16563,16605,16621,16632,16643,16647,16650,16654,16660,16691,16695,16701,16735,16739,16745,16798,16801,16804,16808,16811,16828,16835,16838,16841,16882,16887,16891,16894,16901,16918,16921,16925,16929,16938,16941,16950,16953,16961,16964,16981,16984,16997,17000,17007,17010,17024,17027,17036,17039,17045,17049,17053,17070,17073,17076,17101,17106,17110,17154,17165,17168,17240,17243,17246,17274,17285,17289,17293,17316,17320,17331,17334,17342,17345,17351,17355,17365,17369,17378,17382,17388,17392,17395,17401,17404,17410,17420,17424,17427,17430,17433,17436,17439,17445,17448,17452,17455,17517,17520,17540,17543,17545,17603],[114,14276,14277],{},"这是一份给自己看的项目说明书。它主要以 2026 年 9 月 4 日的实际代码和 VPS 配置为准，从浏览器里看到的页面一直追到内容文件、Git 分支、构建产物、静态服务器和版本化 release。以后忘了某段代码为什么存在、主页颜色为什么能保存、一次 CMS 保存如何自动变成线上新版本，或者 3000、3100、3110 几个端口分别在做什么，可以从这里重新建立完整认识。",[114,14279,14280],{},"本文记录的是一个具体版本的实现，不是一套永远不变的规范。最可靠的事实来源始终是仓库中的代码；本文的作用，是把分散在 Vue、CSS、YAML、脚本和运维文件里的设计意图串起来。",[7389,14282,14283],{},[114,14284,14285,14286,14289,14290,14293,14294,14296,14297,1032],{},"2026 年 9 月 4 日更新：8 月 3 日起网站移除长期 staging 分支、远程测试站和 ",[60,14287,14288],{},"promote"," 流程，全站改为 ",[60,14291,14292],{},"master"," 单分支：CMS 直接提交 ",[60,14295,14292],{},"，Webhook 自动构建部署，纯内容提交走快速构建。此后新增了文章列表状态与阅读进度恢复、移动端返回与回顶按钮、平滑标题锚点和文章表格样式；8 月 26 日曾试验文章 TOC 与自托管统计，当天整体回退；9 月 2 日仓库迁移至 ",[60,14298,14299],{},"XXXCH-siu\u002Fpersonal-site",[148,14301,14303],{"id":14302},"一先用一句话理解整个网站","一、先用一句话理解整个网站",[114,14305,14306],{},"Quilibra（名字也是AI起的捏） 是一个由 Nuxt 生成的静态个人网站：页面结构写在 Vue 中，主页设置写在 YAML 中，文章写在 Markdown 中；发布时把这些源文件编译成普通 HTML、CSS、JavaScript 和图片，再由一个很小的 Node 静态服务器提供给浏览器。",[114,14308,14309],{},"它的静态生成核心是：",[250,14311,14314],{"className":14312,"code":14313,"language":255,"meta":258},[253],"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,14315,14313],{"__ignoreMap":258},[114,14317,14318,14319,14321,14322,14325],{},"现在这套链路只有一个生产实例：",[60,14320,14292],{}," 构建到 ",[60,14323,14324],{},"~\u002F.local\u002Fshare\u002Fpersonal-site\u002Fcurrent","，由 3000 提供正式站。本地开发和 Playwright 测试会临时占用 3100、3101，但它们不常驻，也没有独立的 release。2026 年 8 月 3 日之前，这里曾有第二个 staging 实例和独立的测试域名，双环境简化后彻底删除。",[114,14327,14328,14329],{},"这里最重要的边界是：",[118,14330,14331,14332,14335,14336,14339],{},"Git 分支中的源码是长期事实，",[60,14333,14334],{},".output\u002Fpublic"," 是可重新生成的临时产物，",[60,14337,14338],{},"current"," 指向的 release 才是线上真正提供的版本。",[114,14341,14342],{},"因此，修改 Markdown 或 YAML 只改变源码；只有重新生成并切换 release，线上页面才会改变。网站没有生产数据库，也没有在访问文章时临时渲染 Markdown。",[148,14344,14346],{"id":14345},"二为什么选择静态优先","二、为什么选择静态优先",[114,14348,14349],{},"个人主页的读取远多于写入，而且文章、简介和服务入口都不需要按访客实时计算。静态生成正好适合这种负载：",[473,14351,14352,14355,14358,14361,14364,14367],{},[476,14353,14354],{},"访问时不查询数据库，也不运行 Vue 服务端渲染逻辑；",[476,14356,14357],{},"HTML 已经在发布阶段生成，首屏可以直接返回完整内容；",[476,14359,14360],{},"生产服务只需要读取文件，故障面比常驻 Nuxt 服务小；",[476,14362,14363],{},"每个 release 都是一份独立、完整、可回滚的目录；",[476,14365,14366],{},"Markdown 和 YAML 都在 Git 中，内容、配置和代码可以一起审查与恢复；",[476,14368,14369],{},"CMS 只是源码编辑器，不会成为网站运行时依赖。",[114,14371,14372],{},"代价也很明确：每次内容变化都要重新生成站点；文章越多，生成时间越长；需要登录、评论、实时状态或用户数据时，必须接入独立服务，而不能假装静态文件可以承担动态业务。",[114,14374,14375],{},"当前项目主动接受这个取舍，因为稳定、透明和容易备份比“保存后数据库立刻生效”更重要。",[148,14377,14379],{"id":14378},"三技术栈与各自职责","三、技术栈与各自职责",[114,14381,14382],{},"当前主要依赖如下：",[166,14384,14385,14395],{},[169,14386,14387],{},[172,14388,14389,14392],{},[175,14390,14391],{},"技术",[175,14393,14394],{},"当前职责",[185,14396,14397,14405,14413,14421,14429,14437,14445,14453,14461,14469],{},[172,14398,14399,14402],{},[190,14400,14401],{},"Nuxt 4",[190,14403,14404],{},"应用框架、路由、预渲染、页面头信息和构建产物",[172,14406,14407,14410],{},[190,14408,14409],{},"Vue 3",[190,14411,14412],{},"组件、响应式状态、生命周期和浏览器交互",[172,14414,14415,14418],{},[190,14416,14417],{},"TypeScript",[190,14419,14420],{},"页面、组件、配置、内容脚本和测试的静态类型检查",[172,14422,14423,14426],{},[190,14424,14425],{},"Nuxt Content 3",[190,14427,14428],{},"读取 YAML\u002FMarkdown、建立集合、查询文章并渲染正文",[172,14430,14431,14434],{},[190,14432,14433],{},"Zod",[190,14435,14436],{},"约束站点设置与文章字段",[172,14438,14439,14442],{},[190,14440,14441],{},"Lucide Vue",[190,14443,14444],{},"返回、搜索、服务、调色等界面图标",[172,14446,14447,14450],{},[190,14448,14449],{},"原生 CSS",[190,14451,14452],{},"全部视觉、响应式布局、悬浮反馈和字标动画",[172,14454,14455,14458],{},[190,14456,14457],{},"Sveltia CMS",[190,14459,14460],{},"在浏览器中编辑仓库里的 YAML、Markdown 和上传资源",[172,14462,14463,14466],{},[190,14464,14465],{},"Playwright",[190,14467,14468],{},"真实浏览器中的交互、布局、响应式和回归测试",[172,14470,14471,14474],{},[190,14472,14473],{},"Node.js",[190,14475,14476],{},"内容工具、同步与发布脚本、生产和测试静态服务器",[114,14478,14479],{},"没有引入 Tailwind、组件库、动画框架、运行时数据库或外部字体。页面使用系统无衬线字体和系统等宽字体回退，这减少了字体请求，也避免了字体加载完成时重新排版。",[114,14481,14482,14485],{},[60,14483,14484],{},"package.json"," 中最常用的入口是：",[250,14487,14490],{"className":14488,"code":14489,"language":255,"meta":258},[253],"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,14491,14489],{"__ignoreMap":258},[114,14493,14494,2545,14497,14500,14501,14504,14505,14507],{},[60,14495,14496],{},"build",[60,14498,14499],{},"preview"," 仍是 Nuxt 的常规命令，但当前生产链路以 ",[60,14502,14503],{},"generate"," 和自有静态服务器为准。2026 年 8 月 3 日移除双环境时，",[60,14506,14288],{}," 命令随 staging 一起删除。",[148,14509,14511],{"id":14510},"四目录结构与所有权边界","四、目录结构与所有权边界",[114,14513,14514],{},"理解这个项目，先要知道“改什么就去哪里”。",[250,14516,14519],{"className":14517,"code":14518,"language":255,"meta":258},[253],"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,14520,14518],{"__ignoreMap":258},[114,14522,14523],{},"可以把这些文件分成四层：",[770,14525,14526,14532,14538,14547],{},[476,14527,14528,14531],{},[60,14529,14530],{},"content\u002F"," 是可频繁修改的数据层。",[476,14533,14534,14537],{},[60,14535,14536],{},"app\u002F"," 是页面表现和交互层。",[476,14539,14540,2545,14543,14546],{},[60,14541,14542],{},"scripts\u002F",[60,14544,14545],{},"ops\u002F"," 是发布和运行层。",[476,14548,14549,1332,14552,14555],{},[60,14550,14551],{},".output\u002F",[60,14553,14554],{},".nuxt*"," 是构建缓存或产物，不是手工维护的源码。",[114,14557,14558,14559,14562,14563,1098,14566,14569],{},"CMS 默认只管理 ",[60,14560,14561],{},"content\u002Fsettings\u002F"," 中五份固定 YAML、",[60,14564,14565],{},"content\u002Fwriting\u002F",[60,14567,14568],{},"public\u002Fuploads\u002F","。Vue、CSS、CMS 配置、脚本或测试发生变化时，应当视为代码变更，走完整代码部署流程；设置目录中的未知文件也不会自动进入内容发布白名单。",[148,14571,14573],{"id":14572},"五nuxt-配置与构建数据库","五、Nuxt 配置与构建数据库",[114,14575,14576,14579],{},[60,14577,14578],{},"nuxt.config.ts"," 做了几件关键的事：",[473,14581,14582,14588,14594,14597,14603,14609,14612],{},[476,14583,14584,14585,2528],{},"加载 ",[60,14586,14587],{},"@nuxt\u002Fcontent",[476,14589,14590,14591,2528],{},"引入 Cormorant Garamond 字标字体和 ",[60,14592,14593],{},"app\u002Fassets\u002Fcss\u002Fmain.css",[476,14595,14596],{},"关闭开发工具；",[476,14598,14599,14600,2528],{},"设置中文页面语言、移动端 viewport 和浅色 ",[60,14601,14602],{},"color-scheme",[476,14604,6588,14605,14608],{},[60,14606,14607],{},"out-in"," 页面切换过渡；",[476,14610,14611],{},"允许 Nitro 爬取内部链接并预渲染，遇到错误时直接让构建失败；",[476,14613,14614],{},"把 Nuxt Content 的本地数据库放到与构建目录对应的位置。",[114,14616,14617,14618,1098,14621,14624,14625,14628,14629,14632],{},"Nuxt Content 3 在构建过程中会建立 SQLite 内容索引。开发环境默认使用 ",[60,14619,14620],{},".nuxt",[60,14622,14623],{},".data\u002Fcontent\u002Fcontents.sqlite","；release 构建传入 ",[60,14626,14627],{},"NUXT_BUILD_DIR=.nuxt-release","，E2E 使用 ",[60,14630,14631],{},".nuxt-e2e","。把三种构建目录分开，是为了避免开发服务器、正式生成和 Playwright 同时读写同一个内容缓存。",[114,14634,14635,14636,14639,14640,14643],{},"这里的 SQLite 是",[118,14637,14638],{},"构建时索引","，不是生产文章数据库。它帮助 Nuxt Content 查询 Markdown；静态页面生成完成后，生产服务器只负责输出构建结果。遇到 ",[60,14641,14642],{},"no such table: _content_writing"," 时，通常是本地 Nuxt Content 缓存被并发构建或中断破坏，不代表文章源文件丢失。",[114,14645,14646,14649],{},[60,14647,14648],{},"content.config.ts"," 定义了六个集合：",[473,14651,14652,14674],{},[476,14653,14654,1332,14657,1332,14660,1332,14663,1332,14666,14669,14670,14673],{},[60,14655,14656],{},"site",[60,14658,14659],{},"homeSettings",[60,14661,14662],{},"writingSettings",[60,14664,14665],{},"servicesSettings",[60,14667,14668],{},"aboutSettings","：五个 ",[60,14671,14672],{},"data"," 集合，各自读取同名职责的 YAML；",[476,14675,14676,133,14679,14682,14683,14686,14687,14689,14690,14693],{},[60,14677,14678],{},"writing",[60,14680,14681],{},"page"," 类型，读取 ",[60,14684,14685],{},"writing\u002F**\u002F*.md","，因此每篇文章会拥有可路由的 ",[60,14688,1514],{}," 和可供 ",[60,14691,14692],{},"ContentRenderer"," 使用的正文。",[114,14695,14696,14697,14700],{},"E2E 测试通过 ",[60,14698,14699],{},"NUXT_CONTENT_CWD=tests\u002Ffixtures\u002Fcontent"," 把文章集合切换到固定 fixture，测试不会依赖真实文章的标题和数量，也不会改动生产内容。",[148,14702,14704],{"id":14703},"六站点设置五份-yaml-如何保持页面边界","六、站点设置：五份 YAML 如何保持页面边界",[114,14706,14707],{},"站点设置按后台入口拆成五份文件：",[166,14709,14710,14720],{},[169,14711,14712],{},[172,14713,14714,14717],{},[175,14715,14716],{},"文件",[175,14718,14719],{},"管理内容",[185,14721,14722,14732,14742,14752,14762],{},[172,14723,14724,14729],{},[190,14725,14726],{},[60,14727,14728],{},"site.yml",[190,14730,14731],{},"站点名称、浏览器标题、SEO、位置、默认主题色、备案和署名",[172,14733,14734,14739],{},[190,14735,14736],{},[60,14737,14738],{},"home.yml",[190,14740,14741],{},"首页介绍、标题、最近文章、服务矩阵、RGB 模块和底部入口",[172,14743,14744,14749],{},[190,14745,14746],{},[60,14747,14748],{},"writing.yml",[190,14750,14751],{},"文章列表页介绍、SEO 和空状态",[172,14753,14754,14759],{},[190,14755,14756],{},[60,14757,14758],{},"services.yml",[190,14760,14761],{},"服务列表、状态、图标、主页显示和访问说明",[172,14763,14764,14769],{},[190,14765,14766],{},[60,14767,14768],{},"about.yml",[190,14770,14771],{},"关于页介绍、当前状态、关注主题和联系文案",[114,14773,14774,14775,14778,14779,14782,14783,14786,14787,14790],{},"拆分不只是 CMS 菜单分组。如果多个表单只声明同一 YAML 的部分字段，保存其中一个表单可能覆盖另一个表单未声明的字段；因此每个入口必须拥有独立源文件。",[60,14776,14777],{},"useSiteSettings()"," 使用固定异步数据键 ",[60,14780,14781],{},"site-settings","，通过 ",[60,14784,14785],{},"Promise.all"," 并行查询五个 data collection，再返回结构化的 ",[60,14788,14789],{},"site\u002Fhome\u002Fwriting\u002Fservices\u002Fabout"," 对象。任何一份缺失都会立即抛出 500，而不是让页面带着部分设置运行。",[114,14792,14793,14794,14797],{},"这些配置不是任意 YAML。",[60,14795,14796],{},"content.schema.ts"," 为每个文件提供独立严格 Schema，例如：",[473,14799,14800,14806,14809,14812,14821,14824,14830],{},[476,14801,14802,14803,2528],{},"默认强调色必须是 ",[60,14804,14805],{},"#RRGGBB",[476,14807,14808],{},"最新文章数量只能是 1 到 3；",[476,14810,14811],{},"服务数量只能是 1 到 12；",[476,14813,14814,14815,1332,14817,1332,14819,2528],{},"服务状态只能是 ",[60,14816,59],{},[60,14818,71],{},[60,14820,65],{},[476,14822,14823],{},"图标只能使用代码中已有的枚举；",[476,14825,14826,14827,14829],{},"服务链接只能是 ",[60,14828,76],{}," 或完整的 HTTPS URL；",[476,14831,14832],{},"对象使用严格模式，拼错字段不会被静默忽略。",[114,14834,14835,14838,14839,14841,14842,14845],{},[60,14836,14837],{},"public\u002Fadmin\u002Fconfig.yml"," 的表单选项、",[60,14840,14796],{}," 的运行时约束、",[60,14843,14844],{},"app\u002Fconfig\u002Fsite.ts"," 的 TypeScript 枚举应该同步修改。只改 CMS 下拉框，不改 Schema，保存后会校验失败；只改 Schema，不改组件映射，界面可能没有正确图标。",[148,14847,14849],{"id":14848},"七全站外壳与水合边界","七、全站外壳与水合边界",[114,14851,14852,14855],{},[60,14853,14854],{},"app\u002Fapp.vue"," 是每个路由外面的共同外壳。它负责：",[473,14857,14858,14861,14864,14867,14873,14876,14879,14885,14888],{},[476,14859,14860],{},"在页面加载时取得站点设置；",[476,14862,14863],{},"判断当前是否为主页或文章详情页；",[476,14865,14866],{},"初始化和恢复主题色；",[476,14868,14869,14870,2528],{},"把主题 CSS 变量挂到 ",[60,14871,14872],{},".site-shell",[476,14874,14875],{},"为非主页显示上下文相关的“返回”按钮与主题预设切换按钮；",[476,14877,14878],{},"为非主页在滚动超过阈值时显示“回到顶部”按钮；",[476,14880,14881,14882,2528],{},"设置标题模板、站点描述、Open Graph 信息和 ",[60,14883,14884],{},"theme-color",[476,14886,14887],{},"根据当前强调色实时生成 data URL favicon；",[476,14889,14890],{},"显示 Nuxt 页面加载进度条。",[114,14892,14893,14896,14897,14900,14901,14904,14905,14908],{},[60,14894,14895],{},"hydrated"," 初始为 ",[60,14898,14899],{},"false","，在 ",[60,14902,14903],{},"onMounted"," 完成主题恢复和本地监听注册后变成 ",[60,14906,14907],{},"true","，最终反映为：",[250,14910,14914],{"className":14911,"code":14912,"language":14913,"meta":258,"style":258},"language-html shiki shiki-themes github-light github-dark","\u003Cdiv class=\"site-shell\" data-hydrated=\"true\">\n","html",[60,14915,14916],{"__ignoreMap":258},[294,14917,14918,14921,14925,14928,14930,14933,14936,14938,14941],{"class":296,"line":297},[294,14919,9499],{"class":14920},"sVt8B",[294,14922,14924],{"class":14923},"s9eBZ","div",[294,14926,14927],{"class":2356}," class",[294,14929,11303],{"class":14920},[294,14931,14932],{"class":2360},"\"site-shell\"",[294,14934,14935],{"class":2356}," data-hydrated",[294,14937,11303],{"class":14920},[294,14939,14940],{"class":2360},"\"true\"",[294,14942,14943],{"class":14920},">\n",[114,14945,14946],{},"这个属性既是测试可观察的“客户端已经接管页面”标志，也是字标动画的播放闸门。CSS 动画先建立，但在页面完成水合前保持暂停，防止服务端 HTML 刚显示就消耗掉一段动画，等 JavaScript 接管时只剩最终帧。",[114,14948,14949,14950,14953,14954,14957,14958,14961],{},"非主页的全局返回入口是上下文相关的：在文章详情页显示“文章”并返回 ",[60,14951,14952],{},"\u002Fwriting","，在其他页面显示“返回”并回到 ",[60,14955,14956],{},"\u002F","，图标和 ",[60,14959,14960],{},"aria-label"," 补充方向与完整语义。它与文章内部的标题锚点属于不同层级：前者是全局导航，后者是页面内跳转。",[114,14963,14964],{},"滚动超过 360 像素（视口宽度不超过 720 像素时是 200 像素）后，非主页会出现一个“回到顶部”按钮，点击后平滑滚回顶部，并在系统开启减少动态效果时退回瞬间跳转；路由切换时它立即隐藏，避免新页面继承旧滚动状态。",[148,14966,14968],{"id":14967},"八主页是一张单屏工作台","八、主页是一张单屏工作台",[114,14970,14971],{},"主页不是按多个营销区块向下滚动，而是一张固定在一个视口高度内的工作台。核心区域包括：",[473,14973,14974,14977,14980,14983,14986,14989,14992],{},[476,14975,14976],{},"顶部站点标识、上海时区时钟、指针坐标和主题按钮；",[476,14978,14979],{},"左上介绍、普通标题前缀与 Quilibra 字标；",[476,14981,14982],{},"右上服务矩阵；",[476,14984,14985],{},"左下最近三篇文章；",[476,14987,14988],{},"右下 RGB 调色器；",[476,14990,14991],{},"底部中央文章、服务、关于三个入口；",[476,14993,14994],{},"两侧备案与署名。",[114,14996,14997,14998,15001,15002,15005,15006,15009,15010,15013],{},"首页在 ",[60,14999,15000],{},"setup"," 阶段并行取得站点设置和最近文章。文章查询按 ",[60,15003,15004],{},"date DESC"," 排序，再按 YAML 中的 ",[60,15007,15008],{},"limit"," 截取。服务矩阵先筛选 ",[60,15011,15012],{},"showOnHome: true","，按 YAML 顺序最多取四个；服务页仍然展示完整列表。",[114,15015,15016,15017,15020,15021,15024,15025,15028,15029,15032],{},"顶部时钟通过 ",[60,15018,15019],{},"Intl.DateTimeFormat"," 固定使用 ",[60,15022,15023],{},"Asia\u002FShanghai","，每 30 秒更新一次。指针坐标不是每次 ",[60,15026,15027],{},"pointermove"," 都直接改 DOM：事件只保存最新坐标，然后在下一帧 ",[60,15030,15031],{},"requestAnimationFrame"," 中统一计算。元素边界在挂载与窗口缩放时缓存，触摸指针被忽略。这样可以减少高频事件中的布局读取和响应式更新。",[114,15034,15035],{},"工作台没有为了装饰引入 canvas 或 WebGL。网格、面板、条纹、阴影和入场效果全部由 CSS 完成，浏览器可以直接合成绝大多数变换。",[148,15037,15039],{"id":15038},"九响应式布局为何不只是按比例缩小","九、响应式布局为何不只是“按比例缩小”",[114,15041,15042],{},"主页有大量绝对定位元素，因此响应式的目标不是把桌面页面机械缩成手机版，而是维护清晰的空间约束：",[473,15044,15045,15052,15055,15058,15061,15068],{},[476,15046,15047,15048,15051],{},"桌面端通过 ",[60,15049,15050],{},"clamp()","、视口高度和稳定的面板高度控制上下关系；",[476,15053,15054],{},"最近文章与 RGB 面板在宽桌面上对齐顶部和底部；",[476,15056,15057],{},"小屏笔记本缩小间距和模块尺寸，仍避免重叠；",[476,15059,15060],{},"768 像素附近的紧凑布局隐藏无法舒适操作的 RGB 面板；",[476,15062,15063,15064,15067],{},"手机端重新排列主页元素，并使用 ",[60,15065,15066],{},"overflow: clip"," 防止装饰或过渡产生横向滚动；",[476,15069,15070],{},"固定格式组件使用明确的 grid 列、最小宽度和高度，标题悬浮时不会重新计算可用宽度。",[114,15072,15073],{},"Playwright 会在 768×1024、1024×768、1440×900 和 3440×1440 上读取真实元素几何信息，验证介绍不压住服务矩阵、上方面板不压住底部导航、RGB 与最近文章对齐、页面没有横向溢出。这里测试的是关系，而不是容易因一个像素改动就失效的整页截图。",[148,15075,15077],{"id":15076},"十主题色从一个-rgb-值扩展成整套界面","十、主题色从一个 RGB 值扩展成整套界面",[114,15079,15080,15081,15084],{},"主题逻辑集中在 ",[60,15082,15083],{},"useAccentTheme.ts","。唯一的核心状态是：",[250,15086,15090],{"className":15087,"code":15088,"language":15089,"meta":258,"style":258},"language-ts shiki shiki-themes github-light github-dark","interface RgbColor {\n  r: number\n  g: number\n  b: number\n}\n","ts",[60,15091,15092,15104,15117,15126,15135],{"__ignoreMap":258},[294,15093,15094,15098,15101],{"class":296,"line":297},[294,15095,15097],{"class":15096},"szBVR","interface",[294,15099,15100],{"class":2356}," RgbColor",[294,15102,15103],{"class":14920}," {\n",[294,15105,15106,15110,15113],{"class":296,"line":303},[294,15107,15109],{"class":15108},"s4XuR","  r",[294,15111,15112],{"class":15096},":",[294,15114,15116],{"class":15115},"sj4cs"," number\n",[294,15118,15119,15122,15124],{"class":296,"line":34},[294,15120,15121],{"class":15108},"  g",[294,15123,15112],{"class":15096},[294,15125,15116],{"class":15115},[294,15127,15128,15131,15133],{"class":296,"line":314},[294,15129,15130],{"class":15108},"  b",[294,15132,15112],{"class":15096},[294,15134,15116],{"class":15115},[294,15136,15137],{"class":296,"line":320},[294,15138,15139],{"class":14920},"}\n",[114,15141,15142,15143,15146,15147,15150],{},"三个内置预设是红、绿、蓝。站点设置里的 ",[60,15144,15145],{},"theme.defaultAccent"," 可以是任意合法十六进制颜色，并不要求与预设一致。当前颜色存放在 Nuxt ",[60,15148,15149],{},"useState"," 中，因此同一客户端的页面切换不会创建互相冲突的主题实例。",[114,15152,15153],{},"主题计算分为几步：",[770,15155,15156,15159,15168,15171,15177],{},[476,15157,15158],{},"把每个通道四舍五入并限制在 0 到 255。",[476,15160,15161,15162,15165,15166,1032],{},"生成 CSS ",[60,15163,15164],{},"rgb(r g b)"," 与大写 ",[60,15167,14805],{},[476,15169,15170],{},"把强调色分别与白色、画布色和黑色混合，得到 soft、faint、canvas、grid、strong 等衍生色。",[476,15172,15173,15174,1032],{},"计算 sRGB 相对亮度，对比黑白文字的对比度，自动选择更清晰的 ",[60,15175,15176],{},"--accent-ink",[476,15178,15179],{},"把结果以 CSS 变量挂到站点外壳，按钮、悬浮底色、网格、字标和 favicon 共同消费这些变量。",[114,15181,15182],{},"主题变量的意义大致如下：",[250,15184,15187],{"className":15185,"code":15186,"language":255,"meta":258},[253],"--accent          原始强调色\n--accent-ink      强调色背景上的黑或白文字\n--accent-soft     大面积悬浮反馈\n--accent-faint    更浅的面板背景\n--accent-canvas   轻微带主题倾向的页面画布\n--accent-grid     网格与线条色\n--accent-strong   向黑色混合后的强调色\n",[60,15188,15186],{"__ignoreMap":258},[114,15190,15191,15192,15194],{},"这样做比在 CSS 中到处硬编码“红色按钮、浅红背景、深红边框”更可靠。换成极亮或极暗的自定义颜色时，文字对比度仍会自动调整。E2E 会直接把颜色调到纯白和纯黑，验证 ",[60,15193,15176],{}," 分别变成深色和白色。",[148,15196,15198],{"id":15197},"十一主题持久化与旧黄色迁移","十一、主题持久化与旧黄色迁移",[114,15200,15201,15202,15205,15206,15209],{},"客户端挂载时，",[60,15203,15204],{},"restoreSavedAccent()"," 从 ",[60,15207,15208],{},"localStorage"," 读取：",[250,15211,15214],{"className":15212,"code":15213,"language":255,"meta":258},[253],"site-accent-rgb   完整 RGB 对象\nsite-accent       red \u002F green \u002F blue \u002F custom 等名称\n",[60,15215,15213],{"__ignoreMap":258},[114,15217,15218,15219,15222],{},"恢复后，深度监听 ",[60,15220,15221],{},"accentRgb","，后续每次变化都会重新保存。由于服务端无法读取浏览器存储，静态 HTML 首先使用 YAML 中的默认颜色；客户端挂载后再恢复访客上次的选择。",[114,15224,15225,15226,2545,15229,15232],{},"旧版本曾使用黄色默认主题。为了避免已经访问过网站的浏览器永远被旧缓存锁在黄色，恢复逻辑专门识别 ",[60,15227,15228],{},"{255,255,2}",[60,15230,15231],{},"yellow"," 的组合，删除这两个旧值并回到当前 YAML 默认色。这是一次小型数据迁移：不是清空所有人的自定义配色，只清除能够明确识别的旧默认值。",[114,15234,15235,15236,15239,15240,15243,15244,15247,15248,15251,15252,15255],{},"动态 favicon 也跟随主题和站点名称。",[60,15237,15238],{},"app.vue"," 在内存中生成一个包含主题背景色与 ",[60,15241,15242],{},"site.name"," 首字符的 SVG，再编码为 ",[60,15245,15246],{},"data:image\u002Fsvg+xml","。因此 CMS 修改站点名称并重新构建后，标签页字母会同步改变；图标底色、字色和 ",[60,15249,15250],{},"meta[name=theme-color]"," 则继续与 RGB 面板同步。",[60,15253,15254],{},"public\u002Ffavicon.svg"," 只作为静态回退。",[148,15257,15259],{"id":15258},"十二rgb-调色器的交互细节","十二、RGB 调色器的交互细节",[114,15261,15262,15265,15266,15269],{},[60,15263,15264],{},"RgbAssembly.vue"," 不是独立维护另一份颜色，它调用同一个 ",[60,15267,15268],{},"useAccentTheme()","，因此滑杆、预设按钮、页面按钮和 favicon 始终共享状态。",[114,15271,15272],{},"三个 range input 分别控制 R、G、B：",[473,15274,15275,15280,15283,15286,15289],{},[476,15276,15277,15279],{},[60,15278,8383],{}," 事件实时更新颜色，拖动时页面连续渐变；",[476,15281,15282],{},"每条轨道通过 CSS 变量计算已填充百分比；",[476,15284,15285],{},"输出区显示十六进制、十进制通道值，并采用自动对比文字；",[476,15287,15288],{},"点击 RED、GREEN、BLUE 立即加载预设；",[476,15290,15291],{},"重置按钮恢复 YAML 中的默认颜色，而不是写死某个预设。",[114,15293,15294],{},"字标动画不能在滑杆的每一个像素变化时重播，否则会不断重建动画并显得卡顿。组件因此区分“预览变化”和“提交变化”：",[473,15296,15297,15304,15310,15319,15326],{},[476,15298,15299,15300,15303],{},"指针按下后标记 ",[60,15301,15302],{},"pointerActive","，并捕获当前 pointer；",[476,15305,15306,15307,15309],{},"拖动中的 ",[60,15308,8383],{}," 只改变颜色；",[476,15311,15312,2335,15315,15318],{},[60,15313,15314],{},"pointerup",[60,15316,15317],{},"pointercancel"," 才请求重播一次字标；",[476,15320,15321,15322,15325],{},"键盘调整 range 时由 ",[60,15323,15324],{},"change"," 请求重播；",[476,15327,15328,15329,1098,15331,15333],{},"一个零延时抑制标志防止同一次鼠标提交同时触发 ",[60,15330,15314],{},[60,15332,15324],{},"，造成双播。",[114,15335,15336,15337],{},"预设与重置属于离散操作，所以点击时立即换色并重播。这个模型可以概括为：",[118,15338,15339],{},"连续操作实时预览、松手提交动画；离散操作立即提交。",[148,15341,15343],{"id":15342},"十三quilibra-字标是可编辑的艺术字","十三、Quilibra 字标是可编辑的艺术字",[114,15345,15346,15347,15350,15351,15354],{},"当前字标不再依赖固定图片，而是由 ",[60,15348,15349],{},"content\u002Fsettings\u002Fhome.yml"," 中的 ",[60,15352,15353],{},"headline.emphasis"," 生成真实文本。CMS 的“首页 \u002F 艺术字文字”可以直接修改内容，Schema 将长度限制为 20 个字符；组件还会按照中英文字符宽度自动缩小过长文字，避免挤出首屏。",[114,15356,15357],{},"字形使用随构建产物一起发布的 Cormorant Garamond 600 Italic。页面只引入拉丁字符子集，既保留高对比衬线斜体的艺术感，也不依赖 Google Fonts 等站外服务；无法覆盖的字符会回退到本机衬线字体。",[114,15359,15360],{},"动画仍然采用“RGB 套色从偏移到合拢”，没有模拟笔尖逐笔书写。字标由四层相同文本组成：",[473,15362,15363,15366,15369,15372],{},[476,15364,15365],{},"红层从左下附近偏移进入；",[476,15367,15368],{},"绿层从上方偏移进入；",[476,15370,15371],{},"蓝层从右下附近偏移进入；",[476,15373,15374],{},"最终层使用当前主题色并带极轻的阴影。",[114,15376,15377,15378,15381,15382,1332,15385,1098,15388,15391,15392,15395],{},"前三层使用 ",[60,15379,15380],{},"mix-blend-mode: multiply","、动态模糊、透明度和不同位移。动画前段保留可见错版，中段快速靠近，后段降低彩色层透明度并让最终主题层聚焦。四层都只改变 ",[60,15383,15384],{},"opacity",[60,15386,15387],{},"transform",[60,15389,15390],{},"filter","，播放时临时声明 ",[60,15393,15394],{},"will-change","，结束后不长期占用合成资源。",[114,15397,15398,15401],{},[60,15399,15400],{},"ArtisticWordmark.vue"," 的加载控制解决了“首次打开没有动画”和字体中途替换的问题：",[770,15403,15404,15410,15417,15420,15423],{},[476,15405,15406,15409],{},[60,15407,15408],{},"@fontsource\u002Fcormorant-garamond"," 把字体文件交给 Vite 打包并生成带哈希的静态资源。",[476,15411,15412,15413,15416],{},"组件调用 ",[60,15414,15415],{},"document.fonts.load()","，等待艺术字体真正可用于绘制。",[476,15418,15419],{},"只有字体就绪、组件仍存活、页面可见且请求仍是最新时才开始播放。",[476,15421,15422],{},"字体加载失败时继续使用衬线回退字体，不阻断内容显示。",[476,15424,15425],{},"开启减少动态效果时直接展示最终状态。",[114,15427,15428,15429,15432,15433,15435],{},"每次重播都会增加 ",[60,15430,15431],{},"animationRun","，并把它放进四层的 Vue ",[60,15434,1511],{},"。这会让浏览器得到新的动画元素，可靠地从第 0 帧开始，而不是依赖移除 class 后强制读取布局。",[148,15437,15439],{"id":15438},"十四字标在什么时候重播","十四、字标在什么时候重播",[114,15441,15442,15443,15446,15447,15450],{},"全局 ",[60,15444,15445],{},"useWordmarkAnimation()"," 只维护一个数字信号。任何组件调用 ",[60,15448,15449],{},"requestReplay()","，数字加一；字标组件 watch 到变化后重播。这个很小的事件通道避免 RGB 组件直接引用字标 DOM。",[114,15452,15453],{},"当前重播场景包括：",[473,15455,15456,15459,15462,15465,15468,15471,15474],{},[476,15457,15458],{},"首次进入主页并且艺术字体加载完成；",[476,15460,15461],{},"点击主题预设或重置；",[476,15463,15464],{},"RGB 滑杆松手或键盘提交；",[476,15466,15467],{},"从文章、服务、关于页面返回主页，主页组件重新挂载；",[476,15469,15470],{},"浏览器标签页从隐藏变为可见；",[476,15472,15473],{},"窗口重新获得焦点；",[476,15475,15476,15477,15480],{},"浏览器的 ",[60,15478,15479],{},"pageshow","，包括可能来自往返缓存的恢复。",[114,15482,15483,1332,15486,1098,15489,15491],{},[60,15484,15485],{},"visibilitychange",[60,15487,15488],{},"focus",[60,15490,15479],{}," 可能在同一次切回中连续到达。组件使用一个 animation frame 合并激活请求，并设置 200ms 的最小间隔，防止一次切回连续播两三遍。页面变为隐藏时会取消待播放帧、使旧异步请求失效并结束当前播放。",[114,15493,15494],{},"这里还有两个无障碍与容错原则：",[473,15496,15497,15503],{},[476,15498,15499,15502],{},[60,15500,15501],{},"prefers-reduced-motion: reduce"," 时不播放套色，直接显示最终字标；",[476,15504,15505,15506,15509],{},"可读文字仍存在于 ",[60,15507,15508],{},"sr-only"," 中，视觉 mask 不承担标题语义。",[148,15511,15513],{"id":15512},"十五服务矩阵图标与状态点","十五、服务矩阵、图标与状态点",[114,15515,15516],{},"每个服务在 YAML 中有六个字段：名称、说明、链接、状态、图标、是否显示在主页。",[114,15518,15519,15522,15523,2545,15525,15528,15529,15532],{},[60,15520,15521],{},"ServiceIcon.vue"," 把业务名称映射到 Lucide 图标。NAS 使用独立的 ",[60,15524,72],{},[60,15526,15527],{},"HardDrive","，不会复用影音播放图标。未知值虽然有 ",[60,15530,15531],{},"Server"," 回退，但正常内容会在 Schema 阶段被拒绝，因此回退只是组件级防御。",[114,15534,15535],{},"状态共有三种：",[250,15537,15540],{"className":15538,"code":15539,"language":255,"meta":258},[253],"online   在线\nprivate  仅限本人\nsoon     准备中\n",[60,15541,15539],{"__ignoreMap":258},[114,15543,15544,15547,15548,15551,15552,15555],{},[60,15545,15546],{},"ServiceStatusDot.vue"," 把状态写入 ",[60,15549,15550],{},"data-state","，CSS 用属性选择器决定颜色，",[60,15553,15554],{},"title"," 提供文字说明。主页和服务页都复用这个组件，因此 CMS 修改 YAML 状态后，重新构建的页面会同步变化，不需要分别维护配色。",[114,15557,15558,15559,15562],{},"状态点表达的是",[118,15560,15561],{},"人工配置的展示状态","，不是实时健康检查。它不会主动请求 Code、Photos、NAS 或 Status，也不会自动从监控系统同步。如果未来需要实时状态，应由独立状态 API 提供可信结果，再决定是在客户端请求还是构建前抓取；不能仅把绿色小点误解为自动监控。",[114,15564,15565,15566,15568,15569,15572,15573,15576,15577,15580],{},"链接为 ",[60,15567,76],{}," 时，主页服务节点转到 ",[60,15570,15571],{},"\u002Fservices","，服务页显示为不可点击行；完整 HTTPS 链接则在新标签页打开，并带 ",[60,15574,15575],{},"noopener noreferrer","。Schema 禁止 ",[60,15578,15579],{},"javascript:"," 和非 HTTPS 外部地址，避免 CMS 字段直接变成危险链接。",[148,15582,15584],{"id":15583},"十六文章列表筛选与搜索","十六、文章列表、筛选与搜索",[114,15586,15587,15589,15590,15593],{},[60,15588,14952],{}," 在构建阶段只查询 ",[60,15591,15592],{},"hidden = false"," 的文章，并按日期倒序排列。首页最近文章使用同样条件，浏览器端再对已经加载的公开数组做筛选，不需要为每个关键词请求服务器。",[114,15595,15596,15597,15600],{},"分类来自共享常量：随笔、学习、交易、观察。列表额外添加“全部”。搜索会把标题、摘要和标签拼成小写字符串，再做简单的 ",[60,15598,15599],{},"includes"," 匹配。这适合当前文章规模，特点是实现透明、中文可用、没有索引服务；但它不是分词搜索，也不会搜索正文。",[114,15602,15603],{},"文章行显示日期、标题、摘要、分类和箭头。悬浮一篇时：",[473,15605,15606,15609,15612,15615],{},[476,15607,15608],{},"只给当前行增加浅主题色背景；",[476,15610,15611],{},"日期与正文整体平移 8px；",[476,15613,15614],{},"箭头向右上轻移；",[476,15616,15617],{},"其他文章保持完全不变。",[114,15619,15620,15621,1332,15624,15627,15628,15631],{},"标题容器的 grid 列不会在 hover 时改变，所以长标题不会因为悬浮突然获得更小宽度而换行。主页“最近写下”的标题使用 ",[60,15622,15623],{},"minmax(0,1fr)",[60,15625,15626],{},"overflow:hidden","、省略号和 ",[60,15629,15630],{},"white-space:nowrap","，确保固定高度面板不被长标题撑开。",[114,15633,15634,15635,15638],{},"搜索结果区使用 ",[60,15636,15637],{},"aria-live=\"polite\"","，筛选后的篇数和空状态可以被辅助技术感知。",[114,15640,15641,15642,15645,15646,15648,15649,15652,15653,15656],{},"从文章详情返回列表时，列表状态会被恢复。",[60,15643,15644],{},"useWritingListState()"," 用一个小的 Nuxt ",[60,15647,15149],{}," 保存当前分类、搜索词和滚动位置：进入文章时 ",[60,15650,15651],{},"capture()"," 记录这三项并标记 ",[60,15654,15655],{},"restorePending","，返回列表时读取并消费这份状态，恢复筛选结果和滚动位置；浏览器后退同样恢复。直接打开一篇文章（例如从外部链接进入）没有可恢复的上下文，列表从顶部开始。恢复只发生在同一会话内存中，不写入 localStorage。",[148,15658,15660],{"id":15659},"十七文章详情与相邻导航","十七、文章详情与相邻导航",[114,15662,15663,15664,15667,15668,15671],{},"动态路由文件是 ",[60,15665,15666],{},"app\u002Fpages\u002Fwriting\u002F[...slug].vue","。它使用当前 ",[60,15669,15670],{},"route.path"," 查询对应文章；找不到时立即抛出带中文说明的 404。",[114,15673,15674,15675,15677],{},"详情查询本身也要求 ",[60,15676,15592],{},"，所以隐藏文章不只是从列表消失，直接访问原 URL 也会得到真正的 404。详情页同时取得按日期倒序排列的全部公开文章，用当前文章的数组位置计算：",[473,15679,15680,15683],{},[476,15681,15682],{},"数组中下一项是时间上更旧的“上一篇”；",[476,15684,15685],{},"数组中上一项是时间上更新的“下一篇”。",[114,15687,15688,15689,15691],{},"页面展示分类、发布日期、可选的更新日期、标题、摘要、标签与正文。正文由 Nuxt Content 的 ",[60,15690,14692],{}," 生成。SEO 标题使用全站模板变成“文章标题 · Quilibra”，描述来自 frontmatter，Open Graph 类型设为 article。",[114,15693,15694,15695,15698,15699,15702,15703,15706],{},"详情页会记住每篇文章的阅读位置。",[60,15696,15697],{},"useArticleReadingState()"," 把“路径到滚动位置”的映射存在 ",[60,15700,15701],{},"sessionStorage"," 中，离开文章时捕获当前位置，回到文章时用 ",[60,15704,15705],{},"behavior: 'instant'"," 一步恢复，不经过平滑滚动，避免看到一次从顶部滑下来的动画；恢复值会被限制在当前可滚动的最大范围内，内容变化后也不会定位到不存在的区域。同一会话内在多篇文章间切换，各自的位置互不覆盖。",[114,15708,15709,15710,15713,15714,15717,15718,15721,15722,15725],{},"文章内的标题锚点在允许动画的系统中平滑滚动：CSS 在 ",[60,15711,15712],{},"prefers-reduced-motion: no-preference"," 时给 ",[60,15715,15716],{},"html:has(.article-prose)"," 设置 ",[60,15719,15720],{},"scroll-behavior: smooth","，标题统一留出 16 像素的 ",[60,15723,15724],{},"scroll-margin-top","；减少动态效果时保持瞬间跳转。",[114,15727,15728,15729,15732],{},"正文样式集中在 ",[60,15730,15731],{},".article-prose","：限制阅读宽度、提高行高、分级处理标题、列表、引用、代码、链接、图片和带边框的表格（表格在 2026 年 8 月底加上了完整的边框、内边距和表头底色，长表格在窄屏下可横向阅读）。文章页面可以自由使用 Markdown，不需要为每篇文章编写 Vue 模板。",[148,15734,15736],{"id":15735},"十八文章-frontmatter-合约","十八、文章 frontmatter 合约",[114,15738,15739],{},"一篇新文章最小结构如下：",[250,15741,15745],{"className":15742,"code":15743,"language":15744,"meta":258,"style":258},"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,15746,15747,15752,15762,15772,15782,15792,15802,15809,15817],{"__ignoreMap":258},[294,15748,15749],{"class":296,"line":297},[294,15750,15751],{"class":2356},"---\n",[294,15753,15754,15756,15759],{"class":296,"line":303},[294,15755,15554],{"class":14923},[294,15757,15758],{"class":14920},": ",[294,15760,15761],{"class":2360},"清楚的文章标题\n",[294,15763,15764,15767,15769],{"class":296,"line":34},[294,15765,15766],{"class":14923},"description",[294,15768,15758],{"class":14920},[294,15770,15771],{"class":2360},"一句话说明文章实际记录了什么。\n",[294,15773,15774,15777,15779],{"class":296,"line":314},[294,15775,15776],{"class":14923},"date",[294,15778,15758],{"class":14920},[294,15780,15781],{"class":15115},"2026-07-26\n",[294,15783,15784,15787,15789],{"class":296,"line":320},[294,15785,15786],{"class":14923},"hidden",[294,15788,15758],{"class":14920},[294,15790,15791],{"class":15115},"false\n",[294,15793,15794,15797,15799],{"class":296,"line":326},[294,15795,15796],{"class":14923},"category",[294,15798,15758],{"class":14920},[294,15800,15801],{"class":2360},"学习\n",[294,15803,15804,15806],{"class":296,"line":332},[294,15805,2446],{"class":14923},[294,15807,15808],{"class":14920},":\n",[294,15810,15811,15814],{"class":296,"line":338},[294,15812,15813],{"class":14920},"  - ",[294,15815,15816],{"class":2360},"Nuxt\n",[294,15818,15819],{"class":296,"line":343},[294,15820,15751],{"class":2356},[114,15822,15823],{},"文件名必须是：",[250,15825,15828],{"className":15826,"code":15827,"language":255,"meta":258},[253],"YYYY-MM-DD-english-kebab.md\n",[60,15829,15827],{"__ignoreMap":258},[114,15831,15832,15833,1332,15835,1332,15837,1332,15839,1332,15842,1332,15845,1332,15847,1332,15849,1032,15851,15853,15854,15856],{},"允许字段只有 ",[60,15834,15554],{},[60,15836,15766],{},[60,15838,15776],{},[60,15840,15841],{},"updated",[60,15843,15844],{},"slug",[60,15846,15786],{},[60,15848,15796],{},[60,15850,2446],{},[60,15852,15786],{}," 只能是布尔值，省略时按 ",[60,15855,14899],{}," 处理。发布新文章时：",[473,15858,15859,15864,15870,15875],{},[476,15860,15861,15863],{},[60,15862,15776],{}," 必须是中国时区当天；",[476,15865,15866,15867,15869],{},"文件名日期必须与 ",[60,15868,15776],{}," 一致；",[476,15871,15872,15873,2528],{},"不能包含 ",[60,15874,15841],{},[476,15876,15877,15879],{},[60,15878,15844],{}," 如果存在，必须与文件名后半段一致。",[114,15881,15882,15883,15885,15886,15888,15889,15892],{},"修订文章时保留原文件名和 ",[60,15884,15776],{},"，把 ",[60,15887,15841],{}," 设置为中国时区当天。writer 默认保留现有隐藏状态，只有修订内容明确给出 ",[60,15890,15891],{},"hidden: false"," 才会恢复。正文结构是自由的，但不能为空。若使用二级标题“来源与延伸阅读”，该节必须至少有一个 HTTP 或 HTTPS 链接。",[114,15894,15895,15898,15899,15902],{},[60,15896,15897],{},"scripts\u002Fcontent\u002Fvalidate.ts"," 使用 ",[60,15900,15901],{},"gray-matter"," 和 YAML 解析 frontmatter，再用 Markdown AST 检查正文与来源节。使用结构化解析而不是正则扫描全文，可以区分标题、链接与普通文本，并给出确定的错误。",[148,15904,15906],{"id":15905},"十九sveltia-cms-到底是什么","十九、Sveltia CMS 到底是什么",[114,15908,15909,15912,15913,15915],{},[60,15910,15911],{},"\u002Fadmin\u002Findex.html"," 只加载 Sveltia CMS 的前端脚本，",[60,15914,14837],{}," 描述后台字段。它不是另一个网站后端，也不存文章副本。",[114,15917,15918],{},"后台能维护：",[473,15920,15921,15924,15927,15930,15933,15936],{},[476,15922,15923],{},"站点、首页与栏目文案；",[476,15925,15926],{},"默认强调色和主页模块开关；",[476,15928,15929],{},"服务的增删、顺序、URL、状态、图标和主页显示；",[476,15931,15932],{},"文章的新建、搜索、筛选、修改和删除；",[476,15934,15935],{},"文章的隐藏和恢复；",[476,15937,15938,15940],{},[60,15939,14568],{}," 中的文章资源。",[114,15942,15943],{},"CMS 有两种工作方式：",[770,15945,15946,15949],{},[476,15947,15948],{},"本地仓库模式：Chromium 通过 File System Access API 直接编辑本机选择的仓库。",[476,15950,15951],{},"GitHub 模式：用户经 GitHub 登录后，CMS 根据配置向私人仓库提交内容。",[114,15953,15954,15955,15957],{},"无论哪种模式，“保存”都只意味着源文件或远程仓库出现改动。它不会直接覆盖某个静态目录。远程 CMS 当前固定提交私人仓库的 ",[60,15956,14292],{}," 分支，完整链路是：",[250,15959,15962],{"className":15960,"code":15961,"language":255,"meta":258},[253],"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,15963,15961],{"__ignoreMap":258},[114,15965,15966],{},"因此，在 CMS 打开“隐藏文章”并保存，正式站会在同步器构建完成后自动切换到新 release；不再有 staging 预览和 promote 晋级。2026 年 8 月 3 日之前，这条链路中间还有 staging 分支、测试域名和人工晋级，CMS 保存只先出现在测试站；简化后保存即上线，代价是发布前的内容自查变得更加重要。",[114,15968,15969,15970,15973],{},"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,15971,15972],{},".env"," 不进入仓库。",[114,15975,15976,15977,15980,15981,15984,15985,15987],{},"远程后台统一通过 ",[60,15978,15979],{},"https:\u002F\u002Fquilibra.cn\u002Fadmin\u002Findex.html"," 使用。这个路径随静态站点公开发布，页面本身带 ",[60,15982,15983],{},"noindex"," 且不含任何秘密；能不能登录、能不能保存，完全由 GitHub OAuth 与私人仓库权限决定。2026 年 8 月 3 日之前它曾躲在带 Basic Auth 的测试域名后面，双环境简化时这层入口遮罩被一并移除。后台路径和 ",[60,15986,15983],{}," 都不是访问控制。",[148,15989,15991],{"id":15990},"二十受限-writer-为什么存在","二十、受限 writer 为什么存在",[114,15993,15994,15995,15998],{},"普通编辑器可以修改仓库中的任何文件，但自动化发布工具不应该拥有同样宽的写入自由。",[60,15996,15997],{},"scripts\u002Fcontent\u002Fwrite.ts"," 把 OpenClaw 的写入范围压缩为三个明确操作：create、update、delete。",[114,16000,16001],{},"新建流程只接受草稿目录中的一个普通 Markdown 文件名和一个无日期 slug。writer 会检查：",[473,16003,16004,16007,16010,16013,16016,16019,16022],{},[476,16005,16006],{},"草稿名不能包含目录或路径穿越；",[476,16008,16009],{},"草稿必须是普通文件，不能是符号链接；",[476,16011,16012],{},"草稿不超过 1 MiB；",[476,16014,16015],{},"slug 必须是小写英文、数字和连字符；",[476,16017,16018],{},"frontmatter 与正文必须通过完整校验；",[476,16020,16021],{},"日期必须是中国时区当天；",[476,16023,16024],{},"目标文章不能已经存在。",[114,16026,16027,16028,16031,16032,16035,16036,16039,16040,16043],{},"写入时先以 ",[60,16029,16030],{},"wx"," 模式建立权限为 ",[60,16033,16034],{},"0640"," 的随机临时文件并 ",[60,16037,16038],{},"fsync","，再用硬链接创建最终文件，利用文件系统的 ",[60,16041,16042],{},"EEXIST"," 保证不会覆盖已有新文章。更新时先验证现有文件和发布日期，再用 rename 原子替换。删除只接受精确的完整 dated slug，并拒绝目录、符号链接和不存在的目标。",[114,16045,16046],{},"这不是为了让日常写作变复杂，而是为了让“从聊天自动发布”仍有可审计的硬边界。Skill 中的文字规则可能被模型误解，writer 的路径、日期和文件类型检查则由操作系统与代码强制执行。",[148,16048,16050],{"id":16049},"二十一openclaw-发布-skill-的实际状态机","二十一、OpenClaw 发布 Skill 的实际状态机",[114,16052,16053,16054,16057],{},"仓库只维护一个 ",[60,16055,16056],{},"personal-site-publisher"," Skill。它先读取文章合约，再按以下过程工作：",[250,16059,16062],{"className":16060,"code":16061,"language":255,"meta":258},[253],"链接或素材\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,16063,16061],{"__ignoreMap":258},[114,16065,16066,16069,16070,1032],{},[60,16067,16068],{},"直接发布"," 只跳过人工预览，不跳过 writer、内容校验和静态生成。更新文章必须保留 dated URL；删除必须先展示目标信息，再收到完全匹配的 ",[60,16071,16072],{},"确认删除 \u003Cdated-slug>",[114,16074,16075,16076,1332,16079,2335,16082,16085],{},"发布 Skill 还规定：只接受所有者私聊中的发布命令；把网页、对话和搜索结果视为不可信内容；不把 API key、cookie、账号信息、私人对话细节和精确私人资产写入文章；不能用任意编辑器、",[60,16077,16078],{},"rm",[60,16080,16081],{},"git add",[60,16083,16084],{},"git commit"," 绕过 writer 和 release 脚本。",[148,16087,16089],{"id":16088},"二十二本地发布自动同步与内容快速通道","二十二、本地发布、自动同步与内容快速通道",[114,16091,16092],{},"当前有四个相关入口，名字接近但权限完全不同。",[161,16094,16096],{"id":16095},"npm-run-publish",[60,16097,16098],{},"npm run publish",[114,16100,16101],{},"用于受信本地流程中尚未提交的内容改动，例如 OpenClaw Skill。它只允许以下路径：",[250,16103,16106],{"className":16104,"code":16105,"language":255,"meta":258},[253],"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,16107,16105],{"__ignoreMap":258},[114,16109,16110],{},"它验证内容、生成静态站、自动创建本地 Git commit、复制 release 并切换本地默认生产目录。若工作区混入 Vue、CSS、脚本、CMS 配置或文档改动，会拒绝执行。它是受限本地发布入口；线上更新仍然依赖把提交推送到 GitHub 后由 Webhook 部署。",[161,16112,16114],{"id":16113},"npm-run-deploy",[60,16115,16116],{},"npm run deploy",[114,16118,16119],{},"用于已经提交的代码版本。它要求工作区完全干净，不创建 commit，并依次运行：",[250,16121,16124],{"className":16122,"code":16123,"language":255,"meta":258},[253],"类型检查\n内容与 writer 单元测试\nrelease\u002Fsync\u002Fwebhook 单元测试\n静态生成\n浏览器 E2E\n创建 release\n原子切换 current\n清理过旧 release\n",[60,16125,16123],{"__ignoreMap":258},[161,16127,16129],{"id":16128},"npm-run-deploycontent",[60,16130,16131],{},"npm run deploy:content",[114,16133,16134,16135,1032],{},"自动同步器判定提交只包含受管内容时使用的快速路径。它仍执行内容校验和静态生成，但跳过类型检查、单元测试和 E2E，让纯文章或站点设置的提交几十秒内上线；任何代码文件变化都会让同步器退回完整 ",[60,16136,16137],{},"deploy",[161,16139,16141],{"id":16140},"npm-run-sync",[60,16142,16143],{},"npm run sync",[114,16145,16146,16149],{},[60,16147,16148],{},"scripts\u002Fsync.ts"," 是 master 自动同步器的唯一实现，由 systemd oneshot 服务在收到 push 事件后运行。每轮会：",[770,16151,16152,16155,16158,16168,16173,16179],{},[476,16153,16154],{},"从 Sing-box 配置读取带认证的本机 HTTP 代理；",[476,16156,16157],{},"重试 GitHub fetch，只接受能够快进的远端历史；",[476,16159,16160,16161,16164,16165,2528],{},"根据 ",[60,16162,16163],{},"package-lock.json"," 哈希决定是否运行 ",[60,16166,16167],{},"npm ci",[476,16169,16170,16171,2528],{},"比较上次已部署提交与目标提交：只有受管内容变化时执行快速内容部署，否则执行完整 ",[60,16172,16116],{},[476,16174,16175,16176,2528],{},"只有部署成功才原子写入 ",[60,16177,16178],{},"deployed-commit",[476,16180,16181],{},"失败时保留旧 release，由持久化 Webhook 事件延迟重试。",[114,16183,16184,16185,16187,16188,16191],{},"它不再区分 staging：仓库、",[60,16186,16178],{}," 和 release 目录都只有一份。构建通过共享的 ",[60,16189,16190],{},"flock"," 文件串行执行，防止 2 GB 内存的 VPS 同时跑多份 Nuxt 构建和 Playwright。",[161,16193,16195],{"id":16194},"npm-run-webhook",[60,16196,16197],{},"npm run webhook",[114,16199,16200,16203,16204,16207,16208,16211,16212,16214,16215,16218,16219,1032],{},[60,16201,16202],{},"scripts\u002Fwebhook.ts"," 只监听 ",[60,16205,16206],{},"127.0.0.1:3110","，Nginx 把 ",[60,16209,16210],{},"https:\u002F\u002Fquilibra.cn\u002F_hooks\u002Fgithub"," 的精确 POST 路由转给它。接收器先验证 HMAC-SHA256 签名、仓库名、push 类型和 ",[60,16213,14292],{}," 分支，再把事件以权限 ",[60,16216,16217],{},"0600"," 写入用户状态目录并返回 ",[60,16220,16221],{},"202",[114,16223,16224],{},"每个 GitHub delivery 都有独立队列文件。若构建期间出现第二次 push，第一轮完成后会再启动一轮同步；进程重启时也会恢复未完成事件。失败使用延迟重试，不需要周期性轮询 GitHub。Webhook 请求只能映射到固定的同步 unit，不能携带或执行任意命令。",[114,16226,16227,16228,16230,16231,16234,16235,16237,16238,16240,16241,16243],{},"因此，修改文章现在只有两条合法路径：远程 CMS 保存到 ",[60,16229,14292],{},"，或本地受信流程用 writer\u002F",[60,16232,16233],{},"publish"," 提交；两者最终都由同一个 Webhook 部署。修改 Vue、CSS 或脚本并提交后，同步器自动执行完整 ",[60,16236,16137],{},"。不要用 ",[60,16239,16233],{}," 偷渡代码，也不要让 ",[60,16242,16137],{}," 带着未提交文件上线。",[148,16245,16247],{"id":16246},"二十三静态-release-如何做到切换时不中断","二十三、静态 release 如何做到切换时不中断",[114,16249,16250,16253],{},[60,16251,16252],{},"scripts\u002Frelease.ts"," 用 UTC 时间生成 release 名称，例如：",[250,16255,16258],{"className":16256,"code":16257,"language":255,"meta":258},[253],"20260726T144914-134Z\n",[60,16259,16257],{"__ignoreMap":258},[114,16261,16262,16263,16265,16266,16269,16270,16272],{},"生成结束后，把 ",[60,16264,14334],{}," 递归复制到 ",[60,16267,16268],{},"releases\u002F\u003Cid>","。激活时不是先删 ",[60,16271,14338],{}," 再新建，而是：",[770,16274,16275,16278,16281],{},[476,16276,16277],{},"创建一个带进程号的临时符号链接；",[476,16279,16280],{},"让临时链接指向新 release；",[476,16282,16283,16284,16287,16288,1032],{},"用 ",[60,16285,16286],{},"rename"," 把临时链接原子替换成 ",[60,16289,14338],{},[114,16291,16292,16293,16295,16296,16298],{},"对访问者来说，某个请求要么读取旧 release，要么读取新 release，不会遇到 ",[60,16294,14338],{}," 暂时不存在的中间状态。静态 Node 进程也不需要重启，因为它每次请求都从 ",[60,16297,14338],{}," 路径解析文件。",[114,16300,16301,16304,16305,16308],{},[60,16302,16303],{},"PERSONAL_SITE_DATA_ROOT"," 决定 release 根目录。生产不设置时使用 ",[60,16306,16307],{},"~\u002F.local\u002Fshare\u002Fpersonal-site","；双环境简化后，staging 数据根目录已经删除，线上只有这一份 release 历史。",[114,16310,16311],{},"脚本默认保留最近五个 release，并额外保证当前激活版本不被删掉。由于当前版本有可能是手工回滚到的较旧目录，保留“当前版本”比单纯保留时间最新的五个更重要。",[114,16313,16314,16315,16317],{},"回滚只调用同一个原子激活函数，把 ",[60,16316,14338],{}," 指回已存在的 release。它不修改 Git、不重新构建、不删除较新的版本，也不重启生产服务。",[148,16319,16321],{"id":16320},"二十四静态服务器nginx-与-https","二十四、静态服务器、Nginx 与 HTTPS",[114,16323,16324,16327,16328,1332,16331,1332,16334,1098,16337,16340,16341,16343,16344,1332,16347,1332,16350,16353,16354,16357],{},[60,16325,16326],{},"scripts\u002Fstatic-server.mjs"," 使用 Node 自带的 ",[60,16329,16330],{},"http",[60,16332,16333],{},"fs",[60,16335,16336],{},"stream",[60,16338,16339],{},"zlib","。监听地址、端口和 ",[60,16342,14338],{}," 路径分别由 ",[60,16345,16346],{},"PERSONAL_SITE_HOST",[60,16348,16349],{},"PERSONAL_SITE_PORT",[60,16351,16352],{},"PERSONAL_SITE_ROOT"," 控制。systemd unit 把 host 设为 ",[60,16355,16356],{},"127.0.0.1","，所以 3000 不直接接受公网连接。它没有框架中间件，职责非常窄：",[473,16359,16360,16363,16376,16382,16389,16392,16395,16398,16401,16404,16411,16417,16420],{},[476,16361,16362],{},"只接受 GET 和 HEAD，其他方法返回 405；",[476,16364,16365,16366,2545,16369,16372,16373,16375],{},"解码 URL 后用 ",[60,16367,16368],{},"resolve",[60,16370,16371],{},"relative"," 检查目标仍在 ",[60,16374,14338],{}," 内，阻止目录穿越；",[476,16377,16378,16379,2528],{},"请求目录时返回该目录的 ",[60,16380,16381],{},"index.html",[476,16383,16384,16385,16388],{},"找不到文件时返回预生成的 ",[60,16386,16387],{},"404.html"," 和 404 状态；",[476,16390,16391],{},"根据扩展名发送正确的 Content-Type；",[476,16393,16394],{},"对大于等于 1 KiB 的文本资源协商 Brotli 或 gzip；",[476,16396,16397],{},"Brotli 使用质量 4，gzip 使用级别 6，在 CPU 与体积间取中间值；",[476,16399,16400],{},"使用读取流和 pipeline，不把大文件一次性读进内存；",[476,16402,16403],{},"根据文件大小和修改时间生成弱 ETag；",[476,16405,16406,16407,16410],{},"命中 ",[60,16408,16409],{},"If-None-Match"," 时返回 304；",[476,16412,16413,16416],{},[60,16414,16415],{},"_nuxt\u002F"," 哈希资源缓存一年并标记 immutable；",[476,16418,16419],{},"HTML、图片等非哈希路径使用 no-cache，确保 release 切换后会重新验证；",[476,16421,16422],{},"收到 SIGINT 或 SIGTERM 时停止接受新连接并正常退出。",[114,16424,16425,16426,16428],{},"如果 ",[60,16427,14338],{}," 不可用，服务器返回 503，而不是把文件系统错误暴露给访问者。若响应流已经开始后出错，则销毁连接，避免发送一半文件后再拼接错误文本。",[114,16430,16431],{},"它不会处理 TLS、OAuth 或反向代理。公网入口由 Nginx 负责：",[250,16433,16436],{"className":16434,"code":16435,"language":255,"meta":258},[253],"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,16437,16435],{"__ignoreMap":258},[114,16439,16440,16441,16444,16445,16448],{},"根域名和 ",[60,16442,16443],{},"www"," 共用正式站，证书由 Certbot 申请并续期；HTTP 到 HTTPS 的跳转和证书续期都由 Certbot 管理。GitHub Webhook 是 Nginx 上的一个精确 location，只放行 POST，",[60,16446,16447],{},"client_max_body_size"," 限制为 1m。同一台 Nginx 还反代了个别与网站无关的自建子域，它们不属于这个仓库的发布链路，也不使用网站域名证书之外的资源。腾讯云安全组只需要开放 80 和 443，3000、3110、8081 不对公网放行。",[114,16450,16451,16452,16455,16456,16459],{},"页脚备案号来自全站设置 ",[60,16453,16454],{},"content\u002Fsettings\u002Fsite.yml","，当前 ICP 号链接到 ",[60,16457,16458],{},"https:\u002F\u002Fbeian.miit.gov.cn\u002F","。备案展示、DNS、TLS 证书和反向代理是四个不同层次：备案号不会自动配置 HTTPS，证书也不会自动创建 DNS 记录。",[148,16461,16463],{"id":16462},"二十五3000310031013110-分别是什么","二十五、3000、3100、3101、3110 分别是什么",[166,16465,16466,16479],{},[169,16467,16468],{},[172,16469,16470,16473,16476],{},[175,16471,16472],{},"端口",[175,16474,16475],{},"用途",[175,16477,16478],{},"生命周期",[185,16480,16481,16492,16507,16518],{},[172,16482,16483,16486,16489],{},[190,16484,16485],{},"3000",[190,16487,16488],{},"当前生产静态 release",[190,16490,16491],{},"systemd 长期运行",[172,16493,16494,16497,16500],{},[190,16495,16496],{},"3100",[190,16498,16499],{},"本地开发约定端口",[190,16501,16502,16503,16506],{},"仅手工运行 ",[60,16504,16505],{},"npm run dev -- --port 3100"," 时存在",[172,16508,16509,16512,16515],{},[190,16510,16511],{},"3101",[190,16513,16514],{},"Playwright 隔离测试服务",[190,16516,16517],{},"测试自动启动并结束",[172,16519,16520,16523,16526],{},[190,16521,16522],{},"3110",[190,16524,16525],{},"签名 Webhook 接收与事件排队",[190,16527,16491],{},[114,16529,16530,16531,16533,16534,16536],{},"3100 曾经运行一个常驻的 staging 静态服务，双环境简化后它退化为纯粹的本地开发端口；需要热更新时在开发机或 VPS 临时运行 ",[60,16532,16505],{},"，不要让它占据生产心智。3101 使用独立内容 fixture 和 ",[60,16535,14631],{},"，不能拿它当日常预览端口。3110 只监听回环地址，由 Nginx 的精确 location 转发 GitHub 的 POST。",[114,16538,16539],{},"生产 3000 和事件驱动的同步流程可以共存，因为每次构建都通过共享锁串行执行，不会互相踩踏。",[148,16541,16543],{"id":16542},"二十六systemd-用户服务","二十六、systemd 用户服务",[114,16545,16546],{},"当前与网站相关的 unit 有四个：",[250,16548,16551],{"className":16549,"code":16550,"language":255,"meta":258},[253],"personal-site.service                 3000 生产静态服务\npersonal-site-webhook.service         3110 签名 Webhook 接收与事件排队\npersonal-site-sync.service            master 单次同步与部署\npersonal-site-goatcounter.service     8081 自托管统计服务（实验回退后仍在运行）\n",[60,16552,16550],{"__ignoreMap":258},[114,16554,16555,16556,16559],{},"前三个来自仓库的 ",[60,16557,16558],{},"ops\u002Fsystemd\u002F","；第四个是服务器上独立部署的 GoatCounter 进程，不属于网站发布链路。2026 年 8 月 26 日的统计实验回退后，站点不再加载任何统计脚本，这个服务目前只是遗留基础设施。",[114,16561,16562],{},"静态服务的关键设置包括：",[473,16564,16565,16571,16577,16583,16589,16598],{},[476,16566,16567,16570],{},[60,16568,16569],{},"Type=simple","：Node 进程本身就是主进程；",[476,16572,16573,16576],{},[60,16574,16575],{},"After\u002FWants=network-online.target","：网络就绪后启动；",[476,16578,16579,16582],{},[60,16580,16581],{},"Restart=on-failure"," 与 5 秒延迟：异常退出后自动恢复；",[476,16584,16585,16588],{},[60,16586,16587],{},"KillSignal=SIGINT","：与静态服务器的优雅关闭逻辑对应；",[476,16590,16591,1332,16594,16597],{},[60,16592,16593],{},"NoNewPrivileges=true",[60,16595,16596],{},"PrivateTmp=true","：缩小运行时权限；",[476,16599,16600,16601,16604],{},"安装到用户级 ",[60,16602,16603],{},"default.target","，不要求整个服务以 root 运行。",[114,16606,16607,16608,16610,16611,16614,16615,2545,16618,16620],{},"Webhook 服务是 ",[60,16609,16569],{}," 常驻轻量进程，只监听回环地址；同步服务是 ",[60,16612,16613],{},"Type=oneshot","，开机不自启，完全由 Webhook 事件触发，并拥有独立的 ",[60,16616,16617],{},"sync-repo",[60,16619,16178],{},"。仓库没有周期性 GitHub timer；有事件时才 fetch，依赖锁变化时才安装依赖，纯内容提交使用快速构建。Webhook 与同步服务的环境覆盖（例如校验的仓库名）通过 systemd drop-in 目录维护。",[114,16622,16623,16624,16627,16628,16631],{},"unit 当前把 Node 可执行文件写成 NVM 下的明确路径。升级 Node 或切换 NVM 版本后，shell 中的 ",[60,16625,16626],{},"node"," 可能已经变化，但 systemd 仍引用旧路径，这是需要主动检查的运维陷阱。修改 unit 或 drop-in 后必须 ",[60,16629,16630],{},"daemon-reload","，而普通文章或页面 release 不需要重启静态服务。",[114,16633,16634,16635,16638,16639,16642],{},"用户级服务要在 VPS 重启且用户未登录时自动启动，需要为运行网站的 Linux 用户启用 linger。管理它时应使用相同用户的 ",[60,16636,16637],{},"systemctl --user","，不应使用 ",[60,16640,16641],{},"sudo systemctl --user"," 去连接 root 的用户总线。",[148,16644,16646],{"id":16645},"二十七测试体系究竟覆盖什么","二十七、测试体系究竟覆盖什么",[114,16648,16649],{},"测试按风险边界分成三组。",[161,16651,16653],{"id":16652},"内容和-writer-单元测试","内容和 writer 单元测试",[114,16655,16656,16659],{},[60,16657,16658],{},"test:content"," 检查：",[473,16661,16662,16665,16668,16671,16674,16679,16682,16685,16688],{},[476,16663,16664],{},"合法文章和四种分类可以通过；",[476,16666,16667],{},"非法日期、分类、文件名、重复标签和空正文会失败；",[476,16669,16670],{},"来源节必须包含公共 HTTP(S) 链接；",[476,16672,16673],{},"CMS 可选 slug 必须与文件名一致；",[476,16675,16676,16678],{},[60,16677,15786],{}," 只能是布尔值，writer 更新时默认保留已有隐藏状态；",[476,16680,16681],{},"站点 YAML 满足 Schema；",[476,16683,16684],{},"writer 拒绝覆盖、穿越、符号链接和错误修订日期；",[476,16686,16687],{},"更新保留发布日期；",[476,16689,16690],{},"删除只接受精确存在的 dated slug。",[161,16692,16694],{"id":16693},"release同步与-webhook-单元测试","release、同步与 Webhook 单元测试",[114,16696,16697,16700],{},[60,16698,16699],{},"test:release"," 在临时目录中检查：",[473,16702,16703,16708,16711,16714,16717,16720,16726,16729,16732],{},[476,16704,16705,16707],{},[60,16706,14338],{}," 能原子切换；",[476,16709,16710],{},"清理历史版本时保留激活 release；",[476,16712,16713],{},"release 名称不能路径穿越；",[476,16715,16716],{},"受管内容路径不会扩大到应用代码；",[476,16718,16719],{},"自动同步失败后不写成功状态，后续重试仍会部署；",[476,16721,16722,16723,16725],{},"Webhook 拒绝无效签名、其他仓库和 ",[60,16724,14292],{}," 之外的分支；",[476,16727,16728],{},"构建期间到达的第二个 push 会保留并追加同步；",[476,16730,16731],{},"纯内容提交走快速构建，代码提交走完整构建；",[476,16733,16734],{},"子命令失败时错误信息保留退出码。",[161,16736,16738],{"id":16737},"playwright-浏览器测试","Playwright 浏览器测试",[114,16740,16741,16744],{},[60,16742,16743],{},"test:e2e"," 在 Chromium 中检查：",[473,16746,16747,16750,16753,16756,16759,16762,16765,16768,16771,16774,16777,16780,16783,16786,16789,16792,16795],{},[476,16748,16749],{},"首页成功水合且没有控制台错误；",[476,16751,16752],{},"动态 favicon、theme-color 和 RGB 输出同步；",[476,16754,16755],{},"自定义主题在纯黑纯白下仍可读；",[476,16757,16758],{},"旧黄色缓存会迁移；",[476,16760,16761],{},"mask 没加载完时字标不偷跑，加载后正常播放；",[476,16763,16764],{},"标签页激活、窗口 focus、换色、滑杆松手和返回主页会重播；",[476,16766,16767],{},"拖动期间不会反复播放；",[476,16769,16770],{},"服务状态、NAS 图标、最近文章、文章详情和更新日期正确；",[476,16772,16773],{},"隐藏文章不出现在公开查询中，原 URL 返回 404；",[476,16775,16776],{},"文章悬浮只影响当前行，长标题宽度不跳变；",[476,16778,16779],{},"从文章返回恢复列表的分类、搜索词和滚动位置，浏览器后退同样恢复，直接进入文章时列表从顶部开始；",[476,16781,16782],{},"文章阅读位置按路径保存并瞬时恢复；",[476,16784,16785],{},"文章标题锚点在允许动画的系统中平滑滚动；",[476,16787,16788],{},"触屏设备有按压反馈且不残留 hover 状态，触摸屏笔记本能正确区分触控与触控板 hover；",[476,16790,16791],{},"移动端返回按钮指向正确目标，“回到顶部”按钮按滚动阈值出现；",[476,16793,16794],{},"手机页面无横向溢出，RGB 在小屏隐藏；",[476,16796,16797],{},"多种桌面尺寸下关键模块互不重叠。",[114,16799,16800],{},"Playwright 配置允许一次重试，等待时间 10 秒，临时 Nuxt 服务启动最长等待 120 秒。响应式几何测试启用 reduced motion，避免入场动画中的瞬时位置影响布局判断。",[114,16802,16803],{},"测试不是越多越好。这里保留的是曾经真实出错、或者一旦回归就很难靠类型系统发现的行为；字体文件数量、每个 CSS 颜色和每一段正文不需要分别写测试。",[148,16805,16807],{"id":16806},"二十八当前性能画像与做过的优化","二十八、当前性能画像与做过的优化",[114,16809,16810],{},"以下数字来自 2026 年 7 月版本的一次本机测量：",[473,16812,16813,16816,16819,16822,16825],{},[476,16814,16815],{},"主 CSS 约 31.1 KB，gzip 后约 6.9 KB；",[476,16817,16818],{},"外部或本地字体文件由 98 个减少到 0；",[476,16820,16821],{},"首页一次传输约 252 KB；",[476,16823,16824],{},"模拟 4G 和 4 倍 CPU 降速时，LCP 约 0.75 秒，完整 load 约 1.37 秒；",[476,16826,16827],{},"CLS 为 0。",[114,16829,16830,16831,16834],{},"VPS 空闲状态下，生产静态服务与 Webhook 接收器等常驻 Node 进程合计约几十 MB；统计实验遗留的 GoatCounter 进程也很轻。完整 Nuxt + Playwright 部署的峰值接近 1 GB，但同步构建通过共享锁串行执行；内容快速构建不启动 Playwright。磁盘主要消耗在工作仓库、同步仓库和各自的 ",[60,16832,16833],{},"node_modules"," 上；这是用磁盘换取工作区、缓存和发布故障互不污染。",[114,16836,16837],{},"这些数字是环境相关快照，不是永久承诺。浏览器缓存、网络、CPU、文章数量和依赖升级都会改变结果。",[114,16839,16840],{},"当前主要性能措施包括：",[473,16842,16843,16846,16849,16852,16859,16864,16867,16870,16873,16876,16879],{},[476,16844,16845],{},"静态预渲染，HTML 首次响应包含页面内容；",[476,16847,16848],{},"移除大批字体资源，改用系统字体；",[476,16850,16851],{},"只预加载首屏真正依赖的 30 KB 字标 mask；",[476,16853,16854,16855,16858],{},"字标等待 ",[60,16856,16857],{},"decode()","，避免资源未准备好时动画丢失；",[476,16860,16861,16863],{},[60,16862,15031],{}," 合并高频指针更新；",[476,16865,16866],{},"缓存元素边界，避免每次 pointermove 读取布局；",[476,16868,16869],{},"连续调色只改颜色，松手才重播动画；",[476,16871,16872],{},"用 transform\u002Fopacity 处理大多数动效；",[476,16874,16875],{},"生产服务器流式响应并支持 Brotli、gzip、ETag 和长期哈希缓存；",[476,16877,16878],{},"固定组件尺寸，避免动态文本造成布局偏移；",[476,16880,16881],{},"分离本地开发、生产同步和 E2E 的工作目录与 Nuxt Content 数据库。",[114,16883,16884,16886],{},[60,16885,14334],{}," 整体约 5.4 MB，其中包括 Nuxt Content 的客户端 SQLite\u002FWASM 相关资源；这不等于每个访客首屏都会下载整个目录。分析性能时应看浏览器实际请求和传输体积，不应把产物目录大小直接当作首页流量。",[148,16888,16890],{"id":16889},"二十九为什么首次交互偶尔会过一会儿才正常","二十九、为什么首次交互偶尔会“过一会儿才正常”",[114,16892,16893],{},"这个问题曾同时表现为字标不播放和 RGB 滑杆暂时失灵，根因方向不是滑杆 CSS 本身，而是客户端水合尚未完成或 Nuxt Dev 仍在首次编译。静态 HTML 可以先显示，但 Vue 事件只有在 JavaScript 加载、执行并成功水合后才生效。",[114,16895,16896,16897,16900],{},"判断方法是观察 ",[60,16898,16899],{},".site-shell[data-hydrated=\"true\"]","。如果页面已经显示但该属性迟迟没有变成 true，应检查：",[473,16902,16903,16906,16909,16912,16915],{},[476,16904,16905],{},"浏览器控制台是否有 JavaScript 错误；",[476,16907,16908],{},"本地 Nuxt Dev 是否还在首次编译；",[476,16910,16911],{},"本地内容 SQLite 缓存是否损坏；",[476,16913,16914],{},"大型依赖或资源是否阻塞客户端入口；",[476,16916,16917],{},"当前访问的是临时 Nuxt Dev，还是已经生成的 3000 静态站。",[114,16919,16920],{},"目前通过移除字体、减轻入口资源、预加载 mask、分离内容数据库和明确动画水合闸门改善了这个过程。所有常驻端口提供的都是静态 release，不存在边访问边编译的线上环境。",[148,16922,16924],{"id":16923},"三十常见修改应该改哪里","三十、常见修改应该改哪里",[161,16926,16928],{"id":16927},"修改个人介绍栏目文案或服务","修改个人介绍、栏目文案或服务",[114,16930,16931,16932,16934,16935,16937],{},"优先通过 CMS 中对应的页面入口，或编辑 ",[60,16933,14561],{}," 下对应 YAML；服务列表位于 ",[60,16936,14758],{},"。若新增服务状态或图标类型，还要同步修改 Schema、配置枚举、CMS 选项、组件映射、CSS 和测试。",[161,16939,16940],{"id":16940},"修改默认颜色",[114,16942,16943,16944,16946,16947,16949],{},"修改 ",[60,16945,15145],{},"。它可以是任意 ",[60,16948,14805],{},"。已有访客若保存过自定义颜色，会继续看到自己的选择；要做全体迁移，需要像旧黄色迁移一样明确识别并处理旧值，不能无条件清空 localStorage。",[161,16951,16952],{"id":16952},"修改红绿蓝预设",[114,16954,16943,16955,1028,16957,16960],{},[60,16956,15083],{},[60,16958,16959],{},"accentPresets","。RGB 面板和非主页调色按钮都复用它。修改后应测试纯黑纯白对比、localStorage 恢复和字标重播。",[161,16962,16963],{"id":16963},"修改艺术字",[114,16965,16966,16967,1028,16969,16971,16972,16974,16975,16977,16978,1032],{},"在 CMS 中打开“站点设置 \u002F 首页”，修改“艺术字文字”即可。它最终写入 ",[60,16968,14738],{},[60,16970,15353],{},"，保存到 ",[60,16973,14292],{}," 后由 Webhook 自动重新构建上线。字形由 ",[60,16976,14578],{}," 引入，字体栈、字号和四层颜色位于 ",[60,16979,16980],{},"main.css",[161,16982,16983],{"id":16983},"修改字标动画",[114,16985,16986,16987,16989,16990,16993,16994,16996],{},"播放条件在 ",[60,16988,15400],{},"，跨组件触发在 ",[60,16991,16992],{},"useWordmarkAnimation.ts","，视觉关键帧在 ",[60,16995,16980],{},"。不要把三者混成一个大组件：字体状态、事件状态和视觉时间线是不同责任。",[161,16998,16999],{"id":16999},"修改文章分类",[114,17001,17002,17003,17006],{},"至少同步 ",[60,17004,17005],{},"app\u002Fconfig\u002Farticles.ts","、Nuxt Content Schema、CMS 选项、校验器测试与界面测试。文章中的旧分类是否迁移，需要单独决定。",[161,17008,17009],{"id":17009},"修改文章样式",[114,17011,17012,17013,17016,17017,17019,17020,17023],{},"文章列表看 ",[60,17014,17015],{},".article-row","，正文看 ",[60,17018,15731],{},"（表格边框也在这里），详情头部看 ",[60,17021,17022],{},".article-hero","。长标题、手机宽度、hover 前后几何和其他文章 opacity 是必须复查的回归点。",[161,17025,17026],{"id":17026},"修改全局返回与回顶按钮",[114,17028,17029,17030,17032,17033,17035],{},"返回目标的判断和阈值在 ",[60,17031,15238],{},"，视觉与过渡在 ",[60,17034,16980],{},"。修改后要同时覆盖文章页、普通内页和移动端三类场景。",[161,17037,17038],{"id":17038},"修改生产服务",[114,17040,17041,17042,17044],{},"静态响应逻辑改 ",[60,17043,16326],{},"；监听端口、release 根目录、Node 路径和重启策略改对应 systemd unit。同步仓库、代理和部署锁改 sync 脚本与 drop-in 环境变量。只有静态服务器或 unit 变化需要重启\u002F重载，普通 release 切换不需要。",[148,17046,17048],{"id":17047},"三十一日常发布配方","三十一、日常发布配方",[161,17050,17052],{"id":17051},"通过远程-cms-修改内容","通过远程 CMS 修改内容",[770,17054,17055,17061,17067],{},[476,17056,17057,17058,17060],{},"打开 ",[60,17059,15979],{}," 并用 GitHub 登录。",[476,17062,17063,17064,17066],{},"保存后 CMS 直接向 ",[60,17065,14292],{}," 提交，Webhook 触发同步：纯内容提交走快速构建，几十秒内上线。",[476,17068,17069],{},"检查正式站对应的页面和 URL。",[114,17071,17072],{},"保存和上线之间没有人工晋级步骤，提交前的内容自查是唯一的安全垫。隐藏文章也遵循同一流程。",[161,17074,17075],{"id":17075},"本地受信流程直接发布内容",[250,17077,17079],{"className":2347,"code":17078,"language":2349,"meta":258,"style":258},"npm run content:validate\nnpm run publish\n",[60,17080,17081,17092],{"__ignoreMap":258},[294,17082,17083,17086,17089],{"class":296,"line":297},[294,17084,17085],{"class":2356},"npm",[294,17087,17088],{"class":2360}," run",[294,17090,17091],{"class":2360}," content:validate\n",[294,17093,17094,17096,17098],{"class":296,"line":303},[294,17095,17085],{"class":2356},[294,17097,17088],{"class":2360},[294,17099,17100],{"class":2360}," publish\n",[114,17102,17103,17105],{},[60,17104,16233],{}," 成功后会输出 commit、release 和 current 路径。它用于 OpenClaw 等本地受限流程，会验证内容、生成静态站、提交并切换本地默认生产目录；把提交推送到 GitHub 后，Webhook 负责线上部署。若工作区混入 Vue、CSS、脚本、CMS 配置或文档改动，它会拒绝执行。",[161,17107,17109],{"id":17108},"发布-vuecss脚本或配置代码","发布 Vue、CSS、脚本或配置代码",[250,17111,17113],{"className":2347,"code":17112,"language":2349,"meta":258,"style":258},"npm run verify\ngit diff --check\n# 明确检查并提交本次文件\ngit push origin master\n",[60,17114,17115,17124,17135,17141],{"__ignoreMap":258},[294,17116,17117,17119,17121],{"class":296,"line":297},[294,17118,17085],{"class":2356},[294,17120,17088],{"class":2360},[294,17122,17123],{"class":2360}," verify\n",[294,17125,17126,17129,17132],{"class":296,"line":303},[294,17127,17128],{"class":2356},"git",[294,17130,17131],{"class":2360}," diff",[294,17133,17134],{"class":15115}," --check\n",[294,17136,17137],{"class":296,"line":34},[294,17138,17140],{"class":17139},"sJ8bj","# 明确检查并提交本次文件\n",[294,17142,17143,17145,17148,17151],{"class":296,"line":314},[294,17144,17128],{"class":2356},[294,17146,17147],{"class":2360}," push",[294,17149,17150],{"class":2360}," origin",[294,17152,17153],{"class":2360}," master\n",[114,17155,17156,17159,17160,17162,17163,1032],{},[60,17157,17158],{},"verify"," 会依次运行内容校验、类型检查、单元测试、静态生成和 E2E；master Webhook 之后触发同步器执行完整 ",[60,17161,16116],{},"。需要在 VPS 立即部署当前干净提交时，也可以手工运行 ",[60,17164,16116],{},[161,17166,17167],{"id":17167},"查看当前生产状态",[250,17169,17171],{"className":2347,"code":17170,"language":2349,"meta":258,"style":258},"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,17172,17173,17187,17198,17209,17217],{"__ignoreMap":258},[294,17174,17175,17178,17181,17184],{"class":296,"line":297},[294,17176,17177],{"class":2356},"systemctl",[294,17179,17180],{"class":15115}," --user",[294,17182,17183],{"class":2360}," is-active",[294,17185,17186],{"class":2360}," personal-site.service\n",[294,17188,17189,17191,17193,17195],{"class":296,"line":303},[294,17190,17177],{"class":2356},[294,17192,17180],{"class":15115},[294,17194,17183],{"class":2360},[294,17196,17197],{"class":2360}," personal-site-webhook.service\n",[294,17199,17200,17203,17206],{"class":296,"line":34},[294,17201,17202],{"class":2356},"readlink",[294,17204,17205],{"class":15115}," -f",[294,17207,17208],{"class":2360}," ~\u002F.local\u002Fshare\u002Fpersonal-site\u002Fcurrent\n",[294,17210,17211,17214],{"class":296,"line":314},[294,17212,17213],{"class":2356},"cat",[294,17215,17216],{"class":2360}," ~\u002F.local\u002Fstate\u002Fpersonal-site-sync\u002Fdeployed-commit\n",[294,17218,17219,17222,17225,17228,17231,17234,17237],{"class":296,"line":320},[294,17220,17221],{"class":2356},"curl",[294,17223,17224],{"class":15115}," -fsS",[294,17226,17227],{"class":15115}," -o",[294,17229,17230],{"class":2360}," \u002Fdev\u002Fnull",[294,17232,17233],{"class":15115}," -w",[294,17235,17236],{"class":2360}," '%{http_code} %{content_type}\\n'",[294,17238,17239],{"class":2360}," http:\u002F\u002F127.0.0.1:3000\u002F\n",[161,17241,17242],{"id":17242},"回滚",[114,17244,17245],{},"先列出确实存在的 release，再执行：",[250,17247,17249],{"className":2347,"code":17248,"language":2349,"meta":258,"style":258},"npm run rollback -- \u003Cexact-release-id>\n",[60,17250,17251],{"__ignoreMap":258},[294,17252,17253,17255,17257,17260,17263,17266,17269,17272],{"class":296,"line":297},[294,17254,17085],{"class":2356},[294,17256,17088],{"class":2360},[294,17258,17259],{"class":2360}," rollback",[294,17261,17262],{"class":15115}," --",[294,17264,17265],{"class":15096}," \u003C",[294,17267,17268],{"class":2360},"exact-release-i",[294,17270,17271],{"class":14920},"d",[294,17273,14943],{"class":15096},[114,17275,17276,17277,17279,17280,17282,17283,1032],{},"回滚后再次检查 ",[60,17278,14338],{}," 和 3000 返回值。不要手动删除 ",[60,17281,14338],{},"，也不要把它指向仓库的 ",[60,17284,14334],{},[148,17286,17288],{"id":17287},"三十二故障排查顺序","三十二、故障排查顺序",[161,17290,17292],{"id":17291},"cms-保存后正式站没变化","CMS 保存后正式站没变化",[114,17294,17295,17296,17298,17299,17301,17302,1332,17305,17308,17309,17312,17313,17315],{},"先确认提交是否进入 GitHub ",[60,17297,14292],{},"，并在 GitHub Recent Deliveries 中确认 Webhook 返回 ",[60,17300,16221],{},"；再检查 ",[60,17303,17304],{},"personal-site-webhook.service",[60,17306,17307],{},"personal-site-sync.service"," 日志、",[60,17310,17311],{},"~\u002F.local\u002Fstate\u002Fpersonal-site-sync\u002Fdeployed-commit"," 和生产 ",[60,17314,14338],{},"。构建失败不会覆盖旧 release；事件文件会保留并延迟重试，也可以修复后手工启动同步服务。",[161,17317,17319],{"id":17318},"rgb-不能拖按钮不能点动画不播放","RGB 不能拖、按钮不能点、动画不播放",[114,17321,17322,17323,17326,17327,17330],{},"先看 ",[60,17324,17325],{},"data-hydrated","，再看控制台错误和网络请求。若只发生在临时 Nuxt Dev 的第一次编译，等待完成后重载；若持续发生，停止那个临时开发进程，运行 ",[60,17328,17329],{},"npx nuxt cleanup"," 后重启开发端口。常驻 3000 是静态服务器，不使用这份开发缓存。",[161,17332,17333],{"id":17333},"切回标签页没有动画",[114,17335,17336,17337,1332,17339,17341],{},"检查浏览器是否启用 reduced motion、mask 图片是否加载失败，以及 ",[60,17338,15485],{},[60,17340,15488],{}," 是否触发。一次切回只播一次是正常防抖，不应去掉 200ms 合并后让三个事件连续触发。",[161,17343,17344],{"id":17344},"换色先闪回旧颜色",[114,17346,17347,17348,17350],{},"检查静态默认色、Nuxt ",[60,17349,15149],{}," 初始值和 localStorage 恢复顺序。服务端 HTML 无法知道客户端保存颜色，完全避免首帧差异需要在页面渲染前执行极小的内联主题恢复脚本；当前实现选择保持静态生成简单，在水合时恢复。",[161,17352,17354],{"id":17353},"服务状态点不随-cms-变化","服务状态点不随 CMS 变化",[114,17356,17357,17358,17361,17362,17364],{},"确认 YAML 中 ",[60,17359,17360],{},"state"," 已保存、值属于三个枚举。CMS 保存后需要等同步构建完成才会看到变化；若 DOM 属性已变而颜色没变，检查 CSS 状态选择器；若属性没变，检查 ",[60,17363,14338],{}," 是否仍是旧 release。",[161,17366,17368],{"id":17367},"_3000-无法访问","3000 无法访问",[114,17370,17371,17372,17374,17375,17377],{},"检查用户级 systemd 状态、日志、Node 固定路径、端口占用和 ",[60,17373,14338],{}," 链接。服务器进程存在但 ",[60,17376,14338],{}," 失效时会返回 503。不要在未确认进程归属前直接杀端口进程。真正常驻的只有 3000 和 3110；3100、3101 都按需临时启动，不为它们单独排障。",[161,17379,17381],{"id":17380},"管理页面提示只允许-https","管理页面提示只允许 HTTPS",[114,17383,17384,17385,17387],{},"这通常来自浏览器安全上下文要求，而不是 Git remote 配错。Git remote 只决定仓库同步地址，不会给 HTTP 页面增加 HTTPS。远程后台应使用已经配置可信证书的 ",[60,17386,15979],{},"；本地仓库模式则从 localhost 打开。",[148,17389,17391],{"id":17390},"三十三安全与备份边界","三十三、安全与备份边界",[114,17393,17394],{},"至少要区分三类数据：",[250,17396,17399],{"className":17397,"code":17398,"language":255,"meta":258},[253],"Git 仓库       源码、文章、站点设置、公开静态资源\nOpenClaw 目录  Skill、草稿、会话和本地配置\nrelease 目录   生产当前及历史静态构建结果\n",[60,17400,17398],{"__ignoreMap":258},[114,17402,17403],{},"Git 提交是版本历史，不是异地备份。私人远程仓库可以备份源码与文章，但 OpenClaw 配置和 release 数据需要单独备份到受控存储。",[114,17405,17406,17407,17409],{},"永远不要提交或写入公开文章的内容包括：API key、OAuth secret、部署私钥、QQ token、cookie、扫码状态、",[60,17408,15972],{},"、私人服务密码、内部地址清单和不适合公开的资产信息。",[114,17411,17412,17413,17416,17417,17419],{},"CMS 的 ",[60,17414,17415],{},"\u002Fadmin\u002F"," 路径和 ",[60,17418,15983],{}," 只能减少搜索引擎收录，不能阻止陌生人访问。当前后台没有 Basic Auth，保护来自可信 HTTPS 下的 GitHub OAuth 登录与私人仓库权限；生产上游只监听回环地址，公网 IP 与未知 Host 由默认 Nginx 站点拒绝（HTTP 返回 444，HTTPS 直接拒绝握手）。真正的保护来自这些边界和最小权限凭据，而不是隐藏 URL。",[148,17421,17423],{"id":17422},"三十四维护这套代码时应坚持的原则","三十四、维护这套代码时应坚持的原则",[114,17425,17426],{},"第一，内容、表现和发布边界分开。能在 YAML 解决的文案不要硬编码进 Vue；能由共享组件表达的状态不要在两个页面复制；生产切换不要混进页面代码。",[114,17428,17429],{},"第二，把浏览器状态当成显式状态。主题色、资源是否解码、页面是否可见、指针是否仍按下、水合是否完成、列表与阅读位置是否待恢复，都应有清楚的变量和生命周期，而不是靠延时猜测。",[114,17431,17432],{},"第三，动画必须有静态最终状态。资源失败、JavaScript 失败或用户减少动态效果时，标题仍要可读，布局仍要成立。",[114,17434,17435],{},"第四，自动化写入必须比人工编辑权限更窄。Skill 负责工作流，writer 负责硬边界，validator 负责内容合约，release 负责上线，四者不能互相冒充。",[114,17437,17438],{},"第五，生产部署应该是“生成一个完整新版本，然后切换”，而不是在访问者正在读取的目录里逐个覆盖文件。原子 release 是这个小网站最值得长期保留的工程设计之一。",[114,17440,17441,17442,17444],{},"第六，发布路径必须单一且有校验。CMS 和本地流程都汇入 ",[60,17443,14292],{},"，由同一个签名 Webhook 部署；内容快速构建与代码完整构建的差别由脚本判定，而不是由人临场决定。环境越简单，越要靠校验和原子切换守住质量。",[114,17446,17447],{},"第七，测试真实的关系和历史 bug。主题对比、滑杆松手、标签页重播、长标题 hover、隐藏文章 404、列表与阅读位置恢复、锚点平滑滚动、服务状态同步和多视口不重叠，都来自实际使用中的问题，比为了提高数字而堆大量脆弱测试更有价值。",[148,17449,17451],{"id":17450},"三十五重新上手时的最短阅读路径","三十五、重新上手时的最短阅读路径",[114,17453,17454],{},"如果未来很久没有维护这个项目，按下面顺序读，可以最快恢复上下文：",[770,17456,17457,17463,17471,17480,17490,17495,17507,17514],{},[476,17458,17459,17460,17462],{},"看 ",[60,17461,14561],{}," 的五份 YAML，知道全站和各页面当前展示什么。",[476,17464,17459,17465,1098,17468,17470],{},[60,17466,17467],{},"app\u002Fpages\u002Findex.vue",[60,17469,16980],{}," 的主页部分，理解首屏结构。",[476,17472,17459,17473,1332,17475,1332,17477,17479],{},[60,17474,15083],{},[60,17476,15264],{},[60,17478,15400],{},"，理解最复杂的交互。",[476,17481,17482,17483,1332,17486,17489],{},"看两个 writing 页面和 ",[60,17484,17485],{},"useWritingListState.ts",[60,17487,17488],{},"useArticleReadingState.ts","，理解文章查询、渲染与状态恢复。",[476,17491,17459,17492,17494],{},[60,17493,14796],{},"、validator 和 writer，理解内容边界。",[476,17496,17459,17497,1332,17500,1332,17503,17506],{},[60,17498,17499],{},"release.ts",[60,17501,17502],{},"sync.ts",[60,17504,17505],{},"webhook.ts"," 和 systemd unit，理解 master 直发的自动部署链路。",[476,17508,17509,17510,17513],{},"看 Nginx 模板与 ",[60,17511,17512],{},"docs\u002F"," 运维文档，理解域名、HTTPS 和默认拒绝站点。",[476,17515,17516],{},"最后看测试，把“哪些行为不能再坏”快速过一遍。",[114,17518,17519],{},"只要能重新回答下面六个问题，就已经掌握了这个项目：",[473,17521,17522,17525,17528,17531,17534,17537],{},[476,17523,17524],{},"当前页面内容的唯一来源在哪里？",[476,17526,17527],{},"浏览器中的主题色如何从 RGB 状态传到 CSS、字标和 favicon？",[476,17529,17530],{},"CMS 保存之后，一次生产 release 是如何自动发生的？",[476,17532,17533],{},"publish、deploy、deploy:content 和 sync 的权限、检查与 Git 行为有什么不同？",[476,17535,17536],{},"一个新 release 如何在不重启 3000 的情况下接管生产请求？",[476,17538,17539],{},"文章列表状态和阅读位置是如何在会话内恢复的？",[114,17541,17542],{},"这些问题就是 Quilibra 当前技术架构的骨架。",[148,17544,2534],{"id":2534},[473,17546,17547,17554,17561,17568,17575,17582,17589,17596],{},[476,17548,17549],{},[123,17550,17553],{"href":17551,"rel":17552},"https:\u002F\u002Fnuxt.com\u002Fdocs",[2543],"Nuxt 文档",[476,17555,17556],{},[123,17557,17560],{"href":17558,"rel":17559},"https:\u002F\u002Fcontent.nuxt.com\u002F",[2543],"Nuxt Content 文档",[476,17562,17563],{},[123,17564,17567],{"href":17565,"rel":17566},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fextras\u002Fcomposition-api-faq.html",[2543],"Vue Composition API",[476,17569,17570],{},[123,17571,17574],{"href":17572,"rel":17573},"https:\u002F\u002Fplaywright.dev\u002Fdocs\u002Fintro",[2543],"Playwright 文档",[476,17576,17577],{},[123,17578,17581],{"href":17579,"rel":17580},"https:\u002F\u002Fgithub.com\u002Fsveltia\u002Fsveltia-cms",[2543],"Sveltia CMS 文档",[476,17583,17584],{},[123,17585,17588],{"href":17586,"rel":17587},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FCSS\u002Fmask",[2543],"MDN：CSS mask",[476,17590,17591],{},[123,17592,17595],{"href":17593,"rel":17594},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FPage_Visibility_API",[2543],"MDN：Page Visibility API",[476,17597,17598],{},[123,17599,17602],{"href":17600,"rel":17601},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FCaching",[2543],"MDN：HTTP 缓存",[2580,17604,17605],{},"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":258,"searchDepth":303,"depth":303,"links":17607},[17608,17609,17610,17611,17612,17613,17614,17615,17616,17617,17618,17619,17620,17621,17622,17623,17624,17625,17626,17627,17628,17629,17636,17637,17638,17639,17640,17645,17646,17647,17658,17665,17674,17675,17676,17677],{"id":14302,"depth":303,"text":14303},{"id":14345,"depth":303,"text":14346},{"id":14378,"depth":303,"text":14379},{"id":14510,"depth":303,"text":14511},{"id":14572,"depth":303,"text":14573},{"id":14703,"depth":303,"text":14704},{"id":14848,"depth":303,"text":14849},{"id":14967,"depth":303,"text":14968},{"id":15038,"depth":303,"text":15039},{"id":15076,"depth":303,"text":15077},{"id":15197,"depth":303,"text":15198},{"id":15258,"depth":303,"text":15259},{"id":15342,"depth":303,"text":15343},{"id":15438,"depth":303,"text":15439},{"id":15512,"depth":303,"text":15513},{"id":15583,"depth":303,"text":15584},{"id":15659,"depth":303,"text":15660},{"id":15735,"depth":303,"text":15736},{"id":15905,"depth":303,"text":15906},{"id":15990,"depth":303,"text":15991},{"id":16049,"depth":303,"text":16050},{"id":16088,"depth":303,"text":16089,"children":17630},[17631,17632,17633,17634,17635],{"id":16095,"depth":34,"text":16098},{"id":16113,"depth":34,"text":16116},{"id":16128,"depth":34,"text":16131},{"id":16140,"depth":34,"text":16143},{"id":16194,"depth":34,"text":16197},{"id":16246,"depth":303,"text":16247},{"id":16320,"depth":303,"text":16321},{"id":16462,"depth":303,"text":16463},{"id":16542,"depth":303,"text":16543},{"id":16645,"depth":303,"text":16646,"children":17641},[17642,17643,17644],{"id":16652,"depth":34,"text":16653},{"id":16693,"depth":34,"text":16694},{"id":16737,"depth":34,"text":16738},{"id":16806,"depth":303,"text":16807},{"id":16889,"depth":303,"text":16890},{"id":16923,"depth":303,"text":16924,"children":17648},[17649,17650,17651,17652,17653,17654,17655,17656,17657],{"id":16927,"depth":34,"text":16928},{"id":16940,"depth":34,"text":16940},{"id":16952,"depth":34,"text":16952},{"id":16963,"depth":34,"text":16963},{"id":16983,"depth":34,"text":16983},{"id":16999,"depth":34,"text":16999},{"id":17009,"depth":34,"text":17009},{"id":17026,"depth":34,"text":17026},{"id":17038,"depth":34,"text":17038},{"id":17047,"depth":303,"text":17048,"children":17659},[17660,17661,17662,17663,17664],{"id":17051,"depth":34,"text":17052},{"id":17075,"depth":34,"text":17075},{"id":17108,"depth":34,"text":17109},{"id":17167,"depth":34,"text":17167},{"id":17242,"depth":34,"text":17242},{"id":17287,"depth":303,"text":17288,"children":17666},[17667,17668,17669,17670,17671,17672,17673],{"id":17291,"depth":34,"text":17292},{"id":17318,"depth":34,"text":17319},{"id":17333,"depth":34,"text":17333},{"id":17344,"depth":34,"text":17344},{"id":17353,"depth":34,"text":17354},{"id":17367,"depth":34,"text":17368},{"id":17380,"depth":34,"text":17381},{"id":17390,"depth":303,"text":17391},{"id":17422,"depth":303,"text":17423},{"id":17450,"depth":303,"text":17451},{"id":2534,"depth":303,"text":2534},"2026-07-26","记录 Quilibra 个人主页的技术选型、页面架构、内容系统、主题动画、master 单分支自动发布、HTTPS、测试体系和维护方法。",{},"\u002Fwriting\u002F2026-07-26-quilibra-complete-technical-handbook","2026-07-26T23:46:23+08:00",{"title":14272,"description":17679},"quilibra-complete-technical-handbook","writing\u002F2026-07-26-quilibra-complete-technical-handbook",[17687,17688,14417,17689,14465,14457,17690],"Nuxt","Vue","Nuxt Content","静态部署","2026-09-04","HzIhtT-51G4G8strCzY8f_5tUQqCBlSY7hKJqm6uh7o",{"id":17694,"title":17695,"body":17696,"category":12985,"date":17678,"description":17855,"extension":2627,"hidden":2628,"kind":5123,"meta":17856,"navigation":30,"path":17857,"publishedAt":17682,"seo":17858,"series":5123,"slug":17859,"stem":17860,"tags":17861,"unit":5123,"updated":13203,"__hash__":17866},"writing\u002Fwriting\u002F2026-07-26-trading-cognition-and-compound-reality.md","做交易的动机：从量化门槛到复利现实",{"type":111,"value":17697,"toc":17844},[17698,17701,17705,17708,17711,17715,17718,17721,17724,17727,17730,17733,17736,17740,17743,17763,17766,17786,17789,17792,17806,17809,17812,17815,17818,17821,17825,17828,17831,17835,17838,17841],[114,17699,17700],{},"刷到一个清华博士的视频，说想赚钱就去量化。但这对于普通人的门槛是不是太高？就算跨过去了，市场容量真的够赚到钱吗？带着这些问题和 AI 聊了一圈，从量化门槛一路聊到\"你到底为什么做交易\"，把思路理了一遍。",[148,17702,17704],{"id":17703},"机构量化-vs-系统化交易","机构量化 vs 系统化交易",[114,17706,17707],{},"机构量化（幻方、九坤级别）的门槛确实很高：博士学历、海量 tick 数据、专用算力、上亿起步资金，而且赛道内卷、alpha 衰减快、容量有限。",[114,17709,17710],{},"但普通人能做的是\"系统化交易\"而非机构量化。20 日均线 + 严格止损 + 行业 ETF，这套逻辑本质就是量化思维。真正的问题不是门槛，是把博士生的参照系错误地套用在自己身上。这类建议对清北学生可操作，对大多数人是废话，因为它省略了关键前提：清北学生的起点已经过滤掉 90% 的竞争。",[148,17712,17714],{"id":17713},"只是为了赚钱可能是对自己的贬低","\"只是为了赚钱\"可能是对自己的贬低",[114,17716,17717],{},"我说做交易只是为了赚钱。但仔细想想，我对系统和机制本身一直有探究欲--止损逻辑、去中心化的交易所架构、自己项目的技术选型会追求优雅和性能。真正纯粹为钱的人不会纠结止损后策略逻辑对不对。",[114,17719,17720],{},"如果账户明天清零，还会不会继续研究这套系统？答案可能是会的。因为做交易挣不挣钱是对我认知是否正确的检验，猜对了奖励，猜错了亏钱。我在市场里的目的是验证认知，想知道自己是怎样的人。",[114,17722,17723],{},"核心驱动不是钱（AI说的 我持怀疑态度），是\"认知闭环\"--市场是不会说谎的裁判，钱只是计分板。",[148,17725,17726],{"id":17726},"一个需要警惕的特质",[114,17728,17729],{},"对\"是否正确\"极度敏感的人，容易把坚持系统等同于坚持自我，把止损等同于承认\"我是错的\"。如果哪天开始扛单不是因为信号而是无法接受\"我看错了\"，这个特质就从优势变成风险。",[114,17731,17732],{},"关键区分：把\"验证认知\"和\"验证自我价值\"分开。市场说这笔交易错了，不等于整个判断体系错了。我相信市场大部分时间是对的，应该是我往市场靠去检验自己，而不是市场错的时候坚持自己。但空仓时说这话容易，真正重仓浮亏 5%、系统说止损但脑子里说\"只是短期波动\"的那一刻，这套认知还在不在才是真考验。",[114,17734,17735],{},"止损系统的价值不只是风控，更是强制执行那些理性认同但情绪会动摇的信念。",[148,17737,17739],{"id":17738},"_10-万到几百万数学可行执行淘汰大多数人","10 万到几百万：数学可行，执行淘汰大多数人",[114,17741,17742],{},"市场容量直接排除--A 股日均成交额 8000 亿到 1.2 万亿，几十万到几百万对市场影响约等于零。真实难度分三段：",[473,17744,17745,17751,17757],{},[476,17746,17747,17750],{},[118,17748,17749],{},"10 万到 100 万","：拼策略稳定性和纪律一致性",[476,17752,17753,17756],{},[118,17754,17755],{},"100 万到 500 万","：拼复利需要时间，以及账户变大后心态是否变化（100 万亏 10% 是 10 万，感受和 10 万亏 10% 完全不同）",[476,17758,17759,17762],{},[118,17760,17761],{},"500 万以上","：拼市场风格切换时系统能否不变形",[114,17764,17765],{},"参照现有系统严格执行，年化 15%-30% 的历史区间成立。三种情景的复利推算：",[473,17767,17768,17774,17780],{},[476,17769,17770,17773],{},[118,17771,17772],{},"保守 15%","：10 万到 100 万约 17 年，到 500 万约 39 年",[476,17775,17776,17779],{},[118,17777,17778],{},"中性 20%","：10 万到 100 万约 13 年，到 500 万约 21 年",[476,17781,17782,17785],{},[118,17783,17784],{},"乐观 25%","：10 万到 100 万约 11 年，到 500 万约 15 年",[114,17787,17788],{},"数学上可行，但\"10 年严格执行\"淘汰的人比策略本身多。年化均值背后有巨大的方差，最大风险不是策略本身，而是震荡市连续止损后大多数人会改变规则。",[148,17790,17791],{"id":17791},"人群中能走到哪",[473,17793,17794,17797,17800,17803],{},[476,17795,17796],{},"能长期跑赢通胀：前 20%",[476,17798,17799],{},"年化 15%+ 持续 5 年以上：前 5%",[476,17801,17802],{},"系统化策略 10 年不变形：前 2-3%",[476,17804,17805],{},"10 万做到 500 万：前 1-2%",[114,17807,17808],{},"这个门槛不是天才门槛，而是纪律和时间门槛。输给认知的少，输给自己的多。",[148,17810,17811],{"id":17811},"交易不是生活的全部",[114,17813,17814],{},"正常认真上班拿现金流，有自己的爱好爱好，偶尔玩玩游戏，继续交易探索市场和世界。每个部分各司其职：工作管现金流，运动游戏管切换放松，交易是认知战场。",[114,17816,17817],{},"这个心态本身是优势。很多人把交易当生活全部，盯盘过度、情绪放大、决策变差。有工作提供现金流、有运动游戏切换状态，交易在这个结构里心理压力更小，压力小反而更容易执行系统。",[114,17819,17820],{},"但有一个前提：执行密度。想清楚和做到之间有 gap，关键不是今天想通了，而是明天还在做。",[148,17822,17824],{"id":17823},"一个佩服的-up-主","一个佩服的 UP 主",[114,17826,17827],{},"关注过一个 UP 主，他的操作和认知确实让人佩服：做过信息差套利，也在暴跌日开过期权多单护盘，事后看判断也大多是对的。",[114,17829,17830],{},"他的宏观判断和产业理解思路值得学，具体操作和套利思路也有参考价值。没有实盘记录这点确实无法验证，但判断以及操作确实很牛逼。",[148,17832,17834],{"id":17833},"a-股打新现状","A 股打新现状",[114,17836,17837],{},"查了一下 A 股打新的数据：2024 年新股首日平均涨幅 253%，单签平均收益约 2 万元；2025 年至 6 月，45 只新股无一破发。但主板网上中签率仅 0.03% 左右，参与人数超 1100 万户，普通人中签概率极低，更像是彩票。网下机构打新年化约 4.5%，但门槛是亿级资金账户。包括今年以来的一些大肉签（比如沐曦股份 摩尔线程 最近的长鑫存储）上市当日涨幅都很夸张，随便中几签都是几万块。可惜科创板门槛高啊。（我什么时候才能有50w）",[148,17839,17840],{"id":17840},"止损永远是最重要的",[114,17842,17843],{},"按照均线止损太死板，套到很多个股上买点太高了。但不管用什么方式入场，止损永远是第一位的。买点可以慢慢校准，进场理由可以有很多种，但止损必须进场前定好，而且不能因为浮亏就移动它。止损一旦守不住，什么策略都白搭。在市场里还是先活下来比较重要。",{"title":258,"searchDepth":303,"depth":303,"links":17845},[17846,17847,17848,17849,17850,17851,17852,17853,17854],{"id":17703,"depth":303,"text":17704},{"id":17713,"depth":303,"text":17714},{"id":17726,"depth":303,"text":17726},{"id":17738,"depth":303,"text":17739},{"id":17791,"depth":303,"text":17791},{"id":17811,"depth":303,"text":17811},{"id":17823,"depth":303,"text":17824},{"id":17833,"depth":303,"text":17834},{"id":17840,"depth":303,"text":17840},"从清华博士的建议想要赚钱就去做量化出发，梳理普通人做系统化交易的现实路径、人群比例和自我认知驱动。",{},"\u002Fwriting\u002F2026-07-26-trading-cognition-and-compound-reality",{"title":17695,"description":17855},"trading-cognition-and-compound-reality","writing\u002F2026-07-26-trading-cognition-and-compound-reality",[12985,17862,17863,17864,17865],"量化","认知","复利","系统化交易","kkeAPkWMCa4zdSvwkAlnHZlUbvyzQ-z5pFmE31fCl0o",{"id":17868,"title":17869,"body":17870,"category":12985,"date":17678,"description":18051,"extension":2627,"hidden":2628,"kind":5123,"meta":18052,"navigation":30,"path":18053,"publishedAt":18054,"seo":18055,"series":5123,"slug":18056,"stem":18057,"tags":18058,"unit":5123,"updated":13203,"__hash__":18065},"writing\u002Fwriting\u002F2026-07-26-btc-synthetic-futures-arbitrage.md","BTC期权合成期货套利：定价偏差与跨平台对冲",{"type":111,"value":17871,"toc":18042},[17872,17875,17879,17882,17885,17889,17895,17903,17906,17909,17912,17918,17924,17927,17931,17934,17937,17941,17948,17954,17957,17960,17974,17979,17987,17993,17998,18012,18017,18029,18032,18036,18039],[114,17873,17874],{},"看到一套量化交易系统看板：年化 197%、极低回撤、多平仓少持仓。核心策略是利用 Deribit 期权市场合成的虚拟期货价格与 Binance 永续合约价格之间的定价偏差进行对冲套利。当时对着看板追问了 AI 几轮，才把这套合成期货套利的逻辑理清楚。这篇文章是给自己留的复盘笔记。",[148,17876,17878],{"id":17877},"deribit-是什么","Deribit 是什么",[114,17880,17881],{},"Deribit 是目前加密货币期权交易量最大的交易所，总部在巴拿马，2016 年上线。BTC 和 ETH 期权的主流流动性都在这里，行权价、到期日覆盖范围广，机构参与度高。它的期权以币本位结算（BTC 期权用 BTC 结算），也支持永续合约和交割期货。",[114,17883,17884],{},"正因为 Deribit 在期权市场的深度优势，它的期权定价成为合成期货策略的基础端。另一端选 Binance 永续合约，是因为 Binance 流动性最好、API 稳定、支持开空，适合做对冲腿。",[148,17886,17888],{"id":17887},"核心概念合成期货","核心概念：合成期货",[114,17890,17891,17892,1032],{},"期权平价公式告诉我们：",[118,17893,17894],{},"买入 Call + 卖出 Put = 合成期货多头",[473,17896,17897,17900],{},[476,17898,17899],{},"买入 Call（看涨期权）：涨了赚，跌了不亏",[476,17901,17902],{},"卖出 Put（看跌期权）：涨了没事，跌了亏",[114,17904,17905],{},"两者组合后的盈亏轨迹，与直接持有 BTC 现货或期货完全一致——涨了赚、跌了亏。这就是\"合成期货\"。",[148,17907,17908],{"id":17908},"两种套利方向",[114,17910,17911],{},"关键在于：Deribit 期权合成的期货价格，和 Binance 永续合约的价格，经常不一致。",[114,17913,17914,17917],{},[118,17915,17916],{},"转换套利（Conversion \u002F SF+BS）","：当合成期货价格 > Binance 期货价格时，在 Deribit 卖出合成期货（卖出 Call + 买入 Put），同时在 Binance 买入永续合约。",[114,17919,17920,17923],{},[118,17921,17922],{},"反转套利（Reversal \u002F BF+SS）","：当合成期货价格 \u003C Binance 期货价格时，在 Deribit 买入合成期货（买入 Call + 卖出 Put），同时在 Binance 卖出永续合约。",[114,17925,17926],{},"本质上就是\"哪边便宜哪边买，哪边贵了哪边卖\"。",[148,17928,17930],{"id":17929},"对冲机制为什么涨跌都不怕","对冲机制：为什么涨跌都不怕",[114,17932,17933],{},"币安端永远做与 Deribit 完全相反的动作。Deribit 开合成多头，Binance 必开永续空头。BTC 暴涨暴跌时，两端的盈亏完全抵消，行情风险归零。",[114,17935,17936],{},"利润不来自方向判断，而是开仓瞬间两端的定价差。期权到期结算时，合成价格强制回归现货价，基差收敛完成无风险落袋。",[148,17938,17940],{"id":17939},"一个关键区分行权价-现价","一个关键区分：行权价 ≠ 现价",[114,17942,17943,17944,17947],{},"看板中的合约代码 ",[60,17945,17946],{},"BTC-10JUN26-62000","，62000 是行权价，不是当时的市场现价。当时 Binance 的标记价约为 62,991 美元。因为现价高于行权价，看涨期权呈浮盈状态，看跌期权的权利金已被吃满。",[114,17949,17950,17951,1032],{},"比对套利机会时，标的不是行权价本身，而是",[118,17952,17953],{},"合成总成本 = 行权价 + 买Call权利金 - 卖Put权利金",[148,17955,17956],{"id":17956},"完整数值演练",[114,17958,17959],{},"设定场景：",[473,17961,17962,17965,17968],{},[476,17963,17964],{},"Binance 现价：$63,000",[476,17966,17967],{},"Deribit 行权价 $62,000 的期权：Call 权利金 $1,500，Put 权利金 $300",[476,17969,17970,17971],{},"合成空头总成本：$62,000 + $1,500 - $300 = ",[118,17972,17973],{},"$63,200",[114,17975,17976],{},[118,17977,17978],{},"开仓动作：",[473,17980,17981,17984],{},[476,17982,17983],{},"Binance 开多，$63,000",[476,17985,17986],{},"Deribit 合成空头（卖 Call + 买 Put），$63,200",[114,17988,17989,17990],{},"开仓瞬间锁定利润：$63,200 - $63,000 = ",[118,17991,17992],{},"$200",[114,17994,17995],{},[118,17996,17997],{},"极端行情 A - 暴涨至 $80,000：",[473,17999,18000,18003,18006],{},[476,18001,18002],{},"Binance 多头盈利：+$17,000",[476,18004,18005],{},"Deribit 合成空头亏损：-$16,800",[476,18007,18008,18009],{},"合计：",[118,18010,18011],{},"+$200",[114,18013,18014],{},[118,18015,18016],{},"极端行情 B - 暴跌至 $40,000：",[473,18018,18019,18022,18025],{},[476,18020,18021],{},"Binance 多头亏损：-$23,000",[476,18023,18024],{},"Deribit 合成空头盈利：+$23,200",[476,18026,18008,18027],{},[118,18028,18011],{},[114,18030,18031],{},"无论涨跌，总利润始终为开仓时锁定的 $200。到期自动结算收敛，整个过程是左手倒右手，利润在开仓那一下就已经确定了。",[148,18033,18035],{"id":18034},"模型不难难的是工程","模型不难，难的是工程",[114,18037,18038],{},"这套套利的交易系统作者说过一句话：套利模型倒是不难，难的是一些工程上的事情。",[114,18040,18041],{},"确实，逻辑上这就是一个低买高卖的价差捕捉，但实际跑起来要处理的问题远比模型复杂：两个交易所的 API 延迟差异、订单簿深度和滑点、手续费摩擦对利润空间的侵蚀、资金在两个平台之间的划转效率、期权到期时的自动结算对齐、极端行情下的风控（硬止损、Gamma 检查、行情降级）、以及资金容量上限——能套的价差就那么多，资金量大了反而吃不掉。这些工程细节才是决定系统能不能真正赚钱的关键。",{"title":258,"searchDepth":303,"depth":303,"links":18043},[18044,18045,18046,18047,18048,18049,18050],{"id":17877,"depth":303,"text":17878},{"id":17887,"depth":303,"text":17888},{"id":17908,"depth":303,"text":17908},{"id":17929,"depth":303,"text":17930},{"id":17939,"depth":303,"text":17940},{"id":17956,"depth":303,"text":17956},{"id":18034,"depth":303,"text":18035},"看到一套量化套利系统后追问AI，拆解Deribit合成期货与Binance永续合约之间的跨平台套利逻辑，含完整数值演练。",{},"\u002Fwriting\u002F2026-07-26-btc-synthetic-futures-arbitrage","2026-07-26T13:37:11+08:00",{"title":17869,"description":18051},"btc-synthetic-futures-arbitrage","writing\u002F2026-07-26-btc-synthetic-futures-arbitrage",[18059,18060,18061,18062,18063,18064],"BTC","期权","套利","合成期货","Deribit","Binance","gIKPEV_QCVSuhIUCasGE6y3oCH3B09doV6BtMezwe8E",{"id":18067,"title":18068,"body":18069,"category":2624,"date":17678,"description":18234,"extension":2627,"hidden":2628,"kind":5123,"meta":18235,"navigation":30,"path":18236,"publishedAt":18237,"seo":18238,"series":5123,"slug":18239,"stem":18240,"tags":18241,"unit":5123,"updated":13203,"__hash__":18244},"writing\u002Fwriting\u002F2026-07-26-why-vector-database.md","向量库的用途与选型",{"type":111,"value":18070,"toc":18227},[18071,18075,18078,18089,18092,18106,18110,18113,18127,18130,18134,18137,18140,18160,18167,18170,18173,18176,18194,18197,18200,18224],[148,18072,18074],{"id":18073},"sql-存个相同列-join-一下不就行了","SQL 存个相同列 join 一下不就行了？",[114,18076,18077],{},"这是我最初的问题。看起来很合理：embedding 不就是一串数字吗，存个字段，SQL join 一下就完事了。",[114,18079,18080,18081,18084,18085,18088],{},"但 SQL 擅长的是 ",[60,18082,18083],{},"WHERE A = B"," 这种精确匹配。Embedding 是高维浮点数组，代表语义特征。两句话语义相似，它们的向量数值却绝不相等。",[60,18086,18087],{},"WHERE Va = Vb"," 直接返回空。",[114,18090,18091],{},"所以向量库真正在做的事情有两层：",[770,18093,18094,18100],{},[476,18095,18096,18099],{},[118,18097,18098],{},"数学计算","：用余弦相似度等算法算高维空间的\"距离\"，而不是判等。",[476,18101,18102,18105],{},[118,18103,18104],{},"专属索引（ANN 算法）","：传统 B-Tree 索引对高维数据失效，会退化成全表扫描。向量库用 HNSW 之类的索引在高维空间建\"地图\"，做到毫秒级近似最近邻检索。",[148,18107,18109],{"id":18108},"那-rag-到底是怎么跑的","那 RAG 到底是怎么跑的",[114,18111,18112],{},"搞清楚向量库的作用之后，整个 RAG 链路就顺了：",[770,18114,18115,18118,18121,18124],{},[476,18116,18117],{},"用户提问 -> 经过 Embedding 模型 -> 转成向量",[476,18119,18120],{},"拿向量去向量库里找最近的文档片段",[476,18122,18123],{},"找到的相关资料 + 用户原问题一起拼成 Prompt 发给大模型",[476,18125,18126],{},"大模型结合资料回答，减少幻觉",[114,18128,18129],{},"核心就是：输入给大模型之前，先用向量检索把相关上下文捞回来，拼在一起再喂进去。",[148,18131,18133],{"id":18132},"全量召回还是选几条","全量召回还是选几条？",[114,18135,18136],{},"这是第二个直觉问题：如果数据库里相关的东西很多，比如 50 页文本都沾边，全部塞进大模型？还是挑几条最相关的？",[114,18138,18139],{},"答案是必须精选截断。原因有三个：",[473,18141,18142,18148,18154],{},[476,18143,18144,18147],{},[118,18145,18146],{},"上下文窗口有限","：大模型的 Context Window 有上限，塞不下就是塞不下。",[476,18149,18150,18153],{},[118,18151,18152],{},"中间迷失","：上下文太长时，大模型容易忽略中间的内容。",[476,18155,18156,18159],{},[118,18157,18158],{},"Token 费用","：输入越长越贵。",[114,18161,18162,18163,18166],{},"所以标准做法是设置 ",[60,18164,18165],{},"top_k","（比如 limit=5），只取相似度最高的前 N 个片段。入库之前也先把长文本切成小块（比如每段 500 字），精准匹配局部内容，避免带入无关段落。",[148,18168,18169],{"id":18169},"但我的数据量没那么大",[114,18171,18172],{},"说到这里我反应过来了--我的复盘日志根本没多少数据。几万字而已，全塞进去不就完了？",[114,18174,18175],{},"确实可以。小数据量下\"全上下文注入\"更简单有效：",[473,18177,18178,18184],{},[476,18179,18180,18183],{},[118,18181,18182],{},"逻辑完整","：保留完整的时间线和因果链，不会因为切片断章取义。",[476,18185,18186,18189,18190,18193],{},[118,18187,18188],{},"架构极简","：直接 SQL ",[60,18191,18192],{},"SELECT *","，全部喂给大模型，不需要向量索引和切片逻辑。",[148,18195,18196],{"id":18196},"那向量库还有没有意义",[114,18198,18199],{},"有。即便是小数据量，向量库（或 SQL 的向量插件）仍然有几个不可替代的能力：",[473,18201,18202,18212,18218],{},[476,18203,18204,18207,18208,18211],{},[118,18205,18206],{},"模糊语义匹配","：日志里写的是\"肝到两点\"或\"披星戴月\"，向量检索能匹配到\"加班\"的语义。传统 SQL ",[60,18209,18210],{},"LIKE"," 搜不到这种。",[476,18213,18214,18217],{},[118,18215,18216],{},"扩展性","：日志会越积越多，一两年后可能就塞不进上下文窗口了。向量检索的成本随数据量亚线性增长，全量注入则是线性上升。",[476,18219,18220,18223],{},[118,18221,18222],{},"聚类与挖掘","：基于向量距离可以做无标签的主题归类和情绪分析，这是全量注入给大模型做不到的。",[114,18225,18226],{},"所以比较务实的方案是：短周期（比如近一周）的分析直接 SQL 全量提取，喂给大模型；跨年度的特定问题搜索走向量匹配。两条路各管各的，不冲突。",{"title":258,"searchDepth":303,"depth":303,"links":18228},[18229,18230,18231,18232,18233],{"id":18073,"depth":303,"text":18074},{"id":18108,"depth":303,"text":18109},{"id":18132,"depth":303,"text":18133},{"id":18169,"depth":303,"text":18169},{"id":18196,"depth":303,"text":18196},"从\"SQL 加个列不就行了\"到理解向量检索和 RAG 的完整链路，以及小数据量场景下到底该怎么选。",{},"\u002Fwriting\u002F2026-07-26-why-vector-database","2026-07-26T00:09:14+08:00",{"title":18068,"description":18234},"why-vector-database","writing\u002F2026-07-26-why-vector-database",[18242,18243,11081],"向量数据库","Embedding","CV6urQnH_Fnpu5wueNjTKv9-xJI0kiwplniuO81pVaw",1790143425228]