-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
558 lines (304 loc) · 276 KB
/
atom.xml
File metadata and controls
558 lines (304 loc) · 276 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>贾攀的流水账</title>
<subtitle>Panmax's Blog</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://jiapan.me/"/>
<updated>2026-02-04T04:48:14.536Z</updated>
<id>https://jiapan.me/</id>
<author>
<name>Panmax</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>【翻译】Portola 如何赋能领域专家提升 AI 质量</title>
<link href="https://jiapan.me/2025/portola/"/>
<id>https://jiapan.me/2025/portola/</id>
<published>2025-11-27T09:40:21.000Z</published>
<updated>2026-02-04T04:48:14.536Z</updated>
<content type="html"><![CDATA[<p>翻译自:<a href="https://www.braintrust.dev/blog/portola" target="_blank" rel="noopener">https://www.braintrust.dev/blog/portola</a></p><h1 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR"></a>TL;DR</h1><p><strong>核心痛点</strong><br>Portola 发现,对于注重情感连接的 AI 伴侣(Tolan),传统的<strong>自动化评估(Automated Evals)无法捕捉对话质量、情商和真实感</strong>等“软性”指标。</p><p><strong>解决方案</strong><br>构建一套基础设施,<strong>赋能非技术领域专家</strong>(行为研究员、科幻作家、游戏设计师)直接掌控 AI 质量优化的全流程,消除工程瓶颈。</p><p><strong>关键流程</strong></p><ol><li><strong>人工审查与发现</strong>:专家通过阅读真实日志发现特定模式(如语气生硬、过度使用俚语)。</li><li><strong>小数据集策略</strong>:不维护庞大的“黄金数据集”,而是针对每个具体问题创建小型、专注的数据集(10-200个示例)。</li><li><strong>手动评估与迭代</strong>:在 Playground 中进行侧重专家直觉的人工对比测试,而非单纯依赖自动化打分。</li><li><strong>直接部署</strong>:专家可将优化后的提示词直接发布到生产环境,无需工程师介入。</li></ol><p><strong>成果</strong></p><ul><li>每周提示词迭代速度<strong>提升 4 倍</strong>。</li><li>系统性改善了记忆调用、对话自然度及敏感话题处理能力。</li><li><strong>结论</strong>:在主观且情感复杂的 AI 领域,必须让懂用户体验的领域专家拥有端到端的修改权限。</li></ul><hr><p>Portola 开发了 <a href="https://www.tolans.com/" target="_blank" rel="noopener">Tolan</a>,这是一款 AI 陪伴应用,旨在为寻求真实、非恋爱 AI 关系的用户充当一位“外星挚友”。与典型的聊天机器人或生产力助手不同,Tolan 专注于通过自然的语音对话和复杂的记忆系统建立真正的情感连接。随着 Portola 团队构建出一个用户可以真正信任的 AI,他们意识到,对话质量、情商和真实行为的细微差别,是无法仅靠自动化评估(automated evals)来捕捉的。</p><p>在这个案例研究中,我们将探讨 Portola 如何构建其工作流程,以赋能非技术背景的领域专家(包括一名行为研究员、一名科幻作家和一名游戏设计师),让他们每天花费数小时审查日志、整理数据集,并直接将改进后的提示词(prompt)发布到生产环境,而无需受制于工程瓶颈。</p><h2 id="挑战:构建真实的-AI-关系"><a href="#挑战:构建真实的-AI-关系" class="headerlink" title="挑战:构建真实的 AI 关系"></a>挑战:构建真实的 AI 关系</h2><p>“人类将如何与 AI 建立健康的关系?”Portola 的首席执行官 Quinten Farmer 问道。“这就是 Tolan 正在探索的问题。”</p><p>创建一个感觉真实、像人一样的 AI 伴侣,需要在心理学、叙事和对话设计方面具备深厚的领域专业知识。该团队确定了三个对建立用户信任至关重要的因素:</p><h3 id="1-真实的记忆"><a href="#1-真实的记忆" class="headerlink" title="1. 真实的记忆"></a>1. 真实的记忆</h3><p>记忆系统的运作方式需要让人感觉真实,就像朋友记事一样。完美的复述能力并不重要,重要的是记住内容的微妙细节、它何时自然地浮现,以及它如何融入对话中。“当你和你的 Tolan 聊天时,如果他们记住了你生活中的某个细节并将其带入对话,那种感觉真的非常特别,”Quinten 解释道。</p><h3 id="2-真实的镜像反应"><a href="#2-真实的镜像反应" class="headerlink" title="2. 真实的镜像反应"></a>2. 真实的镜像反应</h3><p>Tolan 如何反映和回应用户的情绪及沟通风格必须感觉自然,而非算法化。这涉及词汇选择、对话节奏和情感共鸣,这些都无法简化为简单的指标。</p><h3 id="3-避免-AI-恐怖谷效应"><a href="#3-避免-AI-恐怖谷效应" class="headerlink" title="3. 避免 AI 恐怖谷效应"></a>3. 避免 AI 恐怖谷效应</h3><p>某些提问模式和互动行为会立即向用户发出“这是 AI”的信号,从而打破真实连接的幻觉。诸如问太多的“二选一问题”(例如“你想要华夫饼还是煎饼?”)或过度使用 Z 世代俚语等模式,都需要持续的监控和调整。</p><p>Portola 系统的技术复杂性加剧了这些挑战。他们的提示词管道集成了记忆检索、动态生成的用户上下文、实时语音处理以及用户分享的内容(如照片),从而形成连贯的对话流。这种主观的、基于情境的特质使得对话感觉真实。</p><h2 id="工作流程:从监控到部署"><a href="#工作流程:从监控到部署" class="headerlink" title="工作流程:从监控到部署"></a>工作流程:从监控到部署</h2><p>Portola 建立了一个工作流程,使领域专家能够识别问题、整理数据集、测试解决方案并部署变更——所有这些都无需工程团队的交接。其工作原理如下:</p><h3 id="1-模式识别与数据集整理"><a href="#1-模式识别与数据集整理" class="headerlink" title="1. 模式识别与数据集整理"></a>1. 模式识别与数据集整理</h3><p>Lily Doyle 是他们的行为研究员,她每天大约花一个小时在 Braintrust 中阅读聊天日志,寻找对话质量中的模式。“我会寻找形式和功能上的重复模式。这意味着既要看信息是如何发送的,也要看 Tolan 实际上说了什么。我也会留意用户沮丧的任何迹象,”Lily 解释道。</p><p>当 Lily 通过日志审查、用户反馈或焦点小组会议发现一个重复出现的问题时,她会在 Braintrust 中创建一个标记有特定问题的数据集。每个数据集都是真实对话案例的集合,展示了特定的问题。</p><p>她最近在 Braintrust 中追踪的问题示例包括:</p><ul><li><strong><code>somatic-therapy</code>(躯体治疗)</strong>:Tolan 进行了不必要的躯体治疗式提问,如“你在身体上感觉如何?”</li><li><strong><code>or-questions</code>(二选一问题)</strong>:过多的二元选择题,在对话中感觉不自然。</li><li><strong><code>gen-z-lingo</code>(Z 世代俚语)</strong>:过度使用不符合 Tolan 角色设定的流行俚语。</li></ul><p>Portola 并没有维护一个单一的“黄金数据集”,而是在 Braintrust 中创建针对特定问题的数据集,规模从 10 到 200 个示例不等。“搞一个黄金数据集感觉没啥用,”Lily 解释说。“我们使用的是不同的模型,提示词已经改了八次。变化太快了。”</p><p>Braintrust 的数据集管理为 Portola 带来了几个技术优势:</p><ul><li><strong>专注迭代</strong>:每个数据集针对特定的行为模式,使衡量改进变得更容易。</li><li><strong>数据新鲜</strong>:数据集反映当前的产品状态,而不是成为陈旧的快照。</li><li><strong>快速响应</strong>:新问题可以立即解决,无需更新全面的测试套件。</li><li><strong>保留上下文</strong>:每个数据集通过 Braintrust 的追踪(trace)存储保留了完整的对话上下文。</li></ul><h3 id="2-基于-Playground-的迭代"><a href="#2-基于-Playground-的迭代" class="headerlink" title="2. 基于 Playground 的迭代"></a>2. 基于 Playground 的迭代</h3><p>一旦数据集整理完毕,Lily 就会转移到 Playground(演练场)进行并排的提示词比较。她会手动审查当前提示词与迭代版本的输出,利用她的领域专业知识评估对话质量。</p><p>“我们正在处理的很多东西其实是非常‘软性’(squishy)的,”Lily 解释道。“比如针对用户提供的语境向他们提出最好的问题——这不太容易被量化评估。对于那些往往比较‘软性’的东西,我更喜欢手动操作并使用我自己的判断。”</p><p>这种手动评估方法与典型的机器学习工作流程截然不同,但这却是刻意为之。对于 Portola 来说,对话质量从根本上是主观的且依赖于语境。一个自动化评分器可能会标记某个回复太长,但却忽略了该长度在特定语境下创造了情感共鸣。</p><p>Playground 是 Lily 的主要工作空间,她在那里:</p><ul><li>直接从整理好的日志中加载数据集</li><li>在不同提示词版本之间运行比较测试</li><li>综合考量语气、得体性和情商来审查输出</li><li>记录具体的失败或边缘情况</li><li>与 AI 一起迭代以优化提示词</li></ul><h3 id="3-直接部署,无需工程交接"><a href="#3-直接部署,无需工程交接" class="headerlink" title="3. 直接部署,无需工程交接"></a>3. 直接部署,无需工程交接</h3><p>Portola 工作流程的最后一部分是他们的“提示词即代码”(prompts-as-code)基础设施,这使得领域专家一旦对 Playground 的结果感到满意,就可以直接将更改部署到生产环境。</p><p>“我们的科幻作家可以坐下来,看到他不满意的地方,快速针对它进行测试,然后将他的修改部署到生产环境,”Quinten 解释说。“这相当了不起。”</p><p>这种端到端的自主权彻底改变了 Portola 的迭代速度。非技术领域的专家拥有从问题识别到生产部署的完整周期。</p><h2 id="结果:速度与质量的双重提升"><a href="#结果:速度与质量的双重提升" class="headerlink" title="结果:速度与质量的双重提升"></a>结果:速度与质量的双重提升</h2><h3 id="提示词迭代速度提升-4-倍"><a href="#提示词迭代速度提升-4-倍" class="headerlink" title="提示词迭代速度提升 4 倍"></a>提示词迭代速度提升 4 倍</h3><p>在实施此工作流程之前,更改提示词需要领域专家和工程师之间的协调。现在,领域专家可以识别问题、创建数据集、测试解决方案并发布更改,从而使<strong>每周提示词迭代次数达到原来的 4 倍</strong>。</p><h3 id="对话质量的改进"><a href="#对话质量的改进" class="headerlink" title="对话质量的改进"></a>对话质量的改进</h3><p>团队系统地解决了以下方面的边缘情况:</p><ul><li>记忆系统的行为和真实的召回模式</li><li>自然的对话流程和提问模式</li><li>不同语境下的品牌声音一致性</li><li>对心理健康等敏感话题的得体处理</li></ul><p>该工作流程还使 Portola 能够快速处理模型转换,在切换到新模型时迅速识别并修复退化问题。</p><h2 id="主要收获:赋能领域专家"><a href="#主要收获:赋能领域专家" class="headerlink" title="主要收获:赋能领域专家"></a>主要收获:赋能领域专家</h2><p>Portola 的方法表明,为通过主观、情感复杂的领域构建值得信赖的 AI 系统,需要赋能非技术领域的专家来推动质量改进。他们的工作流程提供了几个经验教训:</p><h3 id="1-不要强行对定性工作使用自动化评估"><a href="#1-不要强行对定性工作使用自动化评估" class="headerlink" title="1. 不要强行对定性工作使用自动化评估"></a>1. 不要强行对定性工作使用自动化评估</h3><p>“分数是有用的,但我们正在处理的很多东西其实是非常‘软性’的,”Lily 指出。对于对话质量、情商和品牌声音,来自领域专家的人类判断与自动化指标同等重要。</p><h3 id="2-创建针对特定问题的数据集"><a href="#2-创建针对特定问题的数据集" class="headerlink" title="2. 创建针对特定问题的数据集"></a>2. 创建针对特定问题的数据集</h3><p>不要维护那些会变质的全面测试套件,而是在问题出现时创建针对特定问题的聚焦数据集。这在保持敏捷性的同时,建立了关于边缘情况和失败模式的机构知识。</p><h3 id="3-在高风险领域拥抱人工审查"><a href="#3-在高风险领域拥抱人工审查" class="headerlink" title="3. 在高风险领域拥抱人工审查"></a>3. 在高风险领域拥抱人工审查</h3><p>在构建用于情感复杂领域(如伴侣关系、心理健康支持或治疗语境)的 AI 时,来自领域专家的人工审查至关重要。使这些互动生效的细微差别是无法仅靠指标来捕捉的。</p><p>为领域专家预留时间,让他们花费数小时审查实际使用情况,并使用 Braintrust 中的人工审查等工具来提高这些时间的效率。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>Portola 的工作流程表明,最有能力提高 AI 质量的团队,可能是那些理解你试图创造的用户体验细微差别的非技术领域专家。通过构建基础设施,赋能行为研究员、科幻作家和游戏设计师去识别问题、整理数据集、迭代提示词并将更改直接部署到生产环境,Portola 在迭代速度上实现了 4 倍的提升,同时系统地提高了对话质量。</p><p>如果你正在为自动化评估无法捕捉互动精髓的主观、情感复杂领域构建 AI,请参考 Portola 的做法。投资于可观测性,构建针对特定问题的数据集,并赋能你的领域专家端到端地掌控质量改进周期。</p>]]></content>
<summary type="html">
<p>翻译自:<a href="https://www.braintrust.dev/blog/portola" target="_blank" rel="noopener">https://www.braintrust.dev/blog/portola</a></p>
<h1
</summary>
<category term="LLM" scheme="https://jiapan.me/categories/LLM/"/>
<category term="AI" scheme="https://jiapan.me/categories/LLM/AI/"/>
<category term="翻译" scheme="https://jiapan.me/categories/LLM/AI/%E7%BF%BB%E8%AF%91/"/>
</entry>
<entry>
<title>【翻译】提示工程 (Prompting) 1.1</title>
<link href="https://jiapan.me/2025/prompting-one/"/>
<id>https://jiapan.me/2025/prompting-one/</id>
<published>2025-10-11T09:40:21.000Z</published>
<updated>2026-02-04T04:48:14.586Z</updated>
<content type="html"><![CDATA[<p>提示词结构、提示词层级、元/反向元提示,以及带有示例的基础策略。</p><p>翻译自:<a href="https://docs.lovable.dev/prompting/prompting-one" target="_blank" rel="noopener">https://docs.lovable.dev/prompting/prompting-one</a></p><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR"></a>TL;DR</h2><p>有效使用 AI 进行编程的关键在于<strong>提示工程 (Prompting)</strong>,这是一种需要学习的技能,而不是简单地提问。核心思想是:<strong>像对一个非常聪明但没有常识的初级程序员下达指令一样,做到清晰、具体、有条理。</strong></p><h3 id="核心原则-C-L-E-A-R-框架"><a href="#核心原则-C-L-E-A-R-框架" class="headerlink" title="核心原则 (C.L.E.A.R. 框架)"></a>核心原则 (C.L.E.A.R. 框架)</h3><ul><li><strong>简洁 (Concise):</strong> 直奔主题,不说废话。</li><li><strong>逻辑 (Logical):</strong> 按步骤分解你的请求。</li><li><strong>明确 (Explicit):</strong> 准确说出你想要什么,以及不想要什么。</li><li><strong>适应 (Adaptive):</strong> 第一个结果不完美?迭代优化你的提示词。</li><li><strong>反思 (Reflective):</strong> 总结哪些提示有效,不断改进。</li></ul><h3 id="关键技巧速览"><a href="#关键技巧速览" class="headerlink" title="关键技巧速览"></a>关键技巧速览</h3><ol><li><strong>分步构建:</strong> 不要让 AI 一次性构建整个应用。将大任务分解成“设置数据库”、“创建登录页面”等小步骤,逐步推进。</li><li><strong>提供上下文:</strong> AI 不了解你的项目。在提示中提供背景信息(如技术栈、目标),或使用平台的<strong>知识库</strong>功能来提供持久的上下文。</li><li><strong>结构化你的提示:</strong> 对于复杂任务,使用“<strong>背景、任务、指南、约束</strong>”的结构来组织你的提示,确保所有信息都传达到位。</li><li><strong>提供示例 (Few-Shot Prompting):</strong> 如果你需要特定的代码风格或输出格式,直接在提示中给出几个例子,AI 会模仿它们。</li><li><strong>精确控制,避免幻觉:</strong><ul><li>要修改代码时,明确指出要改<strong>哪个文件</strong>的<strong>哪部分</strong>,并告诉它<strong>不要碰</strong>其他地方。</li><li>为防止 AI 编造不存在的函数或信息(幻觉),请在提示中提供相关的文档或数据作为依据。</li></ul></li><li><strong>让 AI 帮助你:</strong><ul><li><strong>元提示 (Meta Prompting):</strong> 让 AI 帮你改进你自己的提示词。</li><li><strong>反向元提示 (Reverse Meta Prompting):</strong> 在解决问题后,让 AI 总结解决方案,并为你创建一个未来可复用的提示模板。</li></ul></li></ol><p>正文分界线</p><hr><h2 id="重要提示!"><a href="#重要提示!" class="headerlink" title="重要提示!"></a>重要提示!</h2><p>为了帮助您充分利用 Lovable,我们整理了一系列提示策略和方法。其中一些来自我们团队的经验,另一些则由社区成员分享。由于 Lovable 依赖于大型语言模型 (LLM),有效的提示策略可以显著提高其效率和准确性。</p><h2 id="什么是提示工程-Prompting-?"><a href="#什么是提示工程-Prompting-?" class="headerlink" title="什么是提示工程 (Prompting)?"></a>什么是提示工程 (Prompting)?</h2><p>提示工程指的是您为执行某项任务而向 AI 系统发出的文本指令。在 Lovable(一个由 AI 驱动的应用构建器)中,提示就是您“告诉”AI 要做什么的方式——从创建用户界面到编写后端逻辑。有效的提示至关重要,因为 Lovable 使用大型语言模型 (LLM),所以清晰、精心设计的提示可以极大地提高 AI 构建应用时的效率和准确性。简而言之,更好的提示带来更好的结果。</p><h2 id="为什么提示工程很重要"><a href="#为什么提示工程很重要" class="headerlink" title="为什么提示工程很重要"></a>为什么提示工程很重要</h2><p>大多数人认为提示工程只是向 AI 输入一个请求并期待最好的结果——<strong>事实并非如此</strong>。一个平庸的 AI 响应与让 AI 为您构建完整工作流之间的区别,就在于<em>您如何提示</em>。无论您是开发人员还是非技术人员,掌握在 <strong>Lovable</strong> 中的提示工程都可以帮助您:</p><ul><li><strong>自动化重复性任务</strong>,通过精确指示 AI 该做什么。</li><li>借助 AI 生成的见解和解决方案<strong>更快地调试</strong>。</li><li><strong>轻松构建和优化工作流</strong>,在适当引导后让 AI 处理繁重的工作。</li></ul><p>最棒的是,您不需要成为编程专家。使用正确的提示技巧,您就可以释放 Lovable 中 AI 的全部潜力,而无需浪费精力在试错上。本指南将带您从基础概念走到高级提示策略,以便您能有效地与 AI 沟通并更快地构建。</p><h2 id="理解-AI-的思考方式"><a href="#理解-AI-的思考方式" class="headerlink" title="理解 AI 的思考方式"></a>理解 AI 的思考方式</h2><p>与传统编码不同,与 AI 协作的关键在于清晰地<em>沟通</em>您的意图。为 Lovable 提供支持的大型语言模型 (LLM) 并非以人类的方式“理解”——它们根据训练数据中的模式来预测输出。这对您的提示方式有重要影响:为了获得一致的结果,最好将您的提示结构化为清晰的几个部分。一个推荐的格式(如同提示的“<em>辅助轮</em>”)是使用标记的部分来区分<strong>背景 (Context)</strong>、<strong>任务 (Task)</strong>、<strong>指南 (Guidelines)</strong> 和<strong>约束 (Constraints)</strong>:</p><ul><li><strong>提供背景和细节:</strong> AI 模型除了您提供的信息外,没有常识或隐含的背景信息。务必提供相关的背景信息或需求。例如,不要只说“构建一个登录页面”,而要详细说明:“<em>使用 React 创建一个登录页面,包含电子邮件/密码认证和 JWT 处理。</em>” 明确包含任何技术栈或工具(例如,“使用 Supabase 进行身份验证”)。</li><li><strong>明确指示和约束:</strong> 切勿假设 AI 会推断您的目标。如果您有约束条件或偏好,请明确说明。例如,如果输出应使用特定库或保持在某个范围内,<strong>请预先告知模型</strong>。AI 会<strong>严格按照字面意思</strong>遵循您的指示——模糊不清可能导致不想要的结果或 AI“幻觉”(捏造的信息)。</li><li><strong>结构很重要(顺序和重点):</strong> 得益于 Transformer 架构,模型会特别关注您提示的<em>开头和结尾</em>。利用这一点,将最关键细节或请求放在开头,并在必要时在结尾重申任何绝对要求。同时请记住,模型有一个固定的<strong>上下文窗口 (context window)</strong>——过长的提示或非常长的对话可能会导致 AI 忘记先前的细节。保持提示聚焦,并在必要时刷新上下文(例如,如果会话较长,提醒模型关键点)。</li><li><strong>了解模型的限制:</strong> AI 的知识来源于训练数据。它无法了解近期事件或您未提供的专有信息。即使是在猜测(这会导致幻觉),它也会试图表现得<em>很自信</em>。对于事实性查询,请务必提供参考文本或数据,或者准备好验证其输出。</li></ul><p>将提示工程视为告诉一个非常字面思维的实习生您<em>确切</em>需要什么。您的指导越清晰、越有结构,结果就越好。接下来,我们将深入探讨使提示有效的核心原则。</p><h2 id="核心提示原则:C-L-E-A-R-框架"><a href="#核心提示原则:C-L-E-A-R-框架" class="headerlink" title="核心提示原则:C.L.E.A.R. 框架"></a>核心提示原则:C.L.E.A.R. 框架</h2><p>优秀的提示遵循一套简单的原则。一个便于记忆的方法是 <strong>CLEAR</strong>:<strong>简洁 (Concise)、逻辑 (Logical)、明确 (Explicit)、适应 (Adaptive)、反思 (Reflective)</strong>。在构思指令时,请使用这个清单:</p><ul><li><strong>简洁 (Concise):</strong> 清晰明了,直奔主题。多余的废话或模糊的语言会使模型困惑。使用直接的语言:例如,<strong>糟糕的示例:</strong>“你能不能写点关于科学主题的东西?” <strong>优秀的示例:</strong>“<strong>撰写一篇 200 词关于气候变化对沿海城市影响的摘要</strong>。” 避免填充词——如果某个细节不具有指导性,那么它就是干扰。在描述您所需内容时力求精确和简洁。</li><li><strong>逻辑 (Logical):</strong> 以循序渐进或结构良好的方式组织您的提示。将复杂请求分解为有序的步骤或要点,以便 AI 轻松跟随。不要使用一个冗长的连续请求,而是将不同关注点分开。<strong>糟糕的示例:</strong>“给我构建一个用户注册功能,同时显示一些使用统计信息。” <strong>优秀的示例:</strong> <em>“首先,使用 Supabase 实现一个带有电子邮件和密码的用户注册表单。然后,在成功注册后,显示一个包含用户数量统计信息的仪表板。”</em> 逻辑流程确保模型系统地处理您请求的每个部分。</li><li><strong>明确 (Explicit):</strong> 确切说明您想要什么和不想要什么。如果某些事情很重要,请明确说明。如果可能,提供格式或内容的示例。模型拥有海量知识,但它不会读心术来了解具体细节。<strong>糟糕的示例:</strong>“告诉我关于狗的事情。”(太开放了。)<strong>优秀的示例:</strong>“<strong>以要点形式列出关于金毛寻回犬的 5 个独特事实。</strong>” 同样,如果您有期望的输出风格,请说明(例如,“以 JSON 格式回复”或“使用随意语气”)。像对待初学者一样对待 AI:假设所有事情对它都不显而易见。</li><li><strong>适应 (Adaptive):</strong> 如果第一个答案不完美,不要满足——提示可以<strong>迭代地</strong>改进。Lovable 的 AI(以及一般的 LLM)的一大优势是您可以进行对话。如果初始输出不符合要求,<em>调整您的方法</em>:在后续提示中澄清指令或指出错误。例如,“你给出的解决方案缺少认证步骤。请在代码中包含用户身份验证。” 通过迭代,您可以引导模型获得更好的结果。您甚至可以询问 AI 如何改进提示本身(这就是<strong>元提示 (Meta Prompting)</strong>,稍后介绍)。</li><li><strong>反思 (Reflective):</strong> 在每次与 AI 交互后,花时间回顾哪些方法有效,哪些无效。这更多是关于<em>您</em>而不是模型——作为提示工程师,注意哪种提示措辞获得了良好结果,哪种导致了混淆。在复杂的会话之后,您甚至可以要求 AI <strong>总结最终解决方案或推理过程</strong>(我们稍后会讨论<strong>反向元提示 (Reverse Meta Prompting)</strong>)。进行反思有助于您在未来构思更好的提示,在您的 AI 沟通中建立持续改进的循环。</li></ul><p>在构思提示时请牢记这些 CLEAR 原则。接下来,我们将看看从基础到高级的具体提示技巧,包括如何构建提示以及利用 AI 作为协作者。</p><h2 id="提示的四个层级"><a href="#提示的四个层级" class="headerlink" title="提示的四个层级"></a>提示的四个层级</h2><p>有效的提示是一项随着实践而增长的技能。这里我们概述了提示掌握的四个层级,从结构化的“辅助轮”到高级的元技巧。每个层级都有其适用场景——根据需要组合使用:</p><h3 id="1-结构化“辅助轮”提示-Explicit-Format"><a href="#1-结构化“辅助轮”提示-Explicit-Format" class="headerlink" title="1. 结构化“辅助轮”提示 (Explicit Format)"></a>1. 结构化“辅助轮”提示 (Explicit Format)</h3><p>当您刚刚开始或处理非常复杂的任务时,在提示中使用标记结构会很有帮助。这就像是<em>辅助轮</em>,确保您提供所有必要信息。在 Lovable 中一个行之有效的格式是将提示分解为以下几个部分:</p><blockquote><ul><li><strong>背景 (Context):</strong> 为 AI 设置的背景信息或角色。(例如,“您是一个世界级的 Lovable AI 编码助手。”)</li><li><strong>任务 (Task):</strong> 您想要实现的具体目标。(例如,“构建一个具有用户登录和实时同步功能的全栈待办事项列表应用。”)</li><li><strong>指南 (Guidelines):</strong> 首选的方法或风格。(例如,“使用 React 作为前端,Tailwind 进行样式设计,Supabase 用于身份验证和数据库。”)</li><li><strong>约束 (Constraints):</strong> 严格的限制或禁止事项。(例如,“不要使用任何付费 API。应用应在移动设备和桌面上运行。”)</li></ul></blockquote><p>通过清晰地标记每个部分,您几乎不给误解留下空间。例如,一个提示可能看起来像:</p><blockquote><p><strong>背景:</strong> 您是一位使用 Lovable 的专家全栈开发人员。<br><strong>任务:</strong> 在 React 中使用 Supabase(电子邮件/密码认证)创建一个安全的登录页面。<br><strong>指南:</strong> UI 应极简,并遵循 Tailwind CSS 规范。为每个步骤提供清晰的代码注释。<br><strong>约束:</strong> 仅修改 <code>LoginPage</code> 组件;不要更改其他页面。确保最终输出在 Lovable 编辑器中是一个可工作的页面。</p></blockquote><p>这种详细程度可以逐步指导 AI。<em>辅助轮提示</em>对于新手或复杂的多部分任务非常出色——它迫使您仔细思考您到底需要什么,并通过结构化请求来帮助模型。</p><h3 id="2-对话式提示-No-Training-Wheels"><a href="#2-对话式提示-No-Training-Wheels" class="headerlink" title="2. 对话式提示 (No Training Wheels)"></a>2. 对话式提示 (No Training Wheels)</h3><p>当您感到得心应手时,您并不总是需要如此僵化的结构。<strong>对话式提示</strong>意味着您可以更自然地向 AI 书写,类似于您向同事解释任务的方式,同时仍保持清晰。关键在于<strong>无需</strong>正式标签也能保持清晰和完整。例如:</p><blockquote><p>我们来构建一个上传个人资料图片的功能。它应该包含一个带有图片文件输入和提交按钮的表单。提交时,应将图片存储在 Supabase storage 中并更新用户个人资料。请编写必要的 React 组件和任何所需的后端函数,并确保优雅地处理错误(例如文件过大)。</p></blockquote><p>这是一个更自由形式的提示,但仍然<strong>逻辑有序且明确</strong>地说明了要求。没有辅助轮,但它仍然有效。一旦您相信自己不会遗漏重要细节,对话式提示就能很好地工作。它们使交互更加自然,尤其是在持续聊天中迭代结果时。</p><blockquote><p>即使在对话风格中,您也可以通过将请求的不同方面分成段落或要点来模拟结构。目标是相同的:清晰的沟通。对于较快的任务,或者在 AI 已经被赋予背景信息之后,您可能会使用这种风格。</p></blockquote><h3 id="3-元提示-Meta-Prompting"><a href="#3-元提示-Meta-Prompting" class="headerlink" title="3. 元提示 (Meta Prompting)"></a>3. <strong>元提示 (Meta Prompting)</strong></h3><p>这是一种高级技巧,您直接要求 AI 帮助您改进提示或计划。由于 Lovable 的 AI(如 ChatGPT)能够对语言进行推理,您可以用它来优化您的指令。这在您得到的输出偏离目标时特别有用——这可能表明您的提示不清楚。例如:</p><blockquote><p>审查我上一个提示,找出任何模糊或缺失的信息。我该如何重写它才能更简洁和精确?</p></blockquote><blockquote><p>重写这个提示,使其更具体和详细:“在 React 中使用 Supabase 创建一个安全的登录页面,确保基于角色的身份验证。”</p></blockquote><p>AI 可能会回应一个结构更好或更详细的请求版本。这可以揭示哪些地方不清楚。本质上,您是让 AI 充当<em>提示编辑器</em>。在 Lovable 中,您可以在<strong>聊天模式</strong>下安全地执行此操作(因为聊天模式不会直接编辑您的项目)。元提示将 AI 转变为协作者,帮助您询问真正想要的东西。这是一种引导您提示工程技能的强大方式——AI 可以建议您未曾考虑过的改进。</p><h3 id="4-反向元提示-Reverse-Meta-Prompting"><a href="#4-反向元提示-Reverse-Meta-Prompting" class="headerlink" title="4. 反向元提示 (Reverse Meta Prompting)"></a>4. <strong>反向元提示 (Reverse Meta Prompting)</strong></h3><p>反向元提示是指在<em>任务完成后</em>使用 AI 来总结或记录所发生的事情,以便您以后学习或重用。可以将其视为要求 AI 反思过程并为您下次提供提示或解释。这对于调试和知识捕获非常有用。例如,在您解决了 Lovable 中的一个棘手问题后,您可以提示:</p><blockquote><p>总结我们在设置 JWT 身份验证时遇到的错误,并解释我们是如何解决的。然后,起草一个我将来在设置身份验证时可以使用的提示,以避免这些错误。</p></blockquote><p>AI 可能会生成问题及其解决方案的简要重述,然后是一个模板提示,如 <em>“背景:构建身份验证… 任务:通过执行 Y 操作来避免 X 错误…”</em>。这种反向元方法帮助您构建一个<strong>可重用提示</strong>和经验教训的个人库。在 Lovable 中,这可能是无价之宝:下次您面临类似任务时,您就有一个经过验证的提示可以使用(或者至少有一个清晰的清单可以遵循)。</p><blockquote><p>假设您花了一个小时调试 API 调用失败的原因。一旦修复,要求 AI 记录下这一点。您不仅巩固了自己的理解,还创建了可以输入到知识库或未来项目中的材料,这样 AI 就不会重复同样的错误。</p></blockquote><h2 id="高级提示技巧"><a href="#高级提示技巧" class="headerlink" title="高级提示技巧"></a>高级提示技巧</h2><p>一旦掌握了基础知识,就可以利用更高级的策略来充分发挥 Lovable AI 的潜力。这些技巧有助于处理复杂场景、减少错误(如幻觉),并根据您的需求定制 AI 的输出。</p><h3 id="零样本-Zero-Shot-vs-少样本-Few-Shot-提示"><a href="#零样本-Zero-Shot-vs-少样本-Few-Shot-提示" class="headerlink" title="零样本 (Zero-Shot) vs. 少样本 (Few-Shot) 提示"></a>零样本 (Zero-Shot) vs. 少样本 (Few-Shot) 提示</h3><p><strong>零样本提示 (Zero-Shot Prompting)</strong> 意味着您要求模型执行任务而<em>不提供任何示例</em>。您依赖模型的通用训练来知道该做什么。这是大多数提示的默认方式:您陈述请求,AI 纯粹根据其“已知”信息和对您提示的理解来生成答案。零样本提示效率高,如果任务是常见的或描述清晰,效果很好。例如:<em>“将以下句子翻译成西班牙语:‘我正在学习编程。’”</em> 就是一个零样本提示——直接的命令,AI 使用其知识来回应(不需要示例)。</p><p><strong>少样本提示 (Few-Shot Prompting)</strong> 意味着您在提示中提供几个<strong>示例或演示</strong>,向 AI 展示您想要的准确格式或风格。本质上,您是在提示本身中进行示例教学。这对于特定格式或任务不常见的情况,可以显著提高输出质量。在少样本提示中,您可能会说:</p><blockquote><p>纠正这些句子的语法:\<br><strong>输入:</strong>“the code not working good” → <strong>输出:</strong>“The code is not working well.”</p><p><strong>输入:</strong>“API give error in login” → <strong>输出:</strong>“The API gives an error during login.”</p><p>现在 <strong>输入:</strong>“user not found in database” → <strong>输出:</strong></p></blockquote><p>通过给出两个输入-输出的例子,AI 被引导以类似的模式继续处理第三个。少样本提示在 Lovable 中非常有用,当您需要特定风格的响应时(例如,某种格式的代码注释,或提交消息示例)。它确实会消耗更多的提示 token(因为您包含了那些示例),但通常能产生更一致的结果。</p><p><strong>何时使用哪种:</strong>对于简单的任务或当您信任模型的内置能力时,先尝试零样本提示。如果结果不符合您想要的格式或深度,则通过添加示例切换到少样本提示。例如,如果您请求一个函数而输出没有遵循您偏好的风格,请展示一个您喜欢风格的示例函数,然后再次提示。少样本提示在处理复杂输出(如编写测试用例——提供一个示例测试,然后要求编写更多)时表现出色。总之,<strong>零样本用于快速直接的回答,少样本用于受控风格或复杂指令。</strong></p><h3 id="管理幻觉并确保准确性"><a href="#管理幻觉并确保准确性" class="headerlink" title="管理幻觉并确保准确性"></a>管理幻觉并确保准确性</h3><p>AI“幻觉”是指模型自信地编造不正确信息或代码的时刻。在像 Lovable 这样的编码平台中,幻觉可能意味着 AI 使用了不存在的函数、调用了不存在的 API,或在摘要中捏造细节。虽然我们无法完全消除这种情况(这是 AI 的局限性),但我们<strong>可以通过提示方式来减少幻觉</strong>:</p><ul><li><strong>提供基础数据:</strong> 您提供的<em>可靠上下文</em>越多,AI 需要猜测的内容就越少。在 Lovable 中,始终利用您项目的<strong>知识库</strong>。在项目的上下文中包含您的项目需求文档 (PRD)、用户流程、技术栈等。这样,AI 的答案将“基于”<em>您的</em>应用的具体情况。例如,如果您的应用使用了某个特定库或定义了数据模型,请将其放入知识库,这样 AI 就不会编造不同的内容。</li><li><strong>提示内引用:</strong> 当询问事实性问题或与外部系统交互的代码时,包含相关的文档片段或数据。例如,“<em>使用下面给出的 API 响应格式,解析用户对象……[然后包含一个小的 JSON 示例]。</em>” 通过向 AI 展示真实数据或文档,它编造函数或字段的可能性就会降低。</li><li><strong>要求逐步推理:</strong> 有时您怀疑 AI 可能是在即兴发挥。在这种情况下,提示它展示其推理或验证过程。例如,在<strong>聊天模式</strong>下,您可以说:“<em>在给出最终代码之前,解释您的解决方案思路。如果有任何不确定之处,请说明。</em>” 这种“思维链”提示使 AI 放慢速度并自我检查。它可以在推理过程中发现错误或至少揭示错误,然后您可以进行纠正。</li><li><strong>指示诚实:</strong> 您可以在提示中包含一条指南,例如“<em>如果您不确定某个事实或正确代码,请不要捏造——相反,请解释需要什么或请求澄清。</em>” 高级模型通常会遵循此类指令(它们可能会回应:“我不确定,但我假设 X……”而不是直接给出错误答案)。这并非万无一失,但可以减轻自信地输出错误信息的情况。</li><li><strong>迭代验证:</strong> 在 AI 给出答案后,尤其是对于关键内容(如计算、重要事实或复杂代码),执行一个验证步骤。您可以询问 AI,或使用其他工具来双重检查输出。例如:“<em>确认上述代码符合要求,并解释任何可能不符合规范的部分。</em>” 这个提示让 AI 审查其工作,通常如果它偏离了您的指令,它会发现。</li></ul><p>在 Lovable 中,幻觉也可能意味着 AI 创建了您未要求的文件或组件,或者采取了一些未经授权的创意自由。始终审查 AI 生成的代码以确保合理性。如果某些东西看起来太“神奇”或出乎意料,请质疑它。通过使用这些策略管理幻觉,您可以保持对项目的控制并确保准确性。</p><h3 id="利用模型洞察(了解您的-AI-工具)"><a href="#利用模型洞察(了解您的-AI-工具)" class="headerlink" title="利用模型洞察(了解您的 AI 工具)"></a>利用模型洞察(了解您的 AI 工具)</h3><p>并非所有 AI 模型都是一样的,即使是同一个模型,根据设置的不同,行为也可能不同。为了获得大师级的结果,了解您在 Lovable 中可以使用的工具会有所帮助:</p><ul><li><strong>聊天模式 vs 默认模式:</strong> Lovable 提供(截至本文撰写时)一个<strong>聊天模式</strong>(对话式 AI 助手)和一个默认/编辑器模式(直接应用更改)。请有意识地使用它们。<strong>聊天模式</strong>非常适合头脑风暴、讨论设计决策或调试——AI 可以自由生成想法或进行分析,而无需立即编码。例如,您可以描述一个错误,并在聊天模式中说:“<em>让我们分析这个错误日志,找出问题所在。</em>” AI 然后可以逐步排查潜在原因。另一方面,<strong>默认模式</strong>用于执行更改(编写代码、创建组件)。一个典型的工作流程可能是:在聊天模式下概述或排除故障,一旦有了计划,切换到默认模式,使用直接的提示来实施它(因为默认模式会修改您的项目文件)。知道何时使用每种模式可以保持您的开发流程高效且安全。</li><li><strong>Token 长度和响应:</strong> 注意响应长度。如果您请求一个非常大的输出(比如整个代码模块),如果超过 token 限制,AI 可能会截断或失去连贯性。在这种情况下,将任务分解为更小的提示(例如,一次生成一个函数的代码)。如果输出被截断,Lovable 的聊天或提示 UI 可能会显示警告——这表明您需要请求剩余部分或划分工作。</li><li><strong>格式和代码偏好:</strong> 如果您声明了格式偏好,AI 可以适应。例如,告诉它“以 markdown 格式输出代码”,或者“遵循项目的 ESLint 规则”(如果您有的话)。除非您将其包含在上下文中,否则它不会神奇地知道您的风格指南。如果您偏好某些命名约定或模式,可以在提示中提及(这是“明确”原则的一部分)。随着时间的推移,当 AI 在您的项目中看到一致的风格时,它会模仿——但在提示中给予温和的提醒可以加速这种对齐。</li></ul><p>总之,将 AI 视为一个强大但非常较真的工具。了解您正在交互的模式和模型,并始终构建您的提示以发挥其优势(结构化、详细的输入),同时防范其弱点(健忘、冗长、幻觉)。现在,让我们将这些原则转化为在 Lovable 中有效使用的具体最佳实践。</p><h2 id="其他提示技巧"><a href="#其他提示技巧" class="headerlink" title="其他提示技巧"></a><strong>其他提示技巧</strong></h2><p>最后,我们来介绍在 Lovable 平台中工作时的一些具体技巧和技术。这些最佳实践结合了通用的提示工程概念和 Lovable 的特性,以帮助您获得最佳结果。</p><h3 id="从坚实的知识库开始"><a href="#从坚实的知识库开始" class="headerlink" title="从坚实的知识库开始"></a>从坚实的知识库开始</h3><p>在您甚至编写提示之前,请先设置您项目的知识库(在 Lovable 的项目设置中)。包括<strong>项目需求文档 (PRD)</strong>、用户流程、技术栈详情、UI 设计指南以及任何后端细节。这充当了 AI 始终拥有的持久上下文。例如,如果您的 PRD 明确列出“超出范围:社交登录”,那么 AI 就不太可能随机添加 Google 登录功能。您也可以在开始时明确提示:</p><blockquote><p><em>在编写任何代码之前,请阅读项目知识库并确认您理解应用的目的和约束。</em></p></blockquote><p>这确保了 AI 内化您项目的上下文,并减少不相关的建议或幻觉产生的功能。</p><h3 id="要具体,避免模糊"><a href="#要具体,避免模糊" class="headerlink" title="要具体,避免模糊"></a>要具体,避免模糊</h3><p>模糊的提示导致模糊的结果。始终澄清<em>您想要什么</em>以及<em>如何实现</em>。</p><p><strong>不要这样做:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">让这个应用变得更好。</span><br></pre></td></tr></table></figure><p><strong>另一个例子:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">创建一个用于用户输入的表单。</span><br></pre></td></tr></table></figure><p><strong>应该这样做:</strong></p><p>后者明确了范围和预期结果。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">重构应用以清理未使用的组件并提高性能,但不改变 UI 或功能。</span><br></pre></td></tr></table></figure><p><strong>另一个例子:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">创建一个用户注册表单,包含用户名字段、电子邮件字段和密码字段,并包含一个提交按钮。</span><br></pre></td></tr></table></figure><h3 id="渐进式提示"><a href="#渐进式提示" class="headerlink" title="渐进式提示"></a>渐进式提示</h3><p>抵制在一个提示中要求构建整个复杂应用的冲动。将您的开发过程分解为逻辑步骤,并一次提示一个步骤。</p><p><strong>不要这样做:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">构建一个带有 Supabase、身份验证、Google Sheets 导出和数据增强功能的 CRM 应用。</span><br><span class="line"></span><br><span class="line">构建我的整个电子商务应用,包含身份验证、产品列表和结账功能。</span><br></pre></td></tr></table></figure><p><strong>应该这样做:</strong>这种循序渐进的进程有助于 AI 保持专注和准确,并且您可以及早发现问题:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">设置一个连接到 Supabase 的 CRM 后端。</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">很好!能否请您添加一个带有用户角色的安全身份验证流程?</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">谢谢!下一步是集成 Google Sheets 来导出记录。</span><br></pre></td></tr></table></figure><p><strong>另一个例子:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">为用户信息设置数据库模式。</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">请开发一个用于检索用户数据的 API 端点。</span><br></pre></td></tr></table></figure><h3 id="包含约束和要求"><a href="#包含约束和要求" class="headerlink" title="包含约束和要求"></a>包含约束和要求</h3><p>不要回避阐明约束。如果某事<em>必须</em>或<em>绝不能</em>做,请明确说明。</p><p><strong>添加约束</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">创建一个简单的待办事项应用,最多同时显示 3 个任务。</span><br><span class="line">包括添加、编辑和删除任务的功能。</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">优化此代码,但确保 UI 和核心功能保持不变。记录您所做的每一项更改。</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">为此最多使用 3 次 API 调用,并确保不需要外部库。</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">页面应一次最多显示 3 个任务。</span><br></pre></td></tr></table></figure><p>这样的限制可以防止 AI 过度设计。添加像最大项目数或性能目标这样的约束可以使 AI 专注于重要的事情。</p><h3 id="避免措辞模糊"><a href="#避免措辞模糊" class="headerlink" title="避免措辞模糊"></a>避免措辞模糊</h3><p>如果一个术语可能有不同的解释,请澄清它。您越清晰,AI 需要猜测的内容就越少。</p><p><strong>不要这样做:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">添加一个个人资料功能</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">支持通知</span><br></pre></td></tr></table></figure><p><strong>应该这样做:</strong></p><p>后者明确了范围和预期结果。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">添加一个包含 X、Y、Z 字段的用户个人资料页面。</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">在表单提交时发送电子邮件通知。</span><br></pre></td></tr></table></figure><h3 id="注意语气和礼貌"><a href="#注意语气和礼貌" class="headerlink" title="注意语气和礼貌"></a>注意语气和礼貌</h3><p>虽然这不会改变功能,但礼貌的语气有时会产生更好的结果。像“请”或尊重的请求这样的短语可以添加上下文,并使提示更具描述性,这有助于 AI。例如,</p><blockquote><p><em>请不要修改主页,仅专注于仪表板组件。</em></p></blockquote><p>这读起来很礼貌,并且明确告诉 AI 不要做什么。这与 AI 的感受无关——而是关于融入细节。(另外,保持礼貌总是好的!)</p><h3 id="有意识地使用-Lovable-的模式"><a href="#有意识地使用-Lovable-的模式" class="headerlink" title="有意识地使用 Lovable 的模式"></a>有意识地使用 Lovable 的模式</h3><p>如前所述,利用<strong>聊天模式</strong>进行规划,使用<strong>默认模式</strong>进行构建。例如,当开始一个新功能时,您可以进入聊天模式并讨论组件结构:</p><blockquote><p>我想在我的应用中添加一个博客部分。让我们讨论一下如何构建数据和页面。</p></blockquote><p>AI 可能会回应一个提纲。一旦您满意,可以切换到默认模式并说:</p><blockquote><p>根据上述计划,创建一个 <code>BlogPost</code> 页面和一个用于博客文章的 supabase 表或模式。</p></blockquote><h3 id="利用格式优势"><a href="#利用格式优势" class="headerlink" title="利用格式优势"></a>利用格式优势</h3><p>在适当的时候结构化列表或步骤。如果您希望 AI 输出列表或遵循一个序列,请在提示中枚举它们。通过编号步骤,您暗示 AI 以类似方式回应。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">让我们逐步思考如何建立一个安全的身份验证系统:</span><br><span class="line"></span><br><span class="line">1. 必要的组件有哪些?</span><br><span class="line">2. 它们应该如何交互?</span><br><span class="line">3. 提供实现代码。</span><br></pre></td></tr></table></figure><blockquote><p><em>首先,解释方法。其次,展示代码。第三,给出一个测试示例。</em></p></blockquote><h3 id="利用示例或参考"><a href="#利用示例或参考" class="headerlink" title="利用示例或参考"></a>利用示例或参考</h3><p>如果您有目标设计或代码风格,请提及它或提供一个示例。提供示例(图像或代码片段)给 AI 一个具体的参考来模仿。</p><p><strong>设置背景</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">我们正在构建一个帮助团队跟踪任务的项目管理工具。</span><br><span class="line">这个工具应该具有以下功能:</span><br><span class="line"></span><br><span class="line"> - 用户身份验证</span><br><span class="line"> - 项目创建</span><br><span class="line"> - 任务分配</span><br><span class="line"> - 报告</span><br><span class="line"></span><br><span class="line">现在,作为第一个任务,创建项目创建的 UI。</span><br></pre></td></tr></table></figure><p><strong>另一个例子:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">我需要一个带有 Supabase 集成和安全身份验证流程的 CRM 应用。首先设置后端。</span><br></pre></td></tr></table></figure><p><strong>另一个例子:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">我们正在开发一个专注于环保产品的电子商务平台。生成一个带有类别和价格筛选器的产品列表页面。</span><br></pre></td></tr></table></figure><h3 id="使用图片提示"><a href="#使用图片提示" class="headerlink" title="使用图片提示"></a>使用图片提示</h3><p>Lovable 甚至允许在提示中上传图片,因此您可以展示一个设计并说“匹配这种风格”。这里主要有两种方法。第一种是简单的图片上传提示。</p><p><strong>简单的图片上传提示</strong></p><p>您可以上传一张图片,然后添加一个示例提示,如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">创建并实现一个与附件图片尽可能相似的 UI。</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">此屏幕截图显示了移动设备上的布局问题。调整边距和内边距以使其具有响应性,同时保持相同的设计结构。</span><br></pre></td></tr></table></figure><p>或者,您可以帮助 AI 更好地理解图片内容及其一些附加细节。通过向上传的图片添加具体指令,可以获得出色的效果。虽然一张图片胜过千言万语,但用几句话描述所需的功能会大有帮助——尤其是因为交互可能无法从静态图片中显而易见。</p><p><strong>带有详细说明的图片提示</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">我希望您创建一个与屏幕截图中所示应用尽可能相似的应用。</span><br><span class="line">它本质上是一个看板 (Kanban) 克隆。</span><br><span class="line">它应该能够在每一列中添加新卡片(票证),能够更改单个列中这些票证的顺序,甚至能够在列之间移动这些卡片。</span><br><span class="line">请随意使用 Pangea home dnd npm 包来实现拖放功能。</span><br></pre></td></tr></table></figure><h3 id="反馈整合"><a href="#反馈整合" class="headerlink" title="反馈整合"></a>反馈整合</h3><p>审查 AI 的输出并提供具体反馈以进行改进。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">登录表单看起来不错,但请为电子邮件字段添加验证,以确保它包含有效的电子邮件地址。</span><br></pre></td></tr></table></figure><h3 id="强调可访问性"><a href="#强调可访问性" class="headerlink" title="强调可访问性"></a>强调可访问性</h3><p>鼓励生成符合可访问性标准和现代最佳实践的代码。这确保输出不仅功能正常,而且用户友好并符合可访问性指南。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">生成一个遵循可访问性最佳实践的 React 登录表单组件,包括适当的 ARIA 标签和键盘导航支持。</span><br></pre></td></tr></table></figure><h3 id="预定义组件和库"><a href="#预定义组件和库" class="headerlink" title="预定义组件和库"></a>预定义组件和库</h3><p>指定使用某些 UI 库或组件,以保持项目的一致性和效率。这指示 AI 使用特定工具,确保应用程序的兼容性和统一的设计语言。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">使用 shadcn/ui 库和 Tailwind CSS 样式创建一个响应式导航栏。</span><br></pre></td></tr></table></figure><h3 id="多语言提示"><a href="#多语言提示" class="headerlink" title="多语言提示"></a>多语言提示</h3><p>在多语言环境中工作时,指定代码注释和文档所需的语言。这确保生成的内容对讲不同语言的团队成员可访问,从而增强协作。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">生成一个计算斐波那-契数列的 Python 脚本。用法语提供注释和文档。</span><br></pre></td></tr></table></figure><h3 id="定义项目结构和文件管理"><a href="#定义项目结构和文件管理" class="headerlink" title="定义项目结构和文件管理"></a>定义项目结构和文件管理</h3><p>清晰勾勒项目结构,包括文件名和路径,以确保生成有组织且可维护的代码。这明确了新组件在项目中的存放位置,保持了连贯的文件组织。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">创建一个名为 'UserProfile' 的新 React 组件,并将其保存为 'components/user-profile.tsx'。确保它包含个人资料图片、用户名和简介部分。</span><br></pre></td></tr></table></figure><h3 id="提供精确的编辑指令(聚焦-AI)"><a href="#提供精确的编辑指令(聚焦-AI)" class="headerlink" title="提供精确的编辑指令(聚焦 AI)"></a>提供精确的编辑指令(聚焦 AI)</h3><p>默认情况下,当您要求 Lovable AI 更改某些内容时,它可能会重写整个文件或多个文件。为避免意外更改,请非常具体地说明<em>在哪里</em>和<em>更改什么</em>。您可以使用 Lovable 的“选择”功能高亮显示某个组件或文件,然后仅就该选择部分进行提示。或者在您的提示中明确指定文件/组件名称。例如:</p><blockquote><p>在 <code>Header</code> 组件中,将注册按钮的文本改为‘Get Started’,并将其移动到导航栏的左侧。</p></blockquote><p>这样,AI 就知道专注于 <code>Header</code> 组件并仅调整该部分。另一个技巧:<strong>告诉 AI 不要碰什么</strong>。您可以补充说,“不要修改与标题无关的任何其他组件或逻辑。” 这可以防止 AI 偏离主题并可能破坏其他东西。这种实践(有时称为“Diff & Select”方法)确保了最小化、针对性的更改——从而带来更快的响应和更少的回归错误。</p><h3 id="锁定文件(变通方法)"><a href="#锁定文件(变通方法)" class="headerlink" title="锁定文件(变通方法)"></a>锁定文件(变通方法)</h3><p>目前,Lovable 可能没有明确的文件锁定功能,但您可以通过提示措辞来模拟它。如果有 AI 绝不应更改的关键文件(可能是一个运行良好的复杂组件),您可以在每个提示中重复一条指令,例如:</p><blockquote><p><em>不要更改 authentication.js 文件。</em></p></blockquote><p>通过持续告诉 AI 避免修改,您减少了意外编辑的机会。同样,如果您只希望 AI 在项目的一个部分工作,请明确限制它:</p><blockquote><p><em>仅将更改集中在 <code>ProfilePage</code> 组件上;假设应用的所有其他部分保持不变。</em></p></blockquote><p>在提示中预先说明这一点有助于将 AI 限制在范围内。</p><h3 id="设计和-UI-调整"><a href="#设计和-UI-调整" class="headerlink" title="设计和 UI 调整"></a>设计和 UI 调整</h3><p>在 Lovable 中提示 UI 更改时,清晰度至关重要,以免破坏功能:</p><ul><li>如果您想要<strong>纯视觉更改</strong>,请明确说明。<strong>“将登录按钮变为蓝色并放大 20%,但不要改变其任何功能或 onClick 逻辑。”</strong> 这确保了 AI 不会在重新设计样式时意外重命名 ID 或更改逻辑。</li><li>对于<strong>响应性</strong>(使设计适应移动设备),通过计划指导 AI。例如:“<em>优化登陆页面的移动端体验:采用移动优先的方法。首先概述每个部分在较小屏幕上应如何重新排列,然后实现这些 CSS 更改。使用标准的 Tailwind 断点(sm, md, lg)并避免自定义断点。确保功能上没有任何变化,只是布局调整。</em>” 通过提供这种详细的指令,您可以获得对移动设备的彻底适配,而不会破坏桌面布局。</li><li>如果您有设计变更的想法,描述期望的结果和任何约束(如“保持相同的 HTML 结构,只更新 CSS”)将有助于 AI 专注于正确的解决方案。在 AI 进行设计更改后,务必测试应用以确认一切仍按预期工作。</li></ul><h3 id="重构和优化代码"><a href="#重构和优化代码" class="headerlink" title="重构和优化代码"></a>重构和优化代码</h3><p>随着项目的发展,Lovable 的 AI 可能会建议重构以提高性能或可维护性。提示重构是一个高级但很有价值的用例:</p><ul><li>强调<strong>行为不变</strong>:“<strong>重构代码以提高清晰度和效率,但<em>应用的</em>功能和输出必须保持完全相同*。”</strong> 这告诉 AI 重构不应引入错误或功能变更。</li><li>您可以先要求一个<strong>重构计划</strong>:“<strong>扫描 <code>utils/</code> 文件夹,并就代码结构或重复提出改进建议。列出更改但先不要应用。</strong>” AI 可能会给您一份需要改进之处的报告。然后您可以决定提示实施哪些更改。</li><li>对于大规模重构,分阶段进行。一次提示一个模块,测试,然后继续。这与循序渐进的原则相结合。例如:首先重构状态管理逻辑,稍后再重构 API 调用,而不是一次性完成所有工作。</li><li>重构之后,最好提示进行快速<strong>事后检查</strong>:“<strong>现在代码已经重构完毕,快速检查一下:UI 看起来是否相同?所有测试或关键流程是否仍然通过?</strong>” AI 可以进行自我验证或列出需要手动检查的事项。</li></ul><h3 id="借助-AI-辅助调试"><a href="#借助-AI-辅助调试" class="headerlink" title="借助 AI 辅助调试"></a>借助 AI 辅助调试</h3><p>错误是不可避免的。Lovable 有一个“尝试修复”功能用于快速修复,但您也可以通过提示请求 AI 协助:</p><ul><li><p>当错误发生时,将任何<strong>错误日志或消息</strong>复制到提示中(最好在聊天模式下),然后询问:“<em>这是错误和相关代码片段——导致此错误的原因是什么?我们该如何修复?</em>” 详细的错误上下文有助于 AI 精确定位问题。</p></li><li><p>在调试时使用 CLEAR 原则:明确说明代码<em>应该</em>做什么与<em>实际</em>发生了什么。有时,仅仅向 AI 详细解释错误就会引导它找到解决方案。</p></li><li><p>如果 AI 的第一次修复不起作用,使用适应原则:澄清发生了什么变化或提供新的错误,并要求它再试一次或建议替代方法。</p></li><li><p>利用聊天模式讨论错误:“<em>修复无效。状态在运行时仍然是未定义的。还可能是什么问题?让我们思考一下可能的原因。</em>” 您可以进行来回对话,直到找到一个合理的解决方案,然后在默认模式下应用它。</p><p>对于 UI 错误,您甚至可以分享屏幕截图(如果 Lovable 在聊天中支持图片输入)或描述视觉问题。例如,“侧边栏应该在移动设备上隐藏,但它仍然可见。这是 CSS……可能是什么原因导致的?” 如果提供足够的信息,AI 可以推理 CSS 或布局问题。</p></li><li><p>修复后务必测试。如果修复有效,考虑使用反向元提示让 AI 总结根本原因以及未来如何避免,从而丰富您的知识库。</p></li></ul><h3 id="何时(以及何时不)让-AI-参与"><a href="#何时(以及何时不)让-AI-参与" class="headerlink" title="何时(以及何时不)让 AI 参与"></a>何时(以及何时不)让 AI 参与</h3><ul><li>一个大师级的提示工程师知道,有时候根本不需要提示。如果一个更改非常小,或者您已经知道如何快速完成(例如,更改文本标签、调整一个内边距值),直接在代码编辑器中手动操作可能更快。过度依赖 AI 处理琐碎任务可能会减慢您的速度并消耗您的提示配额。在 AI 能增加价值的地方使用它——复杂的逻辑、样板代码生成、多步骤操作或您不确定的事情。对于更简单的问题,您可以:<ul><li>使用您自己的知识或快速搜索(甚至可以在 Lovable 之外询问 ChatGPT)来解决,特别是如果这样可以避免因 AI 可能误解而浪费一个提示。</li><li>利用开发人员工具:打开浏览器开发者工具控制台来检查元素或实时调试 JavaScript 错误。一旦确定了修复方法,您可以直接实施或通过提示确认。</li></ul></li></ul><blockquote><p>如果您发现一个按钮颜色错误,直接修复 CSS 类可能比向 AI 描述问题并冒着它更改更多内容的风险要快。另一方面,如果您需要从头开始实施一个新功能,那正是 AI 的完美工作——您描述<em>做什么</em>和<em>为什么</em>,它用代码解决<em>如何做</em>。</p></blockquote><p>请记住,Lovable 的 AI 就像一个助理开发人员。您通过给予清晰的任务和监督来管理它。它可以极大地加速开发,但您仍然是负责审查和指导工作的负责人。</p><h2 id="在不同工具中应用这些策略"><a href="#在不同工具中应用这些策略" class="headerlink" title="在不同工具中应用这些策略"></a>在不同工具中应用这些策略</h2><p>上述提示原则不仅适用于 Lovable 的聊天界面,也适用于您与 AI 或自动化工具交互的任何地方:</p><h3 id="在-Lovable-的构建器中"><a href="#在-Lovable-的构建器中" class="headerlink" title="在 Lovable 的构建器中"></a>在 Lovable 的构建器中</h3><p>您将主要在 Lovable 聊天界面中使用这些提示来构建和完善您的应用。</p><ol><li>从一个广泛的项目提示开始,然后逐个功能地迭代。</li><li>当您需要讨论或调试而不更改代码时,使用仅聊天模式。</li></ol><h3 id="在-make-com-或-n8n(工作流自动化)-中"><a href="#在-make-com-或-n8n(工作流自动化)-中" class="headerlink" title="在 make.com 或 n8n(工作流自动化) 中"></a>在 <a href="http://make.com/" target="_blank" rel="noopener"><strong><em>make.com 或 n8n(工作流自动化)</em></strong></a> 中</h3><p>您可能不会以同样的自然语言方式提示这些平台,但设计自动化<strong>仍然受益于清晰的 AI 指令</strong>。</p><p>例如,您可以让 Lovable 生成集成逻辑:</p><blockquote><p><a href="http://make.com/" target="_blank" rel="noopener"><strong><em>当表单提交时,将数据发送到 Make.com 的 webhook 以发送 Slack 通知。</em></strong></a></p></blockquote><p>事实上,Lovable 可以通过与 webhook 集成来帮助设置自动化。如果您的应用需要移交任务(如发送电子邮件、更新 CRM),您可以提示 Lovable 使用 Make 或 n8n。</p><blockquote><p><a href="http://make.com/" target="_blank" rel="noopener"><strong><em>在应用中的用户注册后,触发一个 Make.com 工作流,在 Salesforce 中创建一条记录。</em></strong></a></p></blockquote><p>Lovable 将编写调用该 webhook 或 API 的代码。保持提示的结构化确保 AI 确切知道如何将 Lovable 与这些外部服务连接。</p><h3 id="边缘案例和外部集成"><a href="#边缘案例和外部集成" class="headerlink" title="边缘案例和外部集成"></a><strong>边缘案例和外部集成</strong></h3><p>Lovable 与许多服务集成(Stripe、GitHub、Supabase 等)。在为此类集成提示时,请将集成细节视为您<strong>背景/约束</strong>的一部分。例如,</p><blockquote><p><em>将表单连接到 Stripe(测试模式)进行支付。成功后,重定向到 /thank-you。</em></p></blockquote><p>明确说明外部服务应该做什么。对于使用 n8n(自托管自动化)也是如此——您可能会写,</p><blockquote><p><em>在表单提交后向 n8n webhook URL 发送 POST 请求,并等待其响应以显示确认消息。</em></p></blockquote><p>此处的清晰度至关重要,以便 AI 产生正确的调用。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>强大的提示在于<strong>清晰度、结构和上下文</strong>。无论您是告诉 Lovable 构建一个功能,还是编排一个 <a href="http://make.com/" target="_blank" rel="noopener">Make.com</a> 场景,目标都是描绘出您想要的画面。</li><li>如果不确定,请从结构化提示开始,随着信心的增强,逐渐发展为更对话式的风格。</li><li>使用元技巧来改进并从每次互动中学习。</li><li>通过练习,您将像指导开发团队的延伸部分一样指导 AI——并且自然地获得您需要的精确输出。</li></ul><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>到目前为止,您应该已经扎实掌握了如何构思清晰、有效且针对 Lovable AI 量身定制的提示。从基础的 CLEAR 原则到少样本示例和元提示等高级策略,这些技巧使您能够从 AI 那里获得您确切需要的东西——不多也不少。您已经学会了如何构建您的请求、提供上下文、避免像幻觉这样的陷阱,并利用 Lovable 的特定功能(知识库、聊天模式等)来简化您的工作流程。</p><p>大师级的提示工程改变游戏规则:它将 AI 从一个小玩意变成了一个可靠的队友。通过练习,您会发现您可以通过简单地<em>提出正确的问题</em>和给予正确的指导来更快地构建应用、减少挫败感地调试,甚至探索创造性的解决方案。关键在于在您的指令中保持<strong>机智、简洁、直接和适应</strong>——就像一位经验丰富的工程师与团队沟通一样。</p><p>最后,始终从每次互动中学习(那种反思的习惯)。每一个提示/回复都是您进一步优化技巧的反馈。随着您在 Lovable 中继续构建,您将培养出一种直觉,知道 AI 需要听到什么才能产生出色的结果。将这一点与您自己的聪明才智结合起来,几乎没有什么目标是无法实现的。</p><p><strong>专注于您的大胆想法</strong>——一旦您清楚地告诉 Lovable 的 AI 该做什么,就让它来处理执行的细节吧。</p><p>祝您提示愉快,构建顺利!</p>]]></content>
<summary type="html">
<p>提示词结构、提示词层级、元/反向元提示,以及带有示例的基础策略。</p>
<p>翻译自:<a href="https://docs.lovable.dev/prompting/prompting-one" target="_blank" rel="noopener">htt
</summary>
<category term="LLM" scheme="https://jiapan.me/categories/LLM/"/>
<category term="AI" scheme="https://jiapan.me/categories/LLM/AI/"/>
<category term="翻译" scheme="https://jiapan.me/categories/LLM/AI/%E7%BF%BB%E8%AF%91/"/>
</entry>
<entry>
<title>理解 Python 中的协程、Future、任务</title>
<link href="https://jiapan.me/2025/python-coroutine-future-task/"/>
<id>https://jiapan.me/2025/python-coroutine-future-task/</id>
<published>2025-09-26T09:46:41.000Z</published>
<updated>2026-02-04T04:48:14.586Z</updated>
<content type="html"><![CDATA[<p>理解这三者的关系是掌握 <code>asyncio</code> 异步编程的关键。我们可以用一个简单的比喻来贯穿整个解释:<strong>做饭</strong>。</p><hr><h3 id="1-协程-Coroutine"><a href="#1-协程-Coroutine" class="headerlink" title="1. 协程 (Coroutine)"></a>1. 协程 (Coroutine)</h3><p><strong>是什么?</strong><br>协程是 <code>asyncio</code> 异步编程的基石。一个用 <code>async def</code> 关键字定义的函数,其返回值就是一个<strong>协程对象</strong>。协程是一个可以暂停和恢复执行的特殊函数。</p><p><strong>核心特点:</strong></p><ul><li><strong>惰性执行</strong>:仅仅调用一个协程函数并不会执行其中的代码,它只会返回一个协程对象。</li><li><strong>需要驱动</strong>:协程必须被驱动才能运行。驱动方式通常有两种:<ol><li>在另一个已经运行的协程中通过 <code>await</code> 关键字来调用。</li><li>通过 <code>asyncio.run()</code> 启动顶层协程,或通过 <code>asyncio.create_task()</code> 将其包装成任务。</li></ol></li><li><strong><code>await</code> 关键字</strong>:<code>await</code> 只能在 <code>async def</code> 函数内部使用。它会暂停当前协程的执行,让事件循环去处理其他任务,直到 <code>await</code> 后面的可等待对象(另一个协程、任务或 Future)完成。</li></ul><p><strong>做饭的比喻:</strong><br>协程就像一个<strong>菜谱 (Recipe)</strong>。菜谱详细描述了做一道菜的所有步骤(代码逻辑),包括需要在哪里“等待”(比如 <code>await asyncio.sleep(10)</code> 就好比菜谱里写着“将食材腌制10分钟”)。光有菜谱本身,菜是不会自己做出来的。</p><p><strong>代码示例:</strong><br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">make_salad</span><span class="params">()</span>:</span></span><br><span class="line"> print(<span class="string">"开始准备沙拉..."</span>)</span><br><span class="line"> <span class="comment"># 这是一个耗时操作,比如洗菜、切菜</span></span><br><span class="line"> <span class="keyword">await</span> asyncio.sleep(<span class="number">2</span>) <span class="comment"># 模拟IO操作,暂停执行,交出控制权</span></span><br><span class="line"> print(<span class="string">"沙拉准备好了!"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"一份美味的沙拉"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. 调用协程函数,得到一个协程对象</span></span><br><span class="line">salad_coro = make_salad()</span><br><span class="line">print(<span class="string">f"调用 make_salad() 得到的是: <span class="subst">{salad_coro}</span>"</span>)</span><br><span class="line">print(<span class="string">"仅仅调用并不会执行任何代码"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 使用 asyncio.run() 来驱动协程,并实际执行它</span></span><br><span class="line"><span class="comment"># asyncio.run() 会创建事件循环,运行协程,然后关闭循环</span></span><br><span class="line">result = asyncio.run(salad_coro)</span><br><span class="line">print(<span class="string">f"最终结果: <span class="subst">{result}</span>"</span>)</span><br></pre></td></tr></table></figure></p><hr><h3 id="2-Future-对象"><a href="#2-Future-对象" class="headerlink" title="2. Future 对象"></a>2. Future 对象</h3><p><strong>是什么?</strong><br><code>Future</code> 是一个<strong>低层级</strong>的可等待对象,它代表一个异步操作<strong>最终的结果</strong>。可以把它想象成一个“占位符”或“期货”,在未来的某个时刻,这个占位符会被一个真实的值或一个异常所填充。</p><p><strong>核心特点:</strong></p><ul><li><strong>状态</strong>:一个 <code>Future</code> 对象有自己的状态(比如 <code>pending</code>, <code>finished</code>, <code>cancelled</code>)。</li><li><strong>不关心如何产生结果</strong>:<code>Future</code> 对象本身不包含任何业务逻辑,它只关心最终的结果。</li><li><strong>由底层库使用</strong>:通常,应用程序开发者不直接创建 <code>Future</code> 对象。它们更多地被底层的库(如网络库)用来与事件循环集成。例如,一个库在等待网络数据时,可以创建一个 <code>Future</code>,当数据到达时,就调用 <code>future.set_result()</code> 来填充结果。</li></ul><p><strong>做饭的比喻:</strong><br><code>Future</code> 就像你在快餐店点餐后拿到的那个<strong>取餐凭证/电子蜂鸣器</strong>。你不知道后厨具体是怎么做的,但你拿着这个凭证,就代表你未来会得到你的餐点。当蜂鸣器响起时(<code>Future</code> 完成了),你就可以凭它去取餐(获取结果)。</p><hr><h3 id="3-任务-Task"><a href="#3-任务-Task" class="headerlink" title="3. 任务 (Task)"></a>3. 任务 (Task)</h3><p><strong>是什么?</strong><br><code>Task</code> 是 <code>Future</code> 的一个子类。它的特定作用是<strong>包装并独立调度一个协程在事件循环中并发执行</strong>。<code>Task</code> 是实现并发的核心工具。</p><p><strong>核心特点:</strong></p><ul><li><strong>并发执行</strong>:当你用 <code>asyncio.create_task()</code> 把一个协程包装成一个 <code>Task</code> 时,该协程会立即被提交到事件循环中,并尽快开始执行,而不需要你立即 <code>await</code> 它。这使得多个任务可以“同时”运行。</li><li><strong>可等待</strong>:因为 <code>Task</code> 是 <code>Future</code> 的子类,所以它也是一个可等待对象。你可以随时 <code>await</code> 一个 <code>Task</code> 来获取它的最终结果(如果它还没完成,<code>await</code> 会等待它完成)。</li><li><strong>管理和控制</strong>:<code>Task</code> 对象提供了取消任务 (<code>task.cancel()</code>)、检查状态 (<code>task.done()</code>) 等管理接口。</li></ul><p><strong>做饭的比喻:</strong><br><code>Task</code> 就像是你把<strong>菜谱(协程)</strong>交给了<strong>一位厨师(事件循环)</strong>,并让他开始工作。厨师会立即开始按照菜谱做菜,而你可以去做别的事情(比如摆放餐具)。你手里拿到了<strong>取餐凭证(Task 对象本身)</strong>,可以随时用它来查看菜做得怎么样了,或者在最后等菜上桌 (<code>await task</code>)。</p><p><strong>代码示例:</strong><br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">brew_coffee</span><span class="params">()</span>:</span></span><br><span class="line"> print(<span class="string">"开始煮咖啡..."</span>)</span><br><span class="line"> <span class="keyword">await</span> asyncio.sleep(<span class="number">3</span>)</span><br><span class="line"> print(<span class="string">"咖啡煮好了!"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"一杯香浓的咖啡"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">toast_bread</span><span class="params">()</span>:</span></span><br><span class="line"> print(<span class="string">"开始烤面包..."</span>)</span><br><span class="line"> <span class="keyword">await</span> asyncio.sleep(<span class="number">2</span>)</span><br><span class="line"> print(<span class="string">"面包烤好了!"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"两片烤面包"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line"> start_time = time.time()</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 将协程包装成任务,它们会立即开始并发执行</span></span><br><span class="line"> coffee_task = asyncio.create_task(brew_coffee())</span><br><span class="line"> bread_task = asyncio.create_task(toast_bread())</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 在等待任务完成的同时,我们可以做点别的事</span></span><br><span class="line"> print(<span class="string">"正在摆放餐具..."</span>)</span><br><span class="line"> <span class="keyword">await</span> asyncio.sleep(<span class="number">1</span>)</span><br><span class="line"> print(<span class="string">"餐具摆放完毕。"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 现在等待任务完成并获取结果</span></span><br><span class="line"> <span class="comment"># 注意:这里我们分别 await,更好的方式是使用 asyncio.gather</span></span><br><span class="line"> coffee_result = <span class="keyword">await</span> coffee_task</span><br><span class="line"> bread_result = <span class="keyword">await</span> bread_task</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 使用 gather 会更简洁</span></span><br><span class="line"> <span class="comment"># results = await asyncio.gather(coffee_task, bread_task)</span></span><br><span class="line"></span><br><span class="line"> end_time = time.time()</span><br><span class="line"> print(<span class="string">f"\n早餐准备完毕: <span class="subst">{coffee_result}</span> 和 <span class="subst">{bread_result}</span>"</span>)</span><br><span class="line"> print(<span class="string">f"总耗时: <span class="subst">{end_time - start_time:<span class="number">.2</span>f}</span> 秒"</span>) <span class="comment"># 结果约为3秒,而不是 3+2=5秒</span></span><br><span class="line"></span><br><span class="line">asyncio.run(main())</span><br></pre></td></tr></table></figure></p><p>在这个例子中,煮咖啡(3秒)和烤面包(2秒)是并发进行的,所以总耗时取决于最长的那个任务,也就是3秒左右,而不是串行执行的5秒。</p><hr><h3 id="总结:三者关系"><a href="#总结:三者关系" class="headerlink" title="总结:三者关系"></a>总结:三者关系</h3><table><thead><tr><th style="text-align:left">概念</th><th style="text-align:left">核心作用</th><th style="text-align:left">比喻</th><th style="text-align:left">如何创建/使用</th></tr></thead><tbody><tr><td style="text-align:left"><strong>协程 (Coroutine)</strong></td><td style="text-align:left">定义一个可以暂停的异步操作流程。</td><td style="text-align:left"><strong>菜谱</strong></td><td style="text-align:left">通过 <code>async def</code> 定义函数。</td></tr><tr><td style="text-align:left"><strong>Future</strong></td><td style="text-align:left">代表一个异步操作的<strong>最终结果</strong>的占位符。</td><td style="text-align:left"><strong>取餐凭证</strong></td><td style="text-align:left">通常由底层库创建和管理。</td></tr><tr><td style="text-align:left"><strong>任务 (Task)</strong></td><td style="text-align:left"><strong>调度和执行</strong>一个协程,实现并发。</td><td style="text-align:left"><strong>将菜谱交给厨师去执行</strong></td><td style="text-align:left">通过 <code>asyncio.create_task()</code> 包装一个协程。</td></tr></tbody></table><p><strong>核心关系链:</strong></p><p>你编写一个 <strong>协程 (Coroutine)</strong>(菜谱),然后通过 <code>asyncio.create_task()</code> 将它包装成一个 <strong>任务 (Task)</strong>(把菜谱交给厨师),这个 <strong>任务</strong> 本身就是一个特殊的 <strong>Future</strong>(你拿到的取餐凭证),你可以随时等待(<code>await</code>)它完成并获取结果。</p>]]></content>
<summary type="html">
<p>理解这三者的关系是掌握 <code>asyncio</code> 异步编程的关键。我们可以用一个简单的比喻来贯穿整个解释:<strong>做饭</strong>。</p>
<hr>
<h3 id="1-协程-Coroutine"><a href="#1-协程-Corouti
</summary>
<category term="Python" scheme="https://jiapan.me/tags/Python/"/>
<category term="异步编程" scheme="https://jiapan.me/tags/%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B/"/>
<category term="asyncio" scheme="https://jiapan.me/tags/asyncio/"/>
<category term="协程" scheme="https://jiapan.me/tags/%E5%8D%8F%E7%A8%8B/"/>
</entry>
<entry>
<title>执行可等待对象:asyncio.ensure_future()</title>
<link href="https://jiapan.me/2025/python-asyncio-ensure_future/"/>
<id>https://jiapan.me/2025/python-asyncio-ensure_future/</id>
<published>2025-09-26T07:46:41.000Z</published>
<updated>2026-02-04T04:48:14.586Z</updated>
<content type="html"><![CDATA[<p><code>asyncio.ensure_future()</code> 用于调度协程(coroutine)或其他可等待对象(awaitable)的执行。它的核心功能是<strong>确保一个可等待对象最终被包装成一个 <code>asyncio.Task</code> 对象</strong>,并安排它在事件循环中运行。</p><h3 id="asyncio-ensure-future-的主要作用"><a href="#asyncio-ensure-future-的主要作用" class="headerlink" title="asyncio.ensure_future 的主要作用"></a><code>asyncio.ensure_future</code> 的主要作用</h3><ol><li><p><strong>调度执行</strong>:当你有一个协程函数,但不想立即 <code>await</code> 它(因为 <code>await</code> 会阻塞当前任务直到该协程完成),你可以使用 <code>asyncio.ensure_future()</code> 来把它提交给事件循环,让它在后台并发执行。</p></li><li><p><strong>统一对象类型</strong>:这个函数非常灵活,它可以接受多种类型的输入:</p><ul><li><strong>如果是协程(coroutine)</strong>:它会调用 <code>loop.create_task()</code> 将协程包装成一个 <code>Task</code> 对象。 这个 <code>Task</code> 会被安排在事件循环中执行。</li><li><strong>如果是 <code>Future</code> 或 <code>Task</code> 对象</strong>:它会直接返回该对象,不做任何改变。 因为 <code>Task</code> 本身就是 <code>Future</code> 的子类。</li><li><strong>如果是其他可等待对象(awaitable)</strong>:它会将其包装在一个 <code>Task</code> 中,该任务会 <code>await</code> 这个对象。</li></ul><p>这个特性使得 <code>ensure_future()</code> 在编写库或API时特别有用,因为你无需关心传入的到底是一个协程还是一个已经存在的 <code>Task</code>,<code>ensure_future()</code> 都能确保你得到一个可以操作的 <code>Future</code> 对象(比如用于取消操作)。</p></li></ol><h3 id="asyncio-ensure-future-vs-asyncio-create-task"><a href="#asyncio-ensure-future-vs-asyncio-create-task" class="headerlink" title="asyncio.ensure_future vs asyncio.create_task"></a><code>asyncio.ensure_future</code> vs <code>asyncio.create_task</code></h3><p>在现代 Python (3.7+) 中,<code>asyncio.create_task()</code> 是创建和调度任务的首选方式。 了解它们之间的区别很重要:</p><ul><li><p><strong><code>asyncio.create_task(coro)</code></strong>:</p><ul><li><strong>目的明确</strong>:专门用于从一个<strong>协程</strong>创建并调度一个 <code>Task</code>。 它是更高级别的API,推荐在应用代码中使用。</li><li><strong>输入限制</strong>:它的参数只能是协程对象。</li><li><strong>可读性好</strong>:函数名清晰地表达了其意图——创建一个任务。</li></ul></li><li><p><strong><code>asyncio.ensure_future(awaitable)</code></strong>:</p><ul><li><strong>功能更广泛</strong>:如上所述,它可以接受协程、<code>Future</code>、<code>Task</code> 或其他可等待对象。</li><li><strong>向后兼容</strong>:<code>create_task()</code> 是在 Python 3.7 中引入的。在之前的版本中,<code>ensure_future()</code> 是创建任务的主要方式。</li><li><strong>适用场景</strong>:当你需要编写一个能同时处理协程和 <code>Future</code> 对象的通用函数时,<code>ensure_future()</code> 更加合适。</li></ul></li></ul><p><strong>总结</strong>:在 Python 3.7 及更高版本中,如果你明确知道要从一个协程创建一个任务,<strong>应该优先使用 <code>asyncio.create_task()</code></strong>。 只有在需要兼容旧版本或处理不确定是协程还是 <code>Future</code> 的输入时,才需要使用 <code>asyncio.ensure_future()</code>。</p><h3 id="如何使用-asyncio-ensure-future"><a href="#如何使用-asyncio-ensure-future" class="headerlink" title="如何使用 asyncio.ensure_future"></a>如何使用 <code>asyncio.ensure_future</code></h3><p>下面是一个代码示例,展示了如何使用 <code>ensure_future()</code> 来并发执行任务。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">my_coroutine</span><span class="params">(name, delay)</span>:</span></span><br><span class="line"> <span class="string">"""一个模拟耗时操作的协程"""</span></span><br><span class="line"> print(<span class="string">f"任务 '<span class="subst">{name}</span>' 开始执行,将休眠 <span class="subst">{delay}</span> 秒"</span>)</span><br><span class="line"> <span class="keyword">await</span> asyncio.sleep(delay)</span><br><span class="line"> print(<span class="string">f"任务 '<span class="subst">{name}</span>' 执行完毕"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="string">f"结果来自 <span class="subst">{name}</span>"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line"> start_time = time.time()</span><br><span class="line"> print(<span class="string">"主程序开始"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 使用 ensure_future() 来调度两个协程并发执行</span></span><br><span class="line"> <span class="comment"># 它们会被包装成 Task 并立即开始运行</span></span><br><span class="line"> task1 = asyncio.ensure_future(my_coroutine(<span class="string">"任务A"</span>, <span class="number">2</span>))</span><br><span class="line"> task2 = asyncio.ensure_future(my_coroutine(<span class="string">"任务B"</span>, <span class="number">3</span>))</span><br><span class="line"></span><br><span class="line"> <span class="comment"># ensure_future 接收一个已存在的 Task 时,会直接返回它</span></span><br><span class="line"> task3 = asyncio.ensure_future(task2)</span><br><span class="line"> print(<span class="string">f"task2 和 task3 是同一个对象吗? <span class="subst">{task2 <span class="keyword">is</span> task3}</span>"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 在这里可以做其他事情,而 task1 和 task2 正在后台运行</span></span><br><span class="line"> print(<span class="string">"任务已调度,主程序可以继续执行其他操作"</span>)</span><br><span class="line"> <span class="keyword">await</span> asyncio.sleep(<span class="number">1</span>)</span><br><span class="line"> print(<span class="string">"主程序等待 1 秒后继续..."</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 等待两个任务完成并获取结果</span></span><br><span class="line"> <span class="comment"># 注意:可以直接 await task 对象</span></span><br><span class="line"> result1 = <span class="keyword">await</span> task1</span><br><span class="line"> result2 = <span class="keyword">await</span> task2</span><br><span class="line"></span><br><span class="line"> end_time = time.time()</span><br><span class="line"> print(<span class="string">f"获取到的结果: '<span class="subst">{result1}</span>', '<span class="subst">{result2}</span>'"</span>)</span><br><span class="line"> print(<span class="string">f"主程序结束,总耗时: <span class="subst">{end_time - start_time:<span class="number">.2</span>f}</span> 秒"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> asyncio.run(main())</span><br></pre></td></tr></table></figure><p><strong>运行结果分析:</strong></p><ol><li><code>main</code> 函数开始执行,<code>task1</code> 和 <code>task2</code> 通过 <code>asyncio.ensure_future()</code> 被调度。它们会立即开始并发运行,而不是一个接一个。</li><li>程序打印 “任务已调度…”,并等待1秒。在此期间,<code>task1</code> 和 <code>task2</code> 都在后台执行它们的 <code>asyncio.sleep()</code>。</li><li><code>await task1</code> 和 <code>await task2</code> 会等待各自的任务完成。由于它们是并发执行的,总耗时取决于最长的那个任务(任务B,3秒),而不是两个任务时间的总和(2 + 3 = 5秒)。</li><li>最终的总耗时约等于3秒,证明了并发执行的效率。</li><li>示例也验证了当 <code>ensure_future</code> 的参数已经是 <code>Task</code> 时,它会返回完全相同的对象。</li></ol>]]></content>
<summary type="html">
<p><code>asyncio.ensure_future()</code> 用于调度协程(coroutine)或其他可等待对象(awaitable)的执行。它的核心功能是<strong>确保一个可等待对象最终被包装成一个 <code>asyncio.Task</code> 对
</summary>
<category term="Python" scheme="https://jiapan.me/tags/Python/"/>
<category term="asyncio" scheme="https://jiapan.me/tags/asyncio/"/>
<category term="并发编程" scheme="https://jiapan.me/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/"/>
<category term="协程" scheme="https://jiapan.me/tags/%E5%8D%8F%E7%A8%8B/"/>
</entry>
<entry>
<title>收集多个可等待对象的结果:asyncio.gather()</title>
<link href="https://jiapan.me/2025/python-asyncio-gather/"/>
<id>https://jiapan.me/2025/python-asyncio-gather/</id>
<published>2025-09-26T06:46:41.000Z</published>
<updated>2026-02-04T04:48:14.586Z</updated>
<content type="html"><![CDATA[<p><code>asyncio.gather</code> 的核心功能是<strong>并发运行多个可等待对象(awaitables),并等待它们全部完成后收集结果</strong>。</p><p>当你需要同时执行多个独立的异步操作(比如并发发出多个网络请求或数据库查询)并一次性获取所有结果时,<code>asyncio.gather</code> 是理想的选择。</p><h3 id="asyncio-gather-的主要作用"><a href="#asyncio-gather-的主要作用" class="headerlink" title="asyncio.gather 的主要作用"></a><code>asyncio.gather</code> 的主要作用</h3><ol><li><p><strong>并发执行</strong>:<code>gather</code> 接收一个或多个可等待对象(如协程、任务或 Future)作为参数,并将它们并发地在事件循环中调度执行。 如果传入的是协程,<code>gather</code> 会自动将其包装成 <code>asyncio.Task</code>。</p></li><li><p><strong>等待完成</strong>:<code>await asyncio.gather(...)</code> 会阻塞当前任务,直到所有传入的可等待对象都执行完毕。</p></li><li><p><strong>收集结果</strong>:一旦所有任务都成功完成,<code>gather</code> 会返回一个列表,其中包含了每个任务的返回值。 <strong>重要的是,返回结果的顺序与你传入可等待对象的顺序完全一致</strong>,而与它们的实际完成顺序无关。</p></li></ol><h3 id="如何使用-asyncio-gather"><a href="#如何使用-asyncio-gather" class="headerlink" title="如何使用 asyncio.gather"></a>如何使用 <code>asyncio.gather</code></h3><p>最常见的使用方式是将多个协程调用作为参数传递给 <code>gather</code>。</p><h4 id="基础代码示例"><a href="#基础代码示例" class="headerlink" title="基础代码示例"></a>基础代码示例</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">fetch_data</span><span class="params">(source, delay)</span>:</span></span><br><span class="line"> <span class="string">"""一个模拟从不同来源获取数据的协程"""</span></span><br><span class="line"> print(<span class="string">f"开始从 <span class="subst">{source}</span> 获取数据..."</span>)</span><br><span class="line"> <span class="keyword">await</span> asyncio.sleep(delay)</span><br><span class="line"> result = <span class="string">f"来自 <span class="subst">{source}</span> 的数据"</span></span><br><span class="line"> print(<span class="string">f"完成从 <span class="subst">{source}</span> 获取数据"</span>)</span><br><span class="line"> <span class="keyword">return</span> result</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line"> start_time = time.time()</span><br><span class="line"> print(<span class="string">"同时启动多个任务..."</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 使用 asyncio.gather 并发运行两个协程</span></span><br><span class="line"> results = <span class="keyword">await</span> asyncio.gather(</span><br><span class="line"> fetch_data(<span class="string">"API"</span>, <span class="number">2</span>),</span><br><span class="line"> fetch_data(<span class="string">"数据库"</span>, <span class="number">3</span>)</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> end_time = time.time()</span><br><span class="line"></span><br><span class="line"> print(<span class="string">f"\n所有任务完成,总耗时: <span class="subst">{end_time - start_time:<span class="number">.2</span>f}</span> 秒"</span>)</span><br><span class="line"> print(<span class="string">f"获取到的结果: <span class="subst">{results}</span>"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> asyncio.run(main())</span><br></pre></td></tr></table></figure><p><strong>运行结果分析</strong>:</p><ul><li>两个 <code>fetch_data</code> 任务会并发执行。</li><li>程序不会等待2秒再等待3秒(总共5秒),而是会等待最长的那个任务完成,也就是3秒。</li><li>最终的输出结果 <code>results</code> 会是一个列表 <code>['来自 API 的数据', '来自 数据库的数据']</code>,顺序与调用 <code>gather</code> 时的参数顺序一致。</li></ul><h3 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h3><p><code>asyncio.gather</code> 在处理异常时有两种模式,由 <code>return_exceptions</code> 参数控制。</p><h4 id="默认行为-return-exceptions-False"><a href="#默认行为-return-exceptions-False" class="headerlink" title="默认行为 (return_exceptions=False)"></a>默认行为 (<code>return_exceptions=False</code>)</h4><p>默认情况下,如果 <code>gather</code> 中有任何一个任务引发了异常,这个异常会<strong>立即</strong>被传播到 <code>await asyncio.gather(...)</code> 的调用处。 这意味着:</p><ul><li>你的程序会立即因为这个异常而中断(除非你用 <code>try...except</code> 捕获它)。</li><li>其他仍在运行的任务不会被自动取消,它们会继续在后台运行直到完成或事件循环结束。</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">failing_coroutine</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="keyword">await</span> asyncio.sleep(<span class="number">1</span>)</span><br><span class="line"> <span class="keyword">raise</span> ValueError(<span class="string">"任务发生错误!"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main_fail</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> <span class="keyword">await</span> asyncio.gather(</span><br><span class="line"> fetch_data(<span class="string">"API"</span>, <span class="number">2</span>),</span><br><span class="line"> failing_coroutine()</span><br><span class="line"> )</span><br><span class="line"> <span class="keyword">except</span> ValueError <span class="keyword">as</span> e:</span><br><span class="line"> print(<span class="string">f"\n在 gather 中捕获到异常: <span class="subst">{e}</span>"</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 即使捕获了异常,API任务可能仍在后台运行</span></span><br><span class="line"> <span class="keyword">await</span> asyncio.sleep(<span class="number">2</span>) </span><br><span class="line"> print(<span class="string">"主程序结束"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行 asyncio.run(main_fail())</span></span><br></pre></td></tr></table></figure><p><strong>运行结果分析</strong>:</p><ol><li><code>failing_coroutine</code> 在1秒后抛出 <code>ValueError</code>。</li><li><code>gather</code> 立即将这个异常传播出来,并被 <code>try...except</code> 块捕获。</li><li><code>fetch_data("API", 2)</code> 任务不会被取消,它会继续运行并在2秒后打印完成信息。</li></ol><h4 id="收集异常-return-exceptions-True"><a href="#收集异常-return-exceptions-True" class="headerlink" title="收集异常 (return_exceptions=True)"></a>收集异常 (<code>return_exceptions=True</code>)</h4><p>当你需要确保所有任务都执行完毕,无论它们是否成功,并且想要检查每个任务的结果时,可以将 <code>return_exceptions</code> 设置为 <code>True</code>。</p><p>在这种模式下:</p><ul><li><code>gather</code> 不会传播异常,而是会像对待成功结果一样“收集”它们。</li><li>返回的列表中,成功任务的位置是其返回值,而失败任务的位置则是该异常对象。</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main_return_exceptions</span><span class="params">()</span>:</span></span><br><span class="line"> results = <span class="keyword">await</span> asyncio.gather(</span><br><span class="line"> fetch_data(<span class="string">"API"</span>, <span class="number">2</span>),</span><br><span class="line"> failing_coroutine(),</span><br><span class="line"> return_exceptions=<span class="keyword">True</span></span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> print(<span class="string">"\n使用 return_exceptions=True 的结果:"</span>)</span><br><span class="line"> <span class="keyword">for</span> result <span class="keyword">in</span> results:</span><br><span class="line"> <span class="keyword">if</span> isinstance(result, Exception):</span><br><span class="line"> print(<span class="string">f" - 任务失败,异常: <span class="subst">{result}</span>"</span>)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> print(<span class="string">f" - 任务成功,结果: '<span class="subst">{result}</span>'"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行 asyncio.run(main_return_exceptions())</span></span><br></pre></td></tr></table></figure><p><strong>运行结果分析</strong>:<br>程序会等待所有任务完成(大约2秒),然后 <code>results</code> 列表会包含类似 <code>['来自 API 的数据', ValueError('任务发生错误!')]</code> 的内容。你可以遍历这个列表来分别处理成功和失败的情况。</p><h3 id="asyncio-gather-vs-asyncio-wait"><a href="#asyncio-gather-vs-asyncio-wait" class="headerlink" title="asyncio.gather vs asyncio.wait"></a><code>asyncio.gather</code> vs <code>asyncio.wait</code></h3><p><code>asyncio</code> 还有一个 <code>asyncio.wait()</code> 函数,它与 <code>gather</code> 类似但用途不同:</p><ul><li><strong>返回值</strong>:<code>gather</code> 直接返回结果列表;<code>wait</code> 返回两组 <code>Task</code> 对象:已完成的(done)和未完成的(pending)。你需要自己从已完成的任务中提取结果或异常。</li><li><strong>灵活性</strong>:<code>wait</code> 更加灵活,可以配置为在第一个任务完成或第一个任务出现异常时就返回,而 <code>gather</code> 总是等待所有任务完成。</li><li><strong>易用性</strong>:对于“运行一堆任务并获取所有结果”这个常见用例,<code>gather</code> 的接口更简单直接。</li></ul>]]></content>
<summary type="html">
<p><code>asyncio.gather</code> 的核心功能是<strong>并发运行多个可等待对象(awaitables),并等待它们全部完成后收集结果</strong>。</p>
<p>当你需要同时执行多个独立的异步操作(比如并发发出多个网络请求或数据库查询)并一
</summary>
<category term="Python" scheme="https://jiapan.me/tags/Python/"/>
<category term="asyncio" scheme="https://jiapan.me/tags/asyncio/"/>
<category term="并发编程" scheme="https://jiapan.me/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/"/>
<category term="gather" scheme="https://jiapan.me/tags/gather/"/>
</entry>
<entry>
<title>保护协程不被取消:asyncio.shield()</title>
<link href="https://jiapan.me/2025/python-asyncio-shield/"/>
<id>https://jiapan.me/2025/python-asyncio-shield/</id>
<published>2025-09-26T06:46:41.000Z</published>
<updated>2026-02-04T04:48:14.586Z</updated>
<content type="html"><![CDATA[<p><code>asyncio.shield()</code> 用于保护一个协程(awaitable object)免受取消操作的影响。当一个任务(Task)被取消时,<code>asyncio</code> 会在其内部引发一个 <code>CancelledError</code> 异常,这通常会导致任务的执行被中断。然而,在某些情况下,我们希望确保某些关键操作即使在外部请求取消的情况下也能执行完毕,这时 <code>asyncio.shield()</code> 就派上了用场。</p><h3 id="asyncio-shield-的作用"><a href="#asyncio-shield-的作用" class="headerlink" title="asyncio.shield 的作用"></a><code>asyncio.shield</code> 的作用</h3><p><code>asyncio.shield()</code> 的核心作用是<strong>保护一个可等待对象(如协程或任务)不被取消</strong>。它通过将可等待对象包装在一个特殊的 <code>Future</code> 对象中来实现这一点,这个 <code>Future</code> 对象会“吸收”取消请求。</p><p>具体来说:</p><ul><li>当一个被 <code>shield()</code> 保护的任务的外部封装(即 <code>shield()</code> 返回的 Future)被取消时,这个取消请求不会传播到内部被保护的任务中。</li><li>对于发起取消请求的代码来说,看起来取消操作是成功的,因为等待被保护任务的地方会立即收到 <code>CancelledError</code> 异常。</li><li>然而,被保护的内部任务实际上会继续在后台运行,直到它自然完成。</li></ul><p>这在执行一些不能被中断的关键操作时非常有用,例如:</p><ul><li><strong>资源清理</strong>:确保文件句柄、网络连接等资源被正确关闭,即使主任务被取消。</li><li><strong>数据完整性</strong>:在退出前完成重要的数据写入或状态更新,防止数据损坏。</li><li><strong>优雅关闭</strong>:在应用程序关闭时,保证一些清理或回滚操作能够顺利完成。</li></ul><h3 id="如何使用-asyncio-shield"><a href="#如何使用-asyncio-shield" class="headerlink" title="如何使用 asyncio.shield"></a>如何使用 <code>asyncio.shield</code></h3><p><code>asyncio.shield()</code> 的语法很简单,它接受一个可等待对象作为参数:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">asyncio.shield(aw)</span><br></pre></td></tr></table></figure></p><p>如果 <code>aw</code> 是一个协程,它会自动被调度为一个任务来运行。</p><h4 id="代码示例"><a href="#代码示例" class="headerlink" title="代码示例"></a>代码示例</h4><p>下面通过一个例子来直观地展示 <code>asyncio.shield()</code> 的效果。我们将创建一个长时间运行的任务,并尝试在它完成前取消它,分别在有和没有 <code>shield()</code> 保护的情况下进行对比。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">critical_operation</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="string">"""一个模拟的、不应被中断的关键操作"""</span></span><br><span class="line"> print(<span class="string">"关键操作开始,需要5秒钟完成..."</span>)</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">5</span>):</span><br><span class="line"> print(<span class="string">f"关键操作正在进行中... <span class="subst">{i+<span class="number">1</span>}</span>/5"</span>)</span><br><span class="line"> <span class="keyword">await</span> asyncio.sleep(<span class="number">1</span>)</span><br><span class="line"> print(<span class="string">"关键操作成功完成!"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"操作结果"</span></span><br><span class="line"> <span class="keyword">except</span> asyncio.CancelledError:</span><br><span class="line"> print(<span class="string">"关键操作被取消了(这不应该发生)!"</span>)</span><br><span class="line"> <span class="keyword">raise</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="comment"># 创建一个关键操作的任务</span></span><br><span class="line"> task = asyncio.create_task(critical_operation())</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 使用 shield 保护任务</span></span><br><span class="line"> shielded_task = asyncio.shield(task)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 运行另一个任务,它将在1秒后尝试取消被保护的任务</span></span><br><span class="line"> canceller = asyncio.create_task(cancel_after(shielded_task, <span class="number">1</span>))</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> <span class="comment"># 等待被保护的任务</span></span><br><span class="line"> <span class="comment"># 当 canceller 取消 shielded_task 时,这里会立即抛出 CancelledError</span></span><br><span class="line"> result = <span class="keyword">await</span> shielded_task</span><br><span class="line"> print(<span class="string">f"从受保护的任务中获取结果: <span class="subst">{result}</span>"</span>)</span><br><span class="line"> <span class="keyword">except</span> asyncio.CancelledError:</span><br><span class="line"> print(<span class="string">"主协程捕获到 CancelledError,但内部任务应该还在运行。"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 尽管 shielded_task 已经被“取消”,但原始的 task 应该仍在运行</span></span><br><span class="line"> <span class="comment"># 我们可以等待原始任务完成来验证这一点</span></span><br><span class="line"> print(<span class="string">"等待原始任务完成..."</span>)</span><br><span class="line"> final_result = <span class="keyword">await</span> task <span class="comment"># 等待内部任务最终完成</span></span><br><span class="line"> print(<span class="string">f"原始任务最终完成,结果是: '<span class="subst">{final_result}</span>'"</span>)</span><br><span class="line"> print(<span class="string">f"原始任务是否被取消: <span class="subst">{task.cancelled()}</span>"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">cancel_after</span><span class="params">(task_to_cancel, delay)</span>:</span></span><br><span class="line"> <span class="string">"""在一个延迟后取消指定的任务"""</span></span><br><span class="line"> <span class="keyword">await</span> asyncio.sleep(delay)</span><br><span class="line"> print(<span class="string">f"<span class="subst">{delay}</span>秒后,尝试取消任务..."</span>)</span><br><span class="line"> was_cancelled = task_to_cancel.cancel()</span><br><span class="line"> print(<span class="string">f"取消请求是否成功发出: <span class="subst">{was_cancelled}</span>"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> asyncio.run(main())</span><br></pre></td></tr></table></figure><p><strong>运行结果分析:</strong></p><ol><li><code>critical_operation</code> 开始执行并打印 “关键操作开始…”。</li><li><code>main</code> 函数创建了这个任务,并用 <code>asyncio.shield()</code> 将其包裹。</li><li><code>canceller</code> 任务在一秒后调用 <code>shielded_task.cancel()</code>。取消请求被 <code>shield()</code> 创建的 <code>Future</code> 吸收了。</li><li>在 <code>main</code> 函数中,<code>await shielded_task</code> 立即抛出 <code>CancelledError</code>,打印 “主协程捕获到 CancelledError…”。这让调用者以为任务已经被取消了。</li><li>然而,内部的 <code>critical_operation</code> 并没有收到 <code>CancelledError</code>,它会继续执行,打印后续的 “关键操作正在进行中…” 直到完成。</li><li>最后,<code>await task</code> 会成功地等待原始任务完成,并获取其返回值,证明了它并未被中断。</li></ol><h3 id="注意事项和限制"><a href="#注意事项和限制" class="headerlink" title="注意事项和限制"></a>注意事项和限制</h3><ul><li><strong><code>shield()</code> 并非万能</strong>:<code>shield()</code> 只能防止其包裹的任务被包含它的协程的取消操作所影响。如果有人直接获得了对内部任务的引用并取消它,那么 <code>shield()</code> 将无法阻止。</li><li><strong>优雅关闭的复杂性</strong>:在处理像 <code>Ctrl+C</code> (SIGINT) 这样的程序中断信号时,通常会取消所有正在运行的任务。在这种情况下,<code>asyncio.shield()</code> 可能不足以保护任务,因为内部任务本身也可能被直接取消。需要更复杂的信号处理和关闭逻辑来实现真正的优雅关闭。</li></ul>]]></content>
<summary type="html">
<p><code>asyncio.shield()</code> 用于保护一个协程(awaitable object)免受取消操作的影响。当一个任务(Task)被取消时,<code>asyncio</code> 会在其内部引发一个 <code>CancelledError</co
</summary>
<category term="Python" scheme="https://jiapan.me/tags/Python/"/>
<category term="asyncio" scheme="https://jiapan.me/tags/asyncio/"/>
<category term="并发编程" scheme="https://jiapan.me/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/"/>
<category term="协程" scheme="https://jiapan.me/tags/%E5%8D%8F%E7%A8%8B/"/>
</entry>
<entry>
<title>红楼梦中的十二生肖梗</title>
<link href="https://jiapan.me/2025/zodiac-meme/"/>
<id>https://jiapan.me/2025/zodiac-meme/</id>
<published>2025-02-23T06:46:41.000Z</published>
<updated>2026-02-04T04:48:14.973Z</updated>
<content type="html"><![CDATA[<p>今天是个周日,公司的服务要做一些迁移工作,需要来公司加班一天,我主要是来提供情绪价值的,并没有太多需要我做的事情,借这个难得清闲的工作时间写一篇 blog 吧,讲一个我前段时间读红楼梦时的一个小发现。</p><p>在红楼梦第十四回「林如海捐馆扬州城 贾宝玉路谒北静王」中,提到给秦可卿送殡的队伍,之前几次读到这里时都是草草略过,这次读脂评汇校本时看到下边这么一段批语。批注的人提到,这些名字对应的是十二生肖,这让我眼前一亮,原来这里埋了一个生肖梗。</p><p><img src="1.png" width="60%" style="margin: 0 auto;"></p><p>原文如下:</p><blockquote><p>那时,官客送殡的,有镇国公牛清之孙现袭一等伯牛继宗、 理国公柳彪之孙现袭一等子柳芳、齐国公陈翼之孙世袭三品威镇将军陈瑞文、治国公马魁之孙世袭三品威远将军马尚、修国公侯晓明之孙世袭一等子侯孝康;缮国公诰命亡故,其孙石光珠守孝不曾来得。</p></blockquote><p>先来回顾一下十二生肖:子鼠 丑牛 寅虎 卯兔 辰龙 巳蛇 午马 未羊 申猴 酉鸡 戊狗 亥猪。</p><p>接下来我来拆解每一句,逐个盘点下这些名字所对应的生肖:</p><h1 id="子鼠-丑牛"><a href="#子鼠-丑牛" class="headerlink" title="子鼠 丑牛"></a>子鼠 丑牛</h1><h2 id="镇国公牛清之孙现袭一等伯牛继宗"><a href="#镇国公牛清之孙现袭一等伯牛继宗" class="headerlink" title="镇国公牛清之孙现袭一等伯牛继宗"></a>镇国公牛清之孙现袭一等伯牛继宗</h2><ul><li>牛<strong>清</strong>的「清」,表示水,在天干地支中,“子”是十二地支之一,对应的五行是“水”,也就是地支五行中说的「子亥属水」,所以这里的清就代表<strong>子鼠</strong></li><li>牛很明显是<strong>丑牛</strong></li></ul><p>这里也把地支五行贴一下,来自百度百科:寅卯属木,寅为阳木,卯为阴木;巳午属火,午为阳火,巳为阴火;申酉属金,申为阳金,酉为阴金,<strong>子亥属水</strong>,子为阳水,亥为阴水;辰戌丑未属土,辰戌为阳土,丑未为阴土。</p><h1 id="寅虎-卯兔"><a href="#寅虎-卯兔" class="headerlink" title="寅虎 卯兔"></a>寅虎 卯兔</h1><h2 id="理国公柳彪之孙现袭一等子柳芳"><a href="#理国公柳彪之孙现袭一等子柳芳" class="headerlink" title="理国公柳彪之孙现袭一等子柳芳"></a>理国公柳彪之孙现袭一等子柳芳</h2><ul><li>柳<strong>彪</strong>的「彪」字,左半边是个虎,代表<strong>寅虎</strong></li><li><strong>柳</strong>彪的「柳」字右边是个卯字代表<strong>卯兔</strong></li></ul><h1 id="辰龙-巳蛇"><a href="#辰龙-巳蛇" class="headerlink" title="辰龙 巳蛇"></a>辰龙 巳蛇</h1><h2 id="齐国公陈翼之孙世袭三品威镇将军陈瑞文"><a href="#齐国公陈翼之孙世袭三品威镇将军陈瑞文" class="headerlink" title="齐国公陈翼之孙世袭三品威镇将军陈瑞文"></a>齐国公陈翼之孙世袭三品威镇将军陈瑞文</h2><ul><li><strong>陈</strong>谐音晨辰,代表<strong>辰龙</strong></li><li>陈<strong>翼</strong>的「翼」对应翼宿,即翼火蛇,二十八宿之一,所以翼代表的是<strong>巳蛇</strong></li></ul><h1 id="午马-未羊"><a href="#午马-未羊" class="headerlink" title="午马 未羊"></a>午马 未羊</h1><h2 id="治国公马魁之孙世袭三品威远将军马尚"><a href="#治国公马魁之孙世袭三品威远将军马尚" class="headerlink" title="治国公马魁之孙世袭三品威远将军马尚"></a>治国公马魁之孙世袭三品威远将军马尚</h2><ul><li><strong>马</strong>魁的「马」,午马</li><li>马<strong>魁</strong>的「魁」,左边是个鬼字,指二十八宿鬼宿中的即<strong>鬼金羊</strong>,所以魁代表未羊</li></ul><p>在这个禽星表中可以看到前边所说的「翼火蛇」和「鬼金羊」</p><p><img src="2.png" width="60%" style="margin: 0 auto;"></p><h1 id="申猴-酉鸡"><a href="#申猴-酉鸡" class="headerlink" title="申猴 酉鸡"></a>申猴 酉鸡</h1><h2 id="修国公侯晓明之孙世袭一等子侯孝康"><a href="#修国公侯晓明之孙世袭一等子侯孝康" class="headerlink" title="修国公侯晓明之孙世袭一等子侯孝康"></a>修国公侯晓明之孙世袭一等子侯孝康</h2><ul><li><strong>侯</strong>晓明的「侯」字,谐音猴,即申猴</li><li>侯<strong>晓明</strong>的「晓明」,值在清晨鸣叫的鸡,对应酉鸡</li></ul><h1 id="戊狗-亥猪"><a href="#戊狗-亥猪" class="headerlink" title="戊狗 亥猪"></a>戊狗 亥猪</h1><h2 id="缮国公诰命亡故,其孙石光珠守孝不曾来得"><a href="#缮国公诰命亡故,其孙石光珠守孝不曾来得" class="headerlink" title="缮国公诰命亡故,其孙石光珠守孝不曾来得"></a>缮国公诰命亡故,其孙石光珠守孝不曾来得</h2><ul><li>前半句「缮国公诰命亡故」,脂评本中提到:缮国公名叫「守业」,因为狗是用来看家守业的,所以这里指代的是戊狗。(我觉得这个解释稍微牵强了一点点)</li><li><strong>石</strong>光珠的「石」,谐音豕(shǐ),指猪即亥猪。脂评中没有提到「珠」字,我觉得也是猪的意思。</li></ul><p>曹公在每句话中暗藏了两个生肖,实数妙哉。</p>]]></content>
<summary type="html">
<p>今天是个周日,公司的服务要做一些迁移工作,需要来公司加班一天,我主要是来提供情绪价值的,并没有太多需要我做的事情,借这个难得清闲的工作时间写一篇 blog 吧,讲一个我前段时间读红楼梦时的一个小发现。</p>
<p>在红楼梦第十四回「林如海捐馆扬州城 贾宝玉路谒北静王」中,
</summary>
</entry>
<entry>
<title>2024年度回顾</title>
<link href="https://jiapan.me/2024/2024-review/"/>
<id>https://jiapan.me/2024/2024-review/</id>
<published>2024-12-27T10:04:31.000Z</published>
<updated>2026-02-04T04:48:12.914Z</updated>
<content type="html"><![CDATA[<ul><li>从去年5月份开始写<strong>日记</strong>,今年365天笔耕不辍,<strong>多邻国</strong>也是一天不落</li><li>读了<strong>54本书</strong>,创下了新高,包含5本漫画书,今年入坑了海贼王</li><li><strong>念念</strong>正式步入了小学,认识了很多字,不知未来能否留在北京上初高中,留给我的时间不多了</li><li>念念上<strong>陶艺</strong>少年宫第二年,周六上午送念念上课后,享受独自在咖啡店看书的时光</li><li><strong>登登</strong> 1 岁半了,正在牙牙学语,很粘他的姐姐,姐姐也已经接纳了弟弟</li><li><strong>北京玩</strong>了2处:年会去了环球影城、春节去了水裹汤泉</li><li><strong>外地玩</strong>了2处:自己去了一趟西安、全家一起去了趟秦皇岛</li><li>念念去了广州的<strong>长隆野生动物园</strong>,学校组织去了<strong>航空博物馆</strong></li><li>和老家的亲戚们一起把<strong>祖坟</strong>迁了位置,参加了一场娘家人的<strong>葬礼</strong></li><li>买了一辆大行P8折叠车,夏天秋天的时候着实骑了一阵子,喜欢<strong>骑行</strong>时的自由</li><li>买了一个日版破解 <strong>Switch</strong>,开始让念念接触优质游戏</li><li>念念通关了<strong>星之卡比</strong>探索发现,我通关了<strong>路易基鬼屋3</strong></li><li>换了一份<strong>新工作</strong>,算是赶上了 AI 的浪潮,希望是我的最后一份工作</li><li>2、3月份全家人二阳,9、10月份两个孩子轮流<strong>支原体</strong></li><li>除了我和两个孩子,家里其他人都住院做过一场<strong>手术</strong>,每个手术原因不同,11月是昏暗的一个月</li><li>喝了一年的中药调理<strong>睡眠</strong>,有所好转,服用艾司唑仑的频率有下降</li><li>10月份做了一笔糟糕的<strong>投资</strong>,此生再不碰 A 股,长期定投标普500和黄金 ETF</li><li><strong>体重</strong>依然在73-75kg之间浮动,没有达成年初时立的降到70kg以下的 flag</li><li>关闭了<strong>朋友圈</strong>,未来也不会再开了</li></ul>]]></content>
<summary type="html">
<ul>
<li>从去年5月份开始写<strong>日记</strong>,今年365天笔耕不辍,<strong>多邻国</strong>也是一天不落</li>
<li>读了<strong>54本书</strong>,创下了新高,包含5本漫画书,今年入坑了海贼王</li>
<li
</summary>
<category term="生活" scheme="https://jiapan.me/categories/%E7%94%9F%E6%B4%BB/"/>
<category term="生活" scheme="https://jiapan.me/tags/%E7%94%9F%E6%B4%BB/"/>
<category term="年度回顾" scheme="https://jiapan.me/tags/%E5%B9%B4%E5%BA%A6%E5%9B%9E%E9%A1%BE/"/>
<category term="总结" scheme="https://jiapan.me/tags/%E6%80%BB%E7%BB%93/"/>
</entry>
<entry>
<title>《论语》公冶长篇中孔子点评过的弟子</title>
<link href="https://jiapan.me/2024/gong-ye-chang/"/>
<id>https://jiapan.me/2024/gong-ye-chang/</id>
<published>2024-11-26T10:04:31.000Z</published>
<updated>2026-02-04T04:48:13.677Z</updated>
<content type="html"><![CDATA[<h2 id="公冶长,字子芝"><a href="#公冶长,字子芝" class="headerlink" title="公冶长,字子芝"></a>公冶长,字子芝</h2><p>公冶长一生治学,德才兼备,虽然做过牢,可孔子不觉得他有罪,就把女儿嫁给了他;</p><blockquote><p>子谓公冶长:“可妻也,虽在缧绁之中,非其罪也!”以其子妻之。</p></blockquote><h2 id="南宫括,字子容"><a href="#南宫括,字子容" class="headerlink" title="南宫括,字子容"></a>南宫括,字子容</h2><p>南宫适知晓分寸,进退有度,好社会能干、坏社会能自保,孔子把侄女嫁给了他;</p><blockquote><p>子谓南容:“邦有道不废;邦无道免于刑戮。”以其兄之子妻之。</p></blockquote><h2 id="宓不齐,字子贱"><a href="#宓不齐,字子贱" class="headerlink" title="宓不齐,字子贱"></a>宓不齐,字子贱</h2><p>宓子贱仁德好学,刚正不阿;</p><blockquote><p>子谓子贱:“君子哉若人!鲁无君子者,斯焉取斯?”</p></blockquote><h2 id="端木赐,字子贡"><a href="#端木赐,字子贡" class="headerlink" title="端木赐,字子贡"></a>端木赐,字子贡</h2><p>子贡心高气傲,有点喜欢自我标榜,孔子总是苦口婆心地对他旁敲侧击;</p><blockquote><p>子贡问曰:“赐也何如?”子曰:“女,器也。”曰:“何器也?”曰:“瑚琏也。”<br>子贡曰:“我不欲人之加诸我也,吾亦欲无加诸人。”子曰:“赐也,非尔所及也。”</p></blockquote><h2 id="冉雍,字仲弓"><a href="#冉雍,字仲弓" class="headerlink" title="冉雍,字仲弓"></a>冉雍,字仲弓</h2><p>冉雍朴素踏实、勇于实干;</p><blockquote><p>或曰:“雍也仁而不佞。”子曰:“焉用佞?御人以口给,屡憎于人。不知其仁,焉用佞?”</p></blockquote><h2 id="漆雕开,字子开"><a href="#漆雕开,字子开" class="headerlink" title="漆雕开,字子开"></a>漆雕开,字子开</h2><p>漆雕开稳重谦虚,志向高远但也沉得住气;</p><blockquote><p>子使漆雕开仕,对曰:“吾斯之未能信。”子说。</p></blockquote><h2 id="仲由,字子路"><a href="#仲由,字子路" class="headerlink" title="仲由,字子路"></a>仲由,字子路</h2><p>子路是个急性子,自视甚高,直来直去,有什么说什么;</p><blockquote><p>子曰:“道不行,乘桴浮于海,从我者其由与?”子路闻之喜,子曰:“由也好勇过我,无所取材。”<br>子路有闻,未之能行,唯恐有闻。</p></blockquote><h2 id="公西赤,字子华"><a href="#公西赤,字子华" class="headerlink" title="公西赤,字子华"></a>公西赤,字子华</h2><p>公西赤善于外交,口才一流;</p><blockquote><p>“赤也何如?”子曰:“赤也,束带立于朝,可使与宾客言也。不知其仁也。”</p></blockquote><h2 id="冉求,字子有"><a href="#冉求,字子有" class="headerlink" title="冉求,字子有"></a>冉求,字子有</h2><p>冉求多才多艺,特别会管钱,但是因为帮季氏敛财,受到孔子的严厉批评,后来跟孔子学习之后逐渐成为仁德之人;</p><blockquote><p>“求也何如?”子曰:“求也,千室之邑,百乘之家,可使为之宰也,不知其仁也。”、</p></blockquote><h2 id="宰予,字子我"><a href="#宰予,字子我" class="headerlink" title="宰予,字子我"></a>宰予,字子我</h2><p>宰我调皮捣蛋,能言善辩,他最出名的事就是白天睡觉被老师骂;</p><blockquote><p>宰予昼寝,子曰:“朽木不可雕也,粪土之墙不可杇也,于予与何诛?”子曰:“始吾于人也,听其言而信其行;今吾于人也,听其言而观其行。于予与改是。”</p></blockquote><h2 id="申枨(申党),字周"><a href="#申枨(申党),字周" class="headerlink" title="申枨(申党),字周"></a>申枨(申党),字周</h2><p>申枨精通六艺,但欲望比较强,孔子觉得他还没培养出刚健的气质。</p><blockquote><p>子曰:“吾未见刚者。”或对曰:“申枨。”子曰:“枨也欲,焉得刚。”</p></blockquote><p>参考:</p><ul><li><a href="https://baike.baidu.com/item/%E5%AD%94%E9%97%A8%E4%B8%83%E5%8D%81%E4%BA%8C%E8%B4%A4/124049" target="_blank" rel="noopener">https://baike.baidu.com/item/孔门七十二贤/124049</a></li><li><a href="https://lunyu.5000yan.com/" target="_blank" rel="noopener">https://lunyu.5000yan.com/</a></li><li>靳大成论语通读</li></ul>]]></content>
<summary type="html">
<h2 id="公冶长,字子芝"><a href="#公冶长,字子芝" class="headerlink" title="公冶长,字子芝"></a>公冶长,字子芝</h2><p>公冶长一生治学,德才兼备,虽然做过牢,可孔子不觉得他有罪,就把女儿嫁给了他;</p>
<blockq
</summary>
<category term="读书" scheme="https://jiapan.me/categories/%E8%AF%BB%E4%B9%A6/"/>
<category term="论语" scheme="https://jiapan.me/tags/%E8%AE%BA%E8%AF%AD/"/>
<category term="孔子" scheme="https://jiapan.me/tags/%E5%AD%94%E5%AD%90/"/>
<category term="儒家" scheme="https://jiapan.me/tags/%E5%84%92%E5%AE%B6/"/>
<category term="国学" scheme="https://jiapan.me/tags/%E5%9B%BD%E5%AD%A6/"/>
</entry>
<entry>
<title>《金钱心理学》摘抄</title>
<link href="https://jiapan.me/2024/The-Psychology-of-Money/"/>
<id>https://jiapan.me/2024/The-Psychology-of-Money/</id>
<published>2024-10-31T10:04:31.000Z</published>
<updated>2026-02-04T04:48:13.363Z</updated>
<content type="html"><![CDATA[<p>我所读过的理财类书籍并不多,在国庆后由于人性的贪婪,在股市中损失了(对我来说)一大笔钱,机缘巧合下读了这本名叫《金钱心理学》的理财类书籍。这是我读的为数不多的觉得写的非常好的理财书之一,哪怕不限于理财类,它也是一本用来了解人性和世界观的好书,由于得到了非常好的阅读体验,从另一方面来说这次的投资失利也许对我来说属于因祸得福了。</p><p>在读《金钱心理学》时,我脑海中经常飘出一句励志的话:「种一棵树最好的时间是十年前,其次是现在」,刚刚查了一下这句话的来源,出自非洲经济学家丹比萨·莫约的《援助的死亡》一书,巧合的是也是一位经济学家说的。我现在已经开始了超长线的定投计划,用十年时间来定投黄金和标普500,自从开始定投后就出现了两种有冲突的念头:既想让时间过快一点,好让我完成我的定投目标,见证时间和复利带来的强大效果,又想让时间过慢一些,自己还不想那么快的老去,想再多一些时间陪伴孩子们,更不想眼睁睁看着父母一天天的老去。这本书还纠正了我一个错误观念,我之前认为财富跟赚钱多少成非常强的正相关性,这本书告诉我并不是这样,收入当然是一部分,但对大部分人来说更重要的是节俭和储蓄。</p><p>这本书中没有教我们认识各种指标,都是一些软技能,下边是我从这本书中摘录下的句子,通过这些句子也能感受到这本书再讲的是什么样的理财观念。最后我会在写一写我准备开启的一段超长线投资计划。</p><h2 id="我最喜欢的句子"><a href="#我最喜欢的句子" class="headerlink" title="我最喜欢的句子"></a>我最喜欢的句子</h2><ul><li>人们习惯把别人的失败归咎于错误的决策,而把自己的失败归咎于糟糕的运气。</li><li>现代资本擅长创造两种东西:财富和嫉妒。</li><li>时间自由是财富能带给你的最大红利。</li><li>富有的最高级形式是,每天早上起床后你都可以说:“今天我能做我想做的任何事。”</li><li>通过用金钱购买昂贵之物获得的尊重和羡慕可能远比你想象中少。</li><li>历史是对变化的研究,但具有讽刺性的是,人们却将历史当作预测未来的工具。</li><li>杠杆——以负债的方式进行投资——把常规风险扩大到了足以导致毁灭的程度。</li><li>只有当你能给一项计划数年或数十年的时间去成长时,复利的效应才能得到最佳体现。</li><li>无论在工作生涯的哪个节点,都要定下这样均衡的目标:每年做好适中的储蓄,给自己适度的自由时间,让通勤不超过适当的时长,至少花适量的时间来陪伴家人。</li><li>如果你把波动看作要买的入场券,情况就会完全不同。</li><li>市场回报永远不会是免费的,现在不是,将来也不会是。你需要支付一定的费用,就像要花钱购买一件产品一样。</li><li>在做计划的时候,我们会专注于我们想做的和能做的事情,而忽略了他人的计划和能力,但他人的决策也会对结果产生影响。</li><li>用能让你睡踏实的方式来理财。</li><li>如果你想提高投资回报,最简单而有效的方法就是拉长时间。时间是投资中最强大的力量。</li><li>增长是由复利驱动的,而复利通常需要时间。毁灭却可能由独立的致命因素导致,可以在很短的时间内发生;它也可能由失去信心引发,而信心可以在一瞬间崩塌。</li></ul><h2 id="全部摘抄的句子"><a href="#全部摘抄的句子" class="headerlink" title="全部摘抄的句子"></a>全部摘抄的句子</h2><ul><li>一个无法控制个人情绪的天才或许会引发财务上的灾难,但反过来看——那些没有接受过专业金融教育的普通人,也可以凭借与智商衡量标准无关的良好行为习惯,最终走向富裕。</li><li>财务方面的成功并不是一门硬科学,而是一种软技能——你怎么做,比你掌握多少知识更重要。</li><li>有两种事物会影响每一个人,不管你是否对它们感兴趣——健康和金钱。</li><li>我认为,这种现象的主要原因是,我们思考和学习理财的方式更像学习物理的(涉及很多法则和定律),而不像学习心理学的(关注情感及其微妙变</li><li>关于金钱的知识和经验可以被用于生活中的其他许多问题,比如风险、信心和幸福中。很少有其他事物能像金钱这样,仿佛一面强有力的放大镜,帮助你理解人们为何会做出某些举动。</li><li>人类涉及金钱的行为是地球上最伟大的表演之一。</li><li>历史从来不会重复,人类却总会重蹈覆辙。</li><li>你对金钱的个人经验可能只有0.00000001%符合实际,但它构成了你对世界运作方式的主观判断的80%。</li><li>研究股市的历史后,你会觉得自己明白了某些事,但只有亲身经历过,感受过它的巨大影响,你才可能真正改变自己的行为</li><li>有些事只有真正经历过才会懂。</li><li>人们一生中的投资决策在很大程度上取决于其生活经历——尤其是成年后的早期经历。</li><li>每个人对金钱的体验都是不同的,即使是在那些你觉得经历很相似的人之间。</li><li>个体的不同经历可能导致他们对那些看似没有争议的话题出现完全不同的看法。</li><li>人们做的与金钱相关的每个决定都有其合理的一面,因为这些决定是他们在掌握了当时所能掌握的信息,然后将其纳入自己对世界运作方式的独特认知框架后做出的。</li><li>每个关于金钱的决定对当时的他们来说都是合理的,是建立在他们当时具备的条件之上的选择。</li><li>我们之所以经常在金钱方面做出看似疯狂的决策,是因为相较之下在这场游戏里我们都是新手,而在你看来不可理喻的行为对我而言却合乎情理。但是,没有谁真的失去了理智——我们都在依靠自己独特的经验做出选择,而这些经验在特定的时间点和情境下都是合理的。</li><li>生活中的每一个结果都受到个人努力之外的其他作用的影响。</li><li>任何事都没有表面看来那样美好或糟糕。</li><li>在生活这场游戏中起作用的除了我们自己,还有其他70亿人,同时还存在着无数的变量。那些在你控制之外的行为产生的意外影响可能比你有意识的行为产生的影响更大。</li><li>因为运气难以被量化,把他人的成功归咎于运气又是一种不礼貌的举动,所以我们大多数时候会自动忽略运气在成功中扮演的重要角色。</li><li>在评价别人时,将成就归功于运气会显得你很嫉妒和刻薄,哪怕我们知道的确存在运气的成分;而在评价自己时,将成就归功于运气则会令自己感到气馁,难以接受。</li><li>人们习惯把别人的失败归咎于错误的决策,而把自己的失败归咎于糟糕的运气</li><li>不要太关注具体的个人和案例研究,而要看到具有普适性的模式。</li><li>预防失败的诀窍是:做好你的财务规划,使其不至于因为一次糟糕的投资和未能达成的财务目标而全盘崩溃,保证自己能在投资道路上持续前进,一直等到好运降临的那一刻。</li><li>风险的存在也意味着在评价自身的失败时,我们应该原谅和理解自己。</li><li>为了赚他们并未拥有也不需要的钱,他们拿自己已经拥有并确实需要的东西去冒险了。这是愚蠢至极的做法。冒着失去重要东西的风险去争取并不重要的东西的行为毫无道理可言。</li><li>最难的理财技能是让逐利适可而止。</li><li>现代资本擅长创造两种东西:财富和嫉妒。</li><li>幸福是你拥有的减去你期待的。</li><li>攀比就像一场没有人能打赢的战役,取胜的唯一办法是根本不要加入这场战争——用知足的态度接受一切,即使这意味着自己比周围的人逊色</li><li>如果你无法拒绝潜在的金钱诱惑,那么欲望最终可能将你吞没。</li><li>一个领域里的知识和经验常常可以为其他领域提供重要的借鉴。</li><li>冰期形成的主要原因并非极寒的冬季,而是凉爽的夏季。</li><li>地球冰川形成的关键并不一定是大量的降雪,而是雪能累积下来,无论量有多少。</li><li>成功的投资并不需要你一直做出成功的决定。你只要做到一直不把事情搞砸就够了。</li><li>但守富的方式却只有一种:在保持节俭的同时,还需要一些谨小慎微。</li><li>致富和守富是两种完全不同的技能。</li><li>致富需要的是冒险精神、乐观心态,以及放手一搏的勇气。</li><li>守富需要谦逊和敬畏之心,需要清楚财富来得有多快,去得就有多容易。守富需要节俭,并要承认你获得的财富中一部分源自运气,所以不要指望无限复制过去的成功。</li><li>生存应该成为你一切策略的基础,无论是关于投资、规划个人职业还是经营生意的</li><li>没有任何收益值得你冒失去一切的风险。</li><li>你的财务规划要求的具体前提条件越多,你的财务状况就越脆弱。</li><li>从长远看结果是积极的,但从短期看过程可能很糟糕”这一点乍看之下不符合直觉,但生活中很多事确实是这样的。</li><li>经济、市场和个人职业生涯通常也会遵循一条相似的路径——在不断的损失中持续增长的过程。</li><li>对一个投资者来说,为了避免心态膨胀,付出再大的代价都是值得的。</li><li>当投资者持有这些藏品的时间足够长,这系列投资组合的整体收益就会趋近其中表现最好的部分的收益</li><li>一个投资者在一半的时间里都看走了眼,最后却仍然能致富,这个事实是不符合直觉的。它也意味着我们低估了许多事物失败的频率,所以当失败发生时,我们就会反应过度</li><li>任何规模巨大、利润丰厚、声名远播或影响力深远的事物都源自某个尾事件——从几千甚至几百万个事件中脱颖而出的一个。</li><li>拿破仑对军事天才的定义是“当身边所有人都进入非理性状态时还能继续正常行事的人”。</li><li>“当下”其实并没有那么重要。作为投资者,你今天、明天或下周做的决定远不如你一生中个别几天做的决定重要。</li><li>一个投资天才也应该是一个当身边所有人都进入非理性状态时还能继续正常行事的人。</li><li>如果你是一个优秀的雇员,在经过三番五次的尝试和试验后,你终究会在适合自己的领域找到适合自己的公司。</li><li>当我们特别关注某些榜样的成功时,我们就会忽视这样一个事实:他们的成功来自他们全部行为中的一小部分。这种忽视会让我们觉得我们自己的失败、亏损和挫折是因为我们做错了什么。</li><li>“重要的不是你对了还是错了,”“金融大鳄”乔治·索罗斯(George Soros)曾说,“而是当你对的时候,你能赚到多少,或者当你错的时候,你会损失多少。”你即使有一半的时间都在犯错,到最后依然能赢。</li><li>时间自由是财富能带给你的最大红利。</li><li>富有的最高级形式是,每天早上起床后你都可以说:“今天我能做我想做的任何事。”</li><li>幸福是一个复杂的话题,因为每个人的幸福观都不同,但如果幸福的分数有一个公分母——一种普遍的快乐源泉——那就是对生活的全面掌控。</li><li>在自己喜欢的任何时候和自己喜欢的对象做想做的事,而且想做多久就做多久,这样的自由是极其珍贵的,而这就是金钱能带给我们的最大红利</li><li>不是工资多少,不是房子大小,也不是工作好坏,而是对自己想做什么、什么时候做、和谁一起做拥有掌控能力。这是生活中决定幸福感的通用变量。</li><li>金钱最大的内在价值是它能赋予你掌控自己时间的能力——这句话没有任何夸张的成分。</li><li>拥有更多财富则意味着在失业后可以从容地等待更好的职业机会,而不必急于抓住遇到的第一根救命稻草。这种能力可以改变一个人的生活。</li><li>拥有更多财富则意味着可以选择一份待遇不高但时间灵活的,或是通勤时间比较短的工作</li><li>做一份自己喜欢却无法掌控时间的事和做自己讨厌的事没什么区别。</li><li>与前几代人相比,我们对时间的控制力降低了。正因为控制时间是影响幸福感的关键因素,所以我们无须对尽管现在的我们更富有了,但我们没有感到更快乐这一事实感到惊讶。</li><li>这里存在一个悖论:我们都想通过财富来告诉其他人,自己应该受到他们的爱慕与敬仰。但事实上,其他人常常会跳过敬仰你这一步。这并不是因为他们觉得你的财富不值得羡慕,而是因为他们会把你的财富当作标尺,转而表达自己渴望被爱慕与敬仰的愿望。</li><li>你或许觉得你需要一辆昂贵的车子、一块豪华的手表和一座很大的房子,但我想告诉你的是,你并非真想得到这些东西本身。你真想得到的是来自他人的尊重和羡慕。你觉得拥有昂贵的东西会让别人尊重和羡慕你,但可惜,别人不会——尤其是那些你希望得到其尊重和羡慕的人。</li><li>通过用金钱购买昂贵之物获得的尊重和羡慕可能远比你想象中少。</li><li>比起豪车,谦虚、善良和同情心等人格特质才能帮你获得更多尊重。</li><li>炫富是让财富流失的最快途径。</li><li>我们总是喜欢用看到的东西为标准来判断一个人是否富有,因为这些是摆在我们面前、实实在在的东西。</li><li>现代资本主义致力于帮助人们通过超前消费的方式来享受原本力不能及的物质生活,并将这种消费观发展为一个备受推崇的产业。</li><li>财富并不是我们能看到的外在部分。</li><li>财富是由未被转化为实物的金融资产体现的</li><li>让自己感到富有的最佳方式莫过于把大笔钱花在那些真正美好的东西上。但想真变得富有,你需要做的是花自己已经有的钱,而不是透支还不属于自己的钱。事情就是这么简单。</li><li>想真变得富有,唯一的途径就是别去消耗你拥有的财富。这不仅仅是积累财富的唯一方式,也是富有的真正定义</li><li>人们对一次身体锻炼所能燃烧的能量的估值比实际消耗的能量高了4倍,而他们接下来平均摄入的能量大约是运动中消耗的能量的2倍。</li><li>我们很容易找到有钱的人做榜样,但想找到富有的人却不容易,因为从性质上讲,他们的成功更隐蔽。</li><li>富有的前提其实是克制。</li><li>我们擅长通过模仿来学习,但财富看不见的特性让我们很难模仿和学习他人的经验。</li><li>这个世界上有很多看起来低调但实际上很富有的人,还有很多看上去很有钱却生活在破产边缘的人。</li><li>个人的节俭和储蓄行为——在金融方面的节约和高效——是金钱等式中你具备更强控制力的部分,而且在未来也会像今天一样,是百分百行得通的方法。</li><li>财富是对收入扣除开支后剩下的部分进行积累的结果。</li><li>即使你收入不高,你依然可以积累财富,但如果你的储蓄率不高,你绝不可能积累财富——两相对比,孰轻孰重显而易见。</li><li>如果你学会用更少的钱来获得同样多的幸福感,你的欲望和所得之间就会产生积极的落差。你也可以通过提升收入来造就这种落差,但欲望和所得之间的落差才是你更容易控制的。</li><li>但在金钱收支公式的两端,人们在一端投入了大量的精力,在另一端却鲜有作为。这就给了大多数人一个机会</li><li>当你把存款定义为虚荣的自我和收入之差时,你就能明白,为什么很多收入不低的人很难存下钱来,因为他们每天都在和自己想要尽情炫耀并与其他炫富者攀比的本能抗争。</li><li>在一个智力方面的竞争已经白热化,而很多旧有技术已经被自动化技术取代的世界里,竞争优势开始转向更加细微的软件层面,比如沟通能力、共情能力,以及最重要的一点——一个人的灵活度。</li><li>当智力不再是一种持久的优势时,拥有别人没有的灵活度是少数几种能帮你拉开与别人的距离的特质。</li><li>在做投资决策时,不要试图保持绝对理性,而要做出对你而言合乎情理,也就是更好接受的选择。</li><li>坚持对理财来说才是至关重要的一点。</li><li>医生的职责不是简单地治好病,而是使用能让病人接受的人性化手段治好病。</li><li>在影响收益表现(包括收益额和在一定时间内有所收益的概率)的诸多金融参数中,相关性最大的莫过于在经济不景气的年份对投资策略的长期坚持。</li><li>任何能让你留在投资游戏中的因素都会在时间方面增强你的优势。</li><li>如果你一开始就对投资对象很感兴趣——这家企业的使命、产品、团队和技术等方面都非常合你的口味——那么当它因为收益下滑或需要帮助而进入不可避免的低谷期时,你至少会因为感到自己在做一件有意义的事而对损失没有那么在意。</li><li>在其他一些涉及金钱的情况下,做个现实主义者也比做个绝对理性主义者强。</li><li>大多数对经济和股市走向的预测都极不靠谱,但是做预测这种行为本身是合乎情理的。</li><li>人生中很少有理论与现实一致的时候。</li><li>历史是对变化的研究,但具有讽刺性的是,人们却将历史当作预测未来的工具。</li><li>世界上总在发生过去从来没有发生过的事。</li><li>历史研究的主要内容是意料之外的事件,但投资者和经济学家们却经常将其看作对未来不容置疑的指南。</li><li>但是投资并非硬科学。投资从本质上说,是规模巨大的一群人根据有限的信息针对将给他们生活幸福度带来巨大影响的事情做出不完美决策的行为,而这会让最聪明的人也变得紧张、贪婪和疑神疑鬼。</li><li>和金钱相关的任何事背后最重要的驱动力,是人们对各种现象的合理化解释以及对商品和服务的偏好。</li><li>历史上最重要的事件往往是一些重大的、史无前例的意外事件。</li><li>人们对刺激物的反应会随着时间前进而趋于稳定。</li><li>为错误留出余地的行为的智慧就在于承认不确定性、随机性和概率——“一切未知情况”——的存在</li><li>在尽量扩大预测与实际可能发生情况的概率之差的同时,为自己留出即使预测错误也能从头再来的余地。</li><li>安全边际的目的在于让预测变得不再必要。</li><li>那就是我们不能把眼前的世界看成黑白分明的</li><li>在你可以接受可能出现的各种结果的灰色区域展开追求,才是最明智的前进方式。</li><li>但在和金钱有关的几乎所有事务中,人们都低估了容错空间的必要性。</li><li>我们不愿意留出容错空间的原因有两个。第一,我们认为一定有人知道未来会发生什么,因此承认未来的不可知性会让我们感到不舒服。第二,一旦预测成真,你就错过了充分利用该预测去采取行动的时机,会让自己蒙受损失。</li><li>我们很容易低估30%的金融资产损失对自己心理产生的影响。你的信心可能在机会最好的时候严重受挫</li><li>理论上的承受力和情感上的承受力之间的差距是人们常常忽略的一种容错空间。</li><li>获得幸福的最佳方式是把目标定得低一些。</li><li>冒险行为中的乐观偏见”或者“‘俄罗斯轮盘赌在统计学上是可行的’综合征”——当不利结果无论如何都无法接受时,人们一厢情愿地认为出现有利结果的可能性更大的现象。</li><li>如果一件事有95%的概率成功,那么剩下的5%的失败概率就意味着在你人生中的某个时间点,你一定会遭遇失败。如果这种失败意味着输得精光,那么即使出现有利局面的概率是95%,这个险也不值得你去冒,无论它看上去多么诱人。</li><li>杠杆——以负债的方式进行投资——把常规风险扩大到了足以导致毁灭的程度。</li><li>大多数时候的理性乐观主义会让人们忽视极端少数情况下倾家荡产的可能性。</li><li>随时可以做自己想做的事而且想做多久就做多久的能力,才是无限投资回报的源泉。</li><li>在金钱方面,隐患最大的单点故障便是短期开支全部依靠工资,而没有在计划中的开支和将来可能需要的开支之间用存款来建立缓冲空间。</li><li>如果你的理财规划只为已知的风险做准备,那么它会缺乏足够大的安全边际,是无法经受现实世界考验的。</li><li>事实上,每个计划中最重要的部分,就是为计划赶不上变化的情况做好预案。</li><li>我们很难预料到自己未来的想法。</li><li>每个5岁小男孩在成长过程中都有过开拖拉机的梦想。在一个小男孩的眼中,没有什么工作能比每天开着拖拉机,喊着“呜呜呜,嘟嘟嘟,大拖拉机来啦”更美好的事了。</li><li>一个30多岁的新手父母对人生目标的规划是18岁时的他或她无法想象的。</li><li>在我们生命中的每个阶段,我们都会做出一些决定。这些决定会深刻地影响我们未来的生活。当我们实现了曾经的梦想后,我们并不总会对自己当初的决定感到开心。所以我们看到,青少年花了大价钱文身,在长大后又要花大价钱洗掉;有人年轻时急着和某人结婚,上了年纪后却盼着和同一个人离婚;有人中年时努力想得到的东西,年老后却又拼命想放弃……这样的例子不胜枚举。</li><li>只有当你能给一项计划数年或数十年的时间去成长时,复利的效应才能得到最佳体现。</li><li>无论在工作生涯的哪个节点,都要定下这样均衡的目标:每年做好适中的储蓄,给自己适度的自由时间,让通勤不超过适当的时长,至少花适量的时间来陪伴家人。</li><li>在一个人人都随着时间改变的世界上,沉没成本——过去的决策导致的无法收回的支出——就像一头拦路虎。</li><li>投资的成功需要投资者付出相应的代价,但衡量这种代价的不是金钱,而是波动、恐惧、怀疑、不确定感和悔恨——如果你不是那个直接面对它们的人,这些代价都容易被你忽视</li><li>财富之神并不青睐那些只求回报却不愿付出的。</li><li>投资成功需要付出的代价是我们无法立刻看到的。它不会被直观地写在标签上。所以,当你需要支付这种账单时,你会觉得这笔钱并不是为购买好东西而支付的价钱,反倒更像做错事后必须缴纳的罚款,虽然在人们看来,付账是很正常的事,缴纳罚款却是应该避免的,所以人们觉得应该采取某些明智的预防措施,让自己避免受罚。</li><li>把市场波动看作要支付的价钱而不是该缴纳的罚款的视角看似微不足道,却是培养正确理财心态的重要部分</li><li>如果你把波动看作要买的入场券,情况就会完全不同。</li><li>几乎所有波动都是一种费用,而非一笔罚款。</li><li>市场回报永远不会是免费的,现在不是,将来也不会是。你需要支付一定的费用,就像要花钱购买一件产品一样。</li><li>人们常常在缺乏足够信息和不讲逻辑的情况下做出一些理财决定,之后又悔不当初。但站在他们当时的角度来看,这些决定是有道理的。</li><li>投资者们总是会天真地向那些和自己情况不一样的人学习理财经验。</li><li>当投资者的目标和时间规划不同时——在任何一种投资中都会出现这种情况——在一个人看来不合理的价格在另一个人看来也许是可以接受的,因为他们各自关注的因素是不同的</li><li>金融领域内的一条铁律是:金钱会追逐回报的最大化。</li><li>当交易者推高短期回报,更多的投资者就会开始入场。不久之后——通常时间都不会太久——短线投资者就成了最有权威的股市定价者了。</li><li>泡沫与估值上升的关系不大。它体现的其实是另一种情况:随着越来越多的短线投资者入场,交易周期变得越来越短。</li><li>泡沫之所以会形成,并不是因为人们在非理性地参与长期投资,而是因为人们在某种程度上堪称理性地转向短线交易,以追逐不断滚雪球式增长的积极动量。</li><li>很多金融和投资决策都建立在对他人的观察、模仿或与他人对赌的基础上,但如果你不知道为什么有些人会那样做,你就不知道他们那种行为会持续多久,什么会让他们改变主意,或者他们是否会吸取教训并做出调整。</li><li>尽可能努力明确自己玩的是什么游戏。</li><li>但在多年前,我曾这样总结我的理财哲学:我是一名被动的投资者,但对这个世界创造货真价实的经济增长的能力持乐观态度。我相信在接下来的30年里,这种增长会让我的财富不断增加。</li><li>出于一些我无法理解的原因,人们总喜欢听别人说这个世界要完蛋了。</li><li>对绝大多数人来说,保持乐观都是最好的选择,因为这个世界在大多时候对大多数人来说都是越变越好的</li><li>乐观主义是一种信念,相信就算过程中充满坎坷,随着时间过去,你心目中好结果出现的概率也比坏结果出现的概率大</li><li>如果你告诉人们一切都会变得很好,他们可能会不以为然,或者用怀疑的目光看着你。但如果你说他们正处于危险中,你就会获得他们的全部注意力。</li><li>一个在众人心怀绝望时满怀希望的人不会被看重,但一个在众人都心怀希望时满怀绝望的人却会被视为圣人</li><li>人类对失去的过度厌恶是在演化过程中形成的一种保护机制。</li><li>在进行直接比较或权衡时,失去带给我们的精神影响比得到更大。</li><li>相比机遇,对威胁反应更快的生物成功生存和繁殖的可能性才更大</li><li>悲观主义者在推测未来趋势时经常没有将市场会如何适应局势纳入考虑。</li><li>经济学中有一条铁律:极好和极糟的环境都很难长期维持,因为市场的供需会以很难预测的方式对环境进行适应。</li><li>眼前的问题有多糟糕,人们解决问题的动力就有多强——这是经济史中普遍存在的一种现象,却很容易被悲观主义者忽视,</li><li>进步发生得太慢,让人难以发觉,但挫折却出现得太快,让人难以忽视。</li><li>增长是由复利驱动的,而复利通常需要时间。毁灭却可能由独立的致命因素导致,可以在很短的时间内发生;它也可能由失去信心引发,而信心可以在一瞬间崩塌。</li><li>在投资中,你必须认识到成功的代价——在长期增长的背景下出现的波动和损失——并做好为其买单的准备。</li><li>故事是现存的对经济影响最大的潜在力量之一。</li><li>故事是经济发展中最强大的一股力量。</li><li>你越希望某事是真的,你就越容易相信一个高估其成真可能性的故事。</li><li>金融领域内的很多投资观点都带有这样的特性:一旦你听从它们,选择了某种策略或方法,你就同时在金钱和心理上进行了双重投资。</li><li>每个人对世界的看法都是不完整的,但每个人都会编织完整的故事来弥补其中的空白。</li><li>后见之明,即人们解释过去事件的能力,给了我们一种仿佛这个世界可以被理解的错觉,也给了我们一种仿佛这个世界自有其原则的错觉,哪怕在实际上一团混乱的情况下。这是我们在很多领域犯错的重要原因。</li><li>对控制力的幻想比充满不确定性的现实更容易让人接受,所以我们死死抓着某些故事不放,骗自己以为结果尽在掌握。</li><li>在做计划的时候,我们会专注于我们想做的和能做的事情,而忽略了他人的计划和能力,但他人的决策也会对结果产生影响。</li><li>无论是在解释过去还是预测未来时,我们都专注于技能起到的因果性作用,而忽略了运气的重要影响。</li><li>我们专注于我们知道的,忽视了我们不知道的,而这让我们对自己的想法过于自信。</li><li>当事态朝正确的方向发展时,要保持谦逊;当事态朝错误的方向发展时,要心怀谅解或同情。这是因为任何事都没有表面看来那样美好或糟糕。</li><li>虚荣越少,财富越多。你能存下多少钱,要看你彰显自我的需求与你的收入之间的差距,而财富恰恰存在于看不到的地方</li><li>用能让你睡踏实的方式来理财。</li><li>如果你想提高投资回报,最简单而有效的方法就是拉长时间。时间是投资中最强大的力量。</li><li>你应该始终通过衡量自己的整体投资情况,而不是根据某一笔投资的成败来评价自己的表现。</li><li>利用财富来获取对时间的掌控,因为对人生的幸福感而言,最严重而普遍的扣分项就是时间上的不自由。在任何时候和喜欢的人去做喜欢的事而且想做多久就做多久的能力,才是财富能带给你的最大红利。</li><li>多一些善意,少一些奢侈。</li><li>存钱。存就是了。存钱不需要什么特定理由。</li><li>明确成功需要付出的代价。然后做好支付的准备,因为没有什么有价值的东西是免费的。</li><li>你应该喜欢风险,因为长期看它能带给你回报</li><li>这些决策的目的往往不是追求最高的回报,而是尽量降低让伴侣或孩子失望的可能。</li><li>我的目的并不是赚大钱。我想要的不过是独立自主而已</li><li>最主要的秘诀是控制你的欲望,在能力范围内尽可能节俭地生活。自主性与你的收入水平无关,而是由你的储蓄率决定的。而当你的收入超过一定水平后,你的储蓄率是通过控制自己对生活方式的欲望决定的。</li><li>在你负担得起的范围内舒适地生活,不产生过多欲望,你会避免现代西方世界中许多人要承受的巨大社会压力。</li><li>退出无谓的激烈竞争,以获得内心平静为目标来调节你的行为,才是真正的成功。</li><li>比起让金融资产的长期收益最大化,不用每个月还贷款的选择让我们感觉更好,因为这让我感到独立和自由。</li><li>查理·芒格说:“复利的第一条原则是:除非万不得已,永远不要打断这个过程。”</li><li>对大多数投资者来说,用平均成本法【一种以定期及定额投资去积累资产(包括股票及基金)的方法,即“定投”。】去投资低成本的指数基金将是长线投资成功率最高的选择。</li><li>我始终坚持的投资理念是,在投资领域,努力和结果之间几乎没有关系。这是因为世界是由尾事件驱动的——少数几个变量是大部分回报的来源。</li><li>我的投资策略并不依靠选择正确的行业或者把握下一次经济衰退的机会,而是依靠高储蓄率、耐心和认为接下来几十年里全球经济将不断创造价值的乐观态度。</li><li>历史不过是糟心事接踵而至的过程。</li><li>在第二次世界大战结束后,美国人花了75年的时间,培养出了普通家庭对债务文化的高接受度。</li><li>虽然每个群体呐喊的具体细节不同,但他们呐喊的原因——至少部分原因——是在第二次世界大战后形成的对社会本该大体平等的预期落空了。他们没能获得别人获得的利益。</li><li>想想这种心态一旦受到社交媒体和有线新闻网强大的传播力量催化后会变成什么样。在这些平台上,人们比以往任何时候都更容易看到别人是怎样生活的——这就像火上浇油一样。</li><li>互联网让人们越频繁地接触新观点,人们对这些新观点的存在就越感到愤怒</li><li>预期的调整总是晚于实际情况的变化。</li></ul><h2 id="长期理财规划"><a href="#长期理财规划" class="headerlink" title="长期理财规划"></a>长期理财规划</h2><p>我为自己设计了一个为期至少10年的理财规划,规则很简单:通过蚂蚁财富,每天<strong>分别</strong>定投100元到「黄金ETF」 和「标普500ETF」,如果行情出现1%的回撤时则当日加仓50元,当盈利超过30%时则赎回30%的仓位,后边再慢慢加仓定投回去。</p><p>我们来粗略估算了一下10年下来的投资金额:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 每年大约有250个交易日,每年的投资额为:</span></span><br><span class="line">250天*100元*2只股票=5万元</span><br><span class="line"></span><br><span class="line"><span class="comment"># 多预备10%的钱来补仓</span></span><br><span class="line">5万+0.5万=5.5万</span><br><span class="line"></span><br><span class="line"><span class="comment"># 10年下来就是</span></span><br><span class="line">5.5万*10=55万</span><br></pre></td></tr></table></figure><p>按照10年的投资回报率80%(虽然市场不可预测,但人总要有些盼头,况且我定投的两项在过去十年中回报都超过了200%),这样10年下来大概会有44万的投资回报,加上本金刚好100万。虽然这么长的时间只回报44万看起来不多,但这对我来说这是一种无痛的投资方式,用作者的话是:「用能让你睡踏实的方式来理财」。当然在这个过程中有可能还会根据我的生活水平来调整定投金额,比如5年后我提前把房贷还完了,也许就能出多个2、3倍的闲钱用于定投。另外,本段开头也说了,我这个投资计划短则持续10年,长则持续20年、30年,也许在复利的作用下收益远远不止于我上边计算出来的那么多。</p>]]></content>
<summary type="html">
<p>我所读过的理财类书籍并不多,在国庆后由于人性的贪婪,在股市中损失了(对我来说)一大笔钱,机缘巧合下读了这本名叫《金钱心理学》的理财类书籍。这是我读的为数不多的觉得写的非常好的理财书之一,哪怕不限于理财类,它也是一本用来了解人性和世界观的好书,由于得到了非常好的阅读体验,从另
</summary>
</entry>
<entry>
<title>解决低版本SpringBoot使用langchain4j Azure 冲突问题</title>
<link href="https://jiapan.me/2024/springboot-azure-openai/"/>
<id>https://jiapan.me/2024/springboot-azure-openai/</id>
<published>2024-06-26T10:04:31.000Z</published>
<updated>2026-02-04T04:48:14.813Z</updated>
<content type="html"><![CDATA[<p>新公司使用的Java技术栈,我们有部分新业务需要调用 OpenAI 的接口进行交互,之前我找了一个比较轻量的SDK来调用OpenAI的接口,地址是:<a href="https://github.com/Lambdua/openai4j" target="_blank" rel="noopener">https://github.com/Lambdua/openai4j</a> ,这个库作为日常使用足够了,但是一些高阶能力无法满足,而这些也是我们未来会用到的,比如:</p><ul><li>对接微软 Azure 上部署的 GPT 模型</li><li>Function Calling</li><li>RAG</li></ul><p>把第一版功能完成后,这几天工作不是那么多,于是我从Github上找到了这个库<a href="https://github.com/langchain4j/langchain4j" target="_blank" rel="noopener">https://github.com/langchain4j/langchain4j</a> ,从名字就能看出来,这个项目是参考的 Python 的LangChain,Java 库的命名很有意思,很喜欢叫 xxxx4j,4j 的意思是 for Java,比如 log4j。</p><p>我大致看了一下介绍,功能还算完备,给出的demo来看使用方式上可读性也很高,更重要的一点是支持古老的Java8。于是我在项目中进行了引入,将已有代码进行了改造,在跑直接调用 OpenAI 的例子时很顺利,当我切换为 Azure 后问题出现了,报错堆栈如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Exception in thread <span class="string">"main"</span> java.lang.NoClassDefFoundError: reactor/core/Disposable</span><br><span class="line">at java.lang.ClassLoader.defineClass1(Native Method)</span><br><span class="line">at java.lang.ClassLoader.defineClass(ClassLoader.java:<span class="number">756</span>)</span><br><span class="line">at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:<span class="number">142</span>)</span><br><span class="line">at java.net.URLClassLoader.defineClass(URLClassLoader.java:<span class="number">473</span>)</span><br><span class="line">at java.net.URLClassLoader.access$<span class="number">100</span>(URLClassLoader.java:<span class="number">74</span>)</span><br><span class="line">at java.net.URLClassLoader$<span class="number">1</span>.run(URLClassLoader.java:<span class="number">369</span>)</span><br><span class="line">at java.net.URLClassLoader$<span class="number">1</span>.run(URLClassLoader.java:<span class="number">363</span>)</span><br><span class="line">at java.security.AccessController.doPrivileged(Native Method)</span><br><span class="line">at java.net.URLClassLoader.findClass(URLClassLoader.java:<span class="number">362</span>)</span><br><span class="line">at java.lang.ClassLoader.loadClass(ClassLoader.java:<span class="number">418</span>)</span><br><span class="line">at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:<span class="number">352</span>)</span><br><span class="line">at java.lang.ClassLoader.loadClass(ClassLoader.java:<span class="number">351</span>)</span><br><span class="line">at com.azure.core.http.netty.NettyAsyncHttpClientProvider.createInstance(NettyAsyncHttpClientProvider.java:<span class="number">81</span>)</span><br><span class="line">at dev.langchain4j.model.azure.InternalAzureOpenAiHelper.setupOpenAIClientBuilder(InternalAzureOpenAiHelper.java:<span class="number">71</span>)</span><br><span class="line">at dev.langchain4j.model.azure.InternalAzureOpenAiHelper.setupSyncClient(InternalAzureOpenAiHelper.java:<span class="number">51</span>)</span><br><span class="line">at dev.langchain4j.model.azure.AzureOpenAiChatModel.<init>(AzureOpenAiChatModel.java:<span class="number">123</span>)</span><br><span class="line">at dev.langchain4j.model.azure.AzureOpenAiChatModel$Builder.build(AzureOpenAiChatModel.java:<span class="number">536</span>)</span><br></pre></td></tr></table></figure><p>我按照堆栈的引导,一步一步去看代码,发现是在创建 HttpClient 对象时挂了,我进到 <code>ConnectionProvider</code> 源码中查看,确实找不到上边说的 <code>Disposable</code> 类,这个类来自 <code>reactor-core</code> 包。通过IDE跳转进的路径看到,目前项目中所使用的 <code>reactor-core</code> 版本是 2.0.8.RELEASE,我找到最新 3.6.7 版本的 <code>reactor-core</code> 源码看了下是有<code>Disposable</code> 这个类的。</p><p>一开始我认为是 langchain4j 的这个项目有问题,去 Github 的 Issue 中搜了下并没有相关的提问,于是我自己开始尝试动手解决,尝试了以下几种方式都不行:</p><ol><li>直接在项目中引入最新版本的 <code>reactor-core</code></li><li>排除(exclusions) <code>langchain4j-azure-open-ai</code> 下的 <code>reactor-core</code> 依赖,保证我自己引入的最新版本生效</li><li>引入 <code>reactor-netty-core</code> 的最新版</li><li>引入全部 <code>langchain4j</code> 的依赖</li><li>重启IDE</li><li>重启电脑</li></ol><p>在做上边的第2步时,启动调试后可以看到,IDE在进入<code>ConnectionProvider</code> 后确实可以正常跳转进<code>Disposable</code> 了,但最终还是报错。通过依赖分析也没有发现和 <code>reactor</code> 的任何冲突,一直搞到晚上下班也没解决。</p><p>今天早上上班后我换了个思路来排查这个项目,创建了一个新项目,只引入 <code>langchain4j</code> 的依赖,可以正常执行,接下来我把我们项目中其他依赖项引进来,发现还是没问题,当我把 parent 引入后问题出现了。虽然 parent 的 pom 文件在远端,但IDEA提供了一个功能,可以修改本地的文件来进行调试,我用二分法删除 parent 中的依赖,最终将问题定位在了:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-dependencies<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>${spring.boot.version}<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">type</span>></span>pom<span class="tag"></<span class="name">type</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>import<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><p>parent 中 <code>spring.boot.version</code> 的值是 <code>1.5.7.RELEASE</code> ,我在上上家公司写Java时就有这个版本了,是个非常老的版本,但升级 SpringBoot 关联的问题会更多。我继续深入进去看,在 <code>spring-boot-dependencies</code> 的 pom 文件中 <code>properties</code> 指定了<code>reactor.version</code> 为 <strong>2.0.8.RELEASE</strong>,这下破案了。之前我无法通过依赖分析找到冲突,也是因为依赖是在 parent 指定的,且这个依赖版本无法在后续进行修改。</p><p>有种覆盖 parent 版本号的方式是在自己项目的父 pom 中的<code>dependencyManagement</code> 下进行声明,我尝试在 <code>dependencyManagement</code> 加上如下片段:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>io.projectreactor<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>reactor-core<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.6.7<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><p>此时报了另一个错误:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line">java.lang.VerifyError: <span class="class"><span class="keyword">class</span> <span class="title">io</span>.<span class="title">netty</span>.<span class="title">channel</span>.<span class="title">kqueue</span>.<span class="title">AbstractKQueueChannel</span>$<span class="title">AbstractKQueueUnsafe</span> <span class="title">overrides</span> <span class="title">final</span> <span class="title">method</span> <span class="title">close</span>.(<span class="title">Lio</span>/<span class="title">netty</span>/<span class="title">channel</span>/<span class="title">ChannelPromise</span></span>;)V</span><br><span class="line"></span><br><span class="line">at java.lang.ClassLoader.defineClass1(Native Method)</span><br><span class="line">at java.lang.ClassLoader.defineClass(ClassLoader.java:<span class="number">756</span>)</span><br><span class="line">at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:<span class="number">142</span>)</span><br><span class="line">at java.net.URLClassLoader.defineClass(URLClassLoader.java:<span class="number">473</span>)</span><br><span class="line">at java.net.URLClassLoader.access$<span class="number">100</span>(URLClassLoader.java:<span class="number">74</span>)</span><br><span class="line">at java.net.URLClassLoader$<span class="number">1</span>.run(URLClassLoader.java:<span class="number">369</span>)</span><br><span class="line">at java.net.URLClassLoader$<span class="number">1</span>.run(URLClassLoader.java:<span class="number">363</span>)</span><br><span class="line">at java.security.AccessController.doPrivileged(Native Method)</span><br><span class="line">at java.net.URLClassLoader.findClass(URLClassLoader.java:<span class="number">362</span>)</span><br><span class="line">at java.lang.ClassLoader.loadClass(ClassLoader.java:<span class="number">418</span>)</span><br><span class="line">at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:<span class="number">352</span>)</span><br><span class="line">at java.lang.ClassLoader.loadClass(ClassLoader.java:<span class="number">351</span>)</span><br><span class="line">at reactor.netty.resources.DefaultLoopKQueue.getChannel(DefaultLoopKQueue.java:<span class="number">50</span>)</span><br><span class="line">at reactor.netty.resources.LoopResources.onChannel(LoopResources.java:<span class="number">243</span>)</span><br><span class="line">at reactor.netty.tcp.TcpResources.onChannel(TcpResources.java:<span class="number">251</span>)</span><br><span class="line">at reactor.netty.transport.TransportConfig.lambda$connectionFactory$<span class="number">1</span>(TransportConfig.java:<span class="number">277</span>)</span><br><span class="line">at reactor.netty.transport.TransportConnector.doInitAndRegister(TransportConnector.java:<span class="number">277</span>)</span><br><span class="line">at reactor.netty.transport.TransportConnector.connect(TransportConnector.java:<span class="number">164</span>)</span><br><span class="line">at reactor.netty.transport.TransportConnector.connect(TransportConnector.java:<span class="number">123</span>)</span><br><span class="line">at reactor.netty.resources.DefaultPooledConnectionProvider$PooledConnectionAllocator.lambda$connectChannel$<span class="number">0</span>(DefaultPooledConnectionProvider.java:<span class="number">519</span>)</span><br><span class="line">at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:<span class="number">61</span>)</span><br><span class="line">at reactor.core.publisher.Mono.subscribe(Mono.java:<span class="number">4568</span>)</span><br><span class="line">at reactor.core.publisher.Mono.subscribeWith(Mono.java:<span class="number">4634</span>)</span><br><span class="line">at reactor.core.publisher.Mono.subscribe(Mono.java:<span class="number">4534</span>)</span><br><span class="line">at reactor.core.publisher.Mono.subscribe(Mono.java:<span class="number">4470</span>)</span><br><span class="line">at reactor.netty.internal.shaded.reactor.pool.SimpleDequePool.drainLoop(SimpleDequePool.java:<span class="number">437</span>)</span><br><span class="line">at reactor.netty.internal.shaded.reactor.pool.SimpleDequePool.pendingOffer(SimpleDequePool.java:<span class="number">600</span>)</span><br><span class="line">at reactor.netty.internal.shaded.reactor.pool.SimpleDequePool.doAcquire(SimpleDequePool.java:<span class="number">296</span>)</span><br><span class="line">at reactor.netty.internal.shaded.reactor.pool.AbstractPool$Borrower.request(AbstractPool.java:<span class="number">430</span>)</span><br><span class="line">at reactor.netty.resources.DefaultPooledConnectionProvider$DisposableAcquire.onSubscribe(DefaultPooledConnectionProvider.java:<span class="number">204</span>)</span><br><span class="line">at reactor.netty.internal.shaded.reactor.pool.SimpleDequePool$QueueBorrowerMono.subscribe(SimpleDequePool.java:<span class="number">720</span>)</span><br><span class="line">at reactor.netty.resources.PooledConnectionProvider.lambda$acquire$<span class="number">2</span>(PooledConnectionProvider.java:<span class="number">170</span>)</span><br><span class="line">at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:<span class="number">61</span>)</span><br><span class="line">at reactor.netty.http.client.HttpClientConnect$MonoHttpConnect.lambda$subscribe$<span class="number">0</span>(HttpClientConnect.java:<span class="number">273</span>)</span><br><span class="line">at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:<span class="number">61</span>)</span><br><span class="line">at reactor.core.publisher.FluxRetryWhen.subscribe(FluxRetryWhen.java:<span class="number">81</span>)</span><br><span class="line">at reactor.core.publisher.MonoRetryWhen.subscribeOrReturn(MonoRetryWhen.java:<span class="number">46</span>)</span><br><span class="line">at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:<span class="number">63</span>)</span><br><span class="line">at reactor.netty.http.client.HttpClientConnect$MonoHttpConnect.subscribe(HttpClientConnect.java:<span class="number">276</span>)</span><br><span class="line">at reactor.core.publisher.Mono.subscribe(Mono.java:<span class="number">4568</span>)</span><br><span class="line">at reactor.core.publisher.Mono.block(Mono.java:<span class="number">1778</span>)</span><br><span class="line">at com.azure.core.http.netty.NettyAsyncHttpClient.sendSync(NettyAsyncHttpClient.java:<span class="number">199</span>)</span><br><span class="line">at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:<span class="number">51</span>)</span><br><span class="line">at com.azure.core.http.policy.HttpLoggingPolicy.processSync(HttpLoggingPolicy.java:<span class="number">183</span>)</span><br><span class="line">at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:<span class="number">53</span>)</span><br><span class="line">at com.azure.core.implementation.http.policy.InstrumentationPolicy.processSync(InstrumentationPolicy.java:<span class="number">101</span>)</span><br><span class="line">at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:<span class="number">53</span>)</span><br><span class="line">at com.azure.core.http.policy.KeyCredentialPolicy.processSync(KeyCredentialPolicy.java:<span class="number">115</span>)</span><br><span class="line">at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:<span class="number">53</span>)</span><br><span class="line">at com.azure.core.http.policy.CookiePolicy.processSync(CookiePolicy.java:<span class="number">73</span>)</span><br><span class="line">at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:<span class="number">53</span>)</span><br><span class="line">at com.azure.core.http.policy.AddDatePolicy.processSync(AddDatePolicy.java:<span class="number">50</span>)</span><br><span class="line">at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:<span class="number">53</span>)</span><br><span class="line">at com.azure.core.http.policy.RetryPolicy.attemptSync(RetryPolicy.java:<span class="number">211</span>)</span><br><span class="line">at com.azure.core.http.policy.RetryPolicy.processSync(RetryPolicy.java:<span class="number">161</span>)</span><br><span class="line">at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:<span class="number">53</span>)</span><br><span class="line">at com.azure.core.http.policy.AddHeadersPolicy.processSync(AddHeadersPolicy.java:<span class="number">66</span>)</span><br><span class="line">at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:<span class="number">53</span>)</span><br><span class="line">at com.azure.core.http.policy.AddHeadersFromContextPolicy.processSync(AddHeadersFromContextPolicy.java:<span class="number">67</span>)</span><br><span class="line">at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:<span class="number">53</span>)</span><br><span class="line">at com.azure.core.http.policy.RequestIdPolicy.processSync(RequestIdPolicy.java:<span class="number">77</span>)</span><br><span class="line">at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:<span class="number">53</span>)</span><br><span class="line">at com.azure.core.http.policy.HttpPipelineSyncPolicy.processSync(HttpPipelineSyncPolicy.java:<span class="number">51</span>)</span><br><span class="line">at com.azure.core.http.policy.UserAgentPolicy.processSync(UserAgentPolicy.java:<span class="number">174</span>)</span><br><span class="line">at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:<span class="number">53</span>)</span><br><span class="line">at com.azure.core.http.HttpPipeline.sendSync(HttpPipeline.java:<span class="number">138</span>)</span><br><span class="line">at com.azure.core.implementation.http.rest.SyncRestProxy.send(SyncRestProxy.java:<span class="number">62</span>)</span><br><span class="line">at com.azure.core.implementation.http.rest.SyncRestProxy.invoke(SyncRestProxy.java:<span class="number">83</span>)</span><br><span class="line">at com.azure.core.implementation.http.rest.RestProxyBase.invoke(RestProxyBase.java:<span class="number">124</span>)</span><br><span class="line">at com.azure.core.http.rest.RestProxy.invoke(RestProxy.java:<span class="number">95</span>)</span><br><span class="line">at com.sun.proxy.$Proxy24.getChatCompletionsSync(Unknown Source)</span><br><span class="line">at com.azure.ai.openai.implementation.OpenAIClientImpl.getChatCompletionsWithResponse(OpenAIClientImpl.java:<span class="number">1444</span>)</span><br><span class="line">at com.azure.ai.openai.OpenAIClient.getChatCompletionsWithResponse(OpenAIClient.java:<span class="number">318</span>)</span><br><span class="line">at com.azure.ai.openai.OpenAIClient.getChatCompletions(OpenAIClient.java:<span class="number">685</span>)</span><br><span class="line">at dev.langchain4j.model.azure.AzureOpenAiChatModel.generate(AzureOpenAiChatModel.java:<span class="number">257</span>)</span><br><span class="line">at dev.langchain4j.model.azure.AzureOpenAiChatModel.generate(AzureOpenAiChatModel.java:<span class="number">215</span>)</span><br></pre></td></tr></table></figure><p>回到最开始的问题,报错误的根本原因是,初始化 Azure模型时需要构造一个 HttpClient,默认情况下会使用<code>ConnectionProvider</code> 来构造。看了下 <code>AzureOpenAiChatModel</code> 的 builder 方法,支持自己传入 <code>OpenAIClient</code>,而 <code>OpenAIClient</code> 可以自己构造 <code>HttpClient</code>,通过这个文档看到 <a href="https://learn.microsoft.com/en-us/azure/developer/java/sdk/http-client-pipeline" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/azure/developer/java/sdk/http-client-pipeline</a> HttpClient 有多种实现,其中可以用 OkHttpClient 来实现,于是我进行了以下魔改:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> OpenAIClient <span class="title">setupSyncClient</span><span class="params">(String endpoint, String serviceVersion, Object credential, Duration timeout, Integer maxRetries, ProxyOptions proxyOptions, <span class="keyword">boolean</span> logRequestsAndResponses)</span> </span>{</span><br><span class="line"> OpenAIClientBuilder openAIClientBuilder = setupOpenAIClientBuilder(endpoint, serviceVersion, credential, timeout, maxRetries, proxyOptions, logRequestsAndResponses);</span><br><span class="line"> <span class="keyword">return</span> openAIClientBuilder.buildClient();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> OpenAIClientBuilder <span class="title">setupOpenAIClientBuilder</span><span class="params">(String endpoint, String serviceVersion, Object credential, Duration timeout, Integer maxRetries, ProxyOptions proxyOptions, <span class="keyword">boolean</span> logRequestsAndResponses)</span> </span>{</span><br><span class="line"> timeout = getOrDefault(timeout, ofSeconds(<span class="number">60</span>));</span><br><span class="line"> HttpClientOptions clientOptions = <span class="keyword">new</span> HttpClientOptions();</span><br><span class="line"> clientOptions.setConnectTimeout(timeout);</span><br><span class="line"> clientOptions.setResponseTimeout(timeout);</span><br><span class="line"> clientOptions.setReadTimeout(timeout);</span><br><span class="line"> clientOptions.setWriteTimeout(timeout);</span><br><span class="line"> clientOptions.setProxyOptions(proxyOptions);</span><br><span class="line"></span><br><span class="line"> Header header = <span class="keyword">new</span> Header(<span class="string">"User-Agent"</span>, <span class="string">"langchain4j-azure-openai"</span>);</span><br><span class="line"> clientOptions.setHeaders(Collections.singletonList(header));</span><br><span class="line"> <span class="comment">// HttpClient httpClient = new NettyAsyncHttpClientProvider().createInstance(clientOptions);</span></span><br><span class="line"> HttpClient httpClient = <span class="keyword">new</span> OkHttpAsyncClientProvider().createInstance(clientOptions);</span><br><span class="line"></span><br><span class="line"> HttpLogOptions httpLogOptions = <span class="keyword">new</span> HttpLogOptions();</span><br><span class="line"> <span class="keyword">if</span> (logRequestsAndResponses) {</span><br><span class="line"> httpLogOptions.setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> maxRetries = getOrDefault(maxRetries, <span class="number">3</span>);</span><br><span class="line"> ExponentialBackoffOptions exponentialBackoffOptions = <span class="keyword">new</span> ExponentialBackoffOptions();</span><br><span class="line"> exponentialBackoffOptions.setMaxRetries(maxRetries);</span><br><span class="line"> RetryOptions retryOptions = <span class="keyword">new</span> RetryOptions(exponentialBackoffOptions);</span><br><span class="line"></span><br><span class="line"> OpenAIClientBuilder openAIClientBuilder = <span class="keyword">new</span> OpenAIClientBuilder()</span><br><span class="line"> .endpoint(ensureNotBlank(endpoint, <span class="string">"endpoint"</span>))</span><br><span class="line"> .serviceVersion(getOpenAIServiceVersion(serviceVersion))</span><br><span class="line"> .httpClient(httpClient)</span><br><span class="line"> .clientOptions(clientOptions)</span><br><span class="line"> .httpLogOptions(httpLogOptions)</span><br><span class="line"> .retryOptions(retryOptions);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (credential <span class="keyword">instanceof</span> String) {</span><br><span class="line"> openAIClientBuilder.credential(<span class="keyword">new</span> AzureKeyCredential((String) credential));</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (credential <span class="keyword">instanceof</span> KeyCredential) {</span><br><span class="line"> openAIClientBuilder.credential((KeyCredential) credential);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (credential <span class="keyword">instanceof</span> TokenCredential) {</span><br><span class="line"> openAIClientBuilder.credential((TokenCredential) credential);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"Unsupported credential type: "</span> + credential.getClass());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> openAIClientBuilder;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> OpenAIServiceVersion <span class="title">getOpenAIServiceVersion</span><span class="params">(String serviceVersion)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (OpenAIServiceVersion version : OpenAIServiceVersion.values()) {</span><br><span class="line"> <span class="keyword">if</span> (version.getVersion().equals(serviceVersion)) {</span><br><span class="line"> <span class="keyword">return</span> version;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> OpenAIServiceVersion.getLatest();</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>从开源代码中拷贝出 <code>setupSyncClient</code> 和 <code>setupOpenAIClientBuilder</code> 方法,并对<code>setupOpenAIClientBuilder</code> 中的HttpClient httpClient 的创建逻辑进行了调整</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// before</span></span><br><span class="line">HttpClient httpClient = <span class="keyword">new</span> NettyAsyncHttpClientProvider().createInstance(clientOptions);</span><br><span class="line"><span class="comment">// after</span></span><br><span class="line">HttpClient httpClient = <span class="keyword">new</span> OkHttpAsyncClientProvider().createInstance(clientOptions);</span><br></pre></td></tr></table></figure><p>初始化Azure模型时传入我自己的 client:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 默认生成的client使用NettyAsyncHttpClientProvider和SpringBoot所依赖的版本不兼容,改用OkHttpAsyncClientProvider进行重写</span></span><br><span class="line">OpenAIClient client = setupSyncClient(System.getenv(<span class="string">"AZURE_OPENAI_ENDPOINT"</span>), <span class="string">""</span>,</span><br><span class="line"> System.getenv(<span class="string">"AZURE_OPENAI_API_KEY"</span>), ofSeconds(<span class="number">30</span>), <span class="number">2</span>, <span class="keyword">null</span>, <span class="keyword">true</span>);</span><br><span class="line"></span><br><span class="line">model = AzureOpenAiChatModel.builder()</span><br><span class="line"> .openAIClient(client)</span><br><span class="line"> .deploymentName(modelName)</span><br><span class="line"> .temperature(<span class="number">0.0</span>)</span><br><span class="line"> .build();</span><br></pre></td></tr></table></figure><p>并在工程中引入 <code>azure-core-http-okhttp</code> 的依赖</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.azure<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>azure-core-http-okhttp<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.12.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><p>再次执行还是报错了,不过这次的错误变为:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">java.lang.NoClassDefFoundError: reactor/util/context/ContextView</span><br><span class="line"></span><br><span class="line">at com.azure.core.http.rest.RestProxy.<init>(RestProxy.java:<span class="number">56</span>)</span><br><span class="line">at com.azure.core.http.rest.RestProxy.create(RestProxy.java:<span class="number">140</span>)</span><br><span class="line">at com.azure.ai.openai.implementation.OpenAIClientImpl.<init>(OpenAIClientImpl.java:<span class="number">144</span>)</span><br><span class="line">at com.azure.ai.openai.OpenAIClientBuilder.buildInnerClient(OpenAIClientBuilder.java:<span class="number">283</span>)</span><br><span class="line">at com.azure.ai.openai.OpenAIClientBuilder.buildClient(OpenAIClientBuilder.java:<span class="number">351</span>)</span><br></pre></td></tr></table></figure><p>还是 reactor 的问题,但可以看到,现在已经不再使用 <code>reactor.core.Disposable</code> 了,也许升级一下 <code>reactor-core</code> 可以解决,我再次在项目的 parent 的<code>dependencyManagement</code> 下引入</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>io.projectreactor<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>reactor-core<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.6.7<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><p>再次尝试,问题解决。</p>]]></content>
<summary type="html">
<p>新公司使用的Java技术栈,我们有部分新业务需要调用 OpenAI 的接口进行交互,之前我找了一个比较轻量的SDK来调用OpenAI的接口,地址是:<a href="https://github.com/Lambdua/openai4j" target="_blank" r
</summary>
</entry>
<entry>
<title>为什么好久没更新了</title>
<link href="https://jiapan.me/2024/Why-not-update-for-a-long-time/"/>
<id>https://jiapan.me/2024/Why-not-update-for-a-long-time/</id>
<published>2024-04-29T06:23:58.000Z</published>
<updated>2026-02-04T04:48:13.374Z</updated>
<content type="html"><![CDATA[<p>可以看到我在去年8、9月份频繁更新了一批文章,然后在11月就戛然而止了。</p><p>昨天早上坐在旁边的同事告诉我,他的女朋友周末把我的博客通过文字转语音的方式边听边做家务,并且想要人肉催更。每次听到有人说读了我的博客,而且希望催更,我都既兴奋又诚惶诚恐。兴奋是因为有人能喜欢读我喜欢写的东西,惶恐是因为居然有人喜欢我写的东西。</p><p>实际在看似停更的这小半年来我并没有停,并且再坚持每日一更,只不过内容放在了另一个站点上,域名是 <a href="https://diary.jiapan.me/" target="_blank" rel="noopener">https://diary.jiapan.me/</a> 。从域名可以看出,这是我写日记的地方,站点标题叫「小小的避难所」,灵感来自毛姆写的《阅读是一座随身携带的避难所》这本书的书名,正如名字写的这样,我把那里作为我的避难所来记录、倾诉我的所感所想。当然那个站点上的内容也不是每天都会更新发布,而是根据我的心情,想起来了就整理一批我在Notion中写的内容发布出去。</p><p>进入避难所有一点小小的门槛,需要留下你的邮箱和阅读原因,邮箱只要是常见域名就可以,会收到一个入场验证码,输入验证码后再说明原因就可以进入了,原因我并不会审核,只要大于5个字符就可以了。</p><p>设置门槛的原因有两个,首先是我希望让这些内容可控,我需要知道都被谁访问过,其次是我不希望这些内容会被爬虫抓到,或者说可以通过搜索引擎搜到。</p><p>为什么我把那些内容单独隔离到了另一个站点内,而没有放在这里,因为那些都是我的日常碎碎念、流水账,每一篇内容都写的很零散,每天晚上我会回顾一下今天值得纪念的事情。拿出几样来记录一下,没有任何主题。</p><p>这个博客内大部分内容都是围绕着一个主题来写的,但这种写法很费精力,而且实话实说我并没有那么多干货。当然我也知道写这种结构化的文章相比写流水账,对个人来说会有更好的提升,我想先通过记录流水账的方式把写作这个习惯培养起来,然后再慢慢进阶。</p><p>所以,如果想继续读我流水账的朋友可以左转进去我的<a href="https://diary.jiapan.me/" target="_blank" rel="noopener">小小避难所</a>,但我也先在这里做个免责声明(狗头保命),那些内容确实不体系化,没有营养,没有干货,读后可能会让你大失所望。引用曹公的一句话:满纸荒唐言。</p><p>顺便说一句,昨天和老板提了离职,准备开启一段新的征程大海,去向暂时保密,等未来有了水花再回来聊一聊这段经历叭。</p>]]></content>
<summary type="html">
<p>可以看到我在去年8、9月份频繁更新了一批文章,然后在11月就戛然而止了。</p>
<p>昨天早上坐在旁边的同事告诉我,他的女朋友周末把我的博客通过文字转语音的方式边听边做家务,并且想要人肉催更。每次听到有人说读了我的博客,而且希望催更,我都既兴奋又诚惶诚恐。兴奋是因为有人能
</summary>
</entry>
<entry>
<title>回归模型 vs 分类模型</title>
<link href="https://jiapan.me/2023/Regression-model-vs-Classification-model/"/>
<id>https://jiapan.me/2023/Regression-model-vs-Classification-model/</id>
<published>2023-11-13T10:33:21.000Z</published>
<updated>2026-02-04T04:48:13.332Z</updated>
<content type="html"><![CDATA[<p>在机器学习中,回归模型和分类模型是两种常见的预测模型,它们的主要区别在于其预测目标和输出类型。</p><h2 id="预测目标"><a href="#预测目标" class="headerlink" title="预测目标"></a>预测目标</h2><ul><li>回归模型的预测目标是连续数值。<ul><li>回归模型用于预测输出变量的数值,例如房价预测或股票价格预测。</li><li>回归模型试图建立输入特征与输出值之间的数值关系。</li></ul></li><li>分类模型的预测目标是离散类别。<ul><li>分类模型用于将输入实例分配到预定义的类别中,例如垃圾邮件分类或图像识别。</li><li>分类模型试图学习输入特征与类别之间的关系。</li></ul></li></ul><h2 id="输出类型"><a href="#输出类型" class="headerlink" title="输出类型"></a>输出类型</h2><ul><li>回归模型的输出是连续的。<ul><li>回归模型生成一个实数或浮点数作为预测结果,可以是任意精度的数值。</li><li>例如,预测某个人的年龄可以是一个实数,如25.6岁。</li></ul></li><li>分类模型的输出是离散的。<ul><li>分类模型预测样本属于预定义类别的概率或直接预测样本的类别标签。</li><li>例如,对于垃圾邮件分类,模型的输出可以是”垃圾邮件”或”非垃圾邮件”。</li></ul></li></ul><h2 id="小孩子都能懂的回归模型解释"><a href="#小孩子都能懂的回归模型解释" class="headerlink" title="小孩子都能懂的回归模型解释"></a>小孩子都能懂的回归模型解释</h2><p>回归模型就像是一个预测机器,可以帮助我们猜测事物的未来。假设你喜欢吃冰淇淋,而冰淇淋的价格通常会随着天气变化而变化。现在,我们可以观察天气情况和冰淇淋的价格,然后用这些信息来猜测未来的价格。</p><p>比如,如果明天是个炎热的夏天,天气很热,那么冰淇淋的价格可能会比较高,因为很多人想要买冰淇淋来解暑。相反,如果明天是个寒冷的冬天,天气很冷,那么冰淇淋的价格可能会比较低,因为很少人会想要吃冰淇淋。</p><p>回归模型就是通过观察过去的天气和冰淇淋价格的关系,来预测将来的价格。它会考虑到很多因素,例如天气、季节和需求,然后给我们一个猜测的价格。虽然它不能百分之百准确地猜测价格,但它可以给我们一个大概的预测,帮助我们做决策。</p><h2 id="小孩子都能懂的分类模型解释"><a href="#小孩子都能懂的分类模型解释" class="headerlink" title="小孩子都能懂的分类模型解释"></a>小孩子都能懂的分类模型解释</h2><p>分类模型就像是一个分类小助手,可以帮助我们将东西归类。想象一下,你有很多玩具,例如球、娃娃和积木。现在,你想要把它们分类整理,把球放在一起、把娃娃放在一起,积木也放在一起。</p><p>分类模型就是帮助我们做这个分类工作的机器。它会观察玩具的特点,比如形状、颜色和材质,然后根据这些特点把它们分成不同的类别。就像是在玩玩具时,你可以根据它们的外观和特点来决定它们应该放在哪个盒子里。</p><p>分类模型可以帮助我们在很多不同的情况下进行分类,比如识别动物、区分水果、辨别颜色等。它可以根据事物的特征将它们分成不同的组别,让我们更好地理解和组织世界。</p><h2 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h2><h3 id="回归模型的应用场景:"><a href="#回归模型的应用场景:" class="headerlink" title="回归模型的应用场景:"></a>回归模型的应用场景:</h3><ol><li><strong>房价预测</strong>:根据房屋的特征(如面积、卧室数量、地理位置等),预测房屋的价格。</li><li><strong>销售量预测</strong>:根据过去的销售数据、广告投入和季节性因素,预测未来某个产品的销售量。</li><li><strong>股票价格预测</strong>:根据股票过去的价格数据、市场指标和新闻事件,预测股票的未来走势。</li><li><strong>气候模型</strong>:根据历史气象数据、大气压力和温度等因素,预测未来的天气情况。</li><li><strong>医学研究</strong>:根据患者的临床特征和生物标记物,预测患者的疾病风险或治疗效果。</li></ol><h3 id="分类模型的应用场景:"><a href="#分类模型的应用场景:" class="headerlink" title="分类模型的应用场景:"></a>分类模型的应用场景:</h3><ol><li><strong>垃圾邮件分类</strong>:根据电子邮件的内容、发件人和其他特征,将电子邮件分为垃圾邮件和非垃圾邮件。</li><li><strong>图像识别</strong>:根据图像的特征和内容,将图像分类为不同的对象或场景,如猫、狗、汽车或风景。</li><li><strong>疾病诊断</strong>:根据患者的症状、体征和医学测试结果,将患者的疾病分类为不同的类别,如心脏病、癌症或糖尿病。</li><li><strong>情感分析</strong>:根据文本的情感特征,将文本分类为积极、消极或中性的情感。</li><li><strong>客户细分</strong>:根据客户的行为、偏好和购买历史,将客户分为不同的细分群体,以便进行个性化营销。</li></ol>]]></content>
<summary type="html">
<p>在机器学习中,回归模型和分类模型是两种常见的预测模型,它们的主要区别在于其预测目标和输出类型。</p>
<h2 id="预测目标"><a href="#预测目标" class="headerlink" title="预测目标"></a>预测目标</h2><ul>
<li>回
</summary>
<category term="技术" scheme="https://jiapan.me/tags/%E6%8A%80%E6%9C%AF/"/>
</entry>
<entry>
<title>为什么我说宝玉是双子座</title>
<link href="https://jiapan.me/2023/why-baoyu-is-gemini/"/>
<id>https://jiapan.me/2023/why-baoyu-is-gemini/</id>
<published>2023-10-16T11:34:05.000Z</published>
<updated>2026-02-04T04:48:14.968Z</updated>
<content type="html"><![CDATA[<p>我在上一篇流水账中提到贾宝玉是双子座,虽然宝玉生日在《红楼梦》原文中一直没有明确写明,但通过一些线索可以推断出宝玉生日是在夏天,且是在农历四、五月左右。证据如下:</p><ul><li>在六十三回「寿怡红群芳开夜宴」一回中,宝玉晚上想搞个 party,林之孝家过来象征性嘱咐了两句:“还没睡呢?如今天长夜短了,该早些睡,明儿起的方早……”「天长夜短」很明显是夏天的特点。</li><li>六十二回「呆香菱情解石榴裙」中,由于香菱和其他姐妹玩斗草游戏才把石榴裙弄脏了,而且也已经提到这一天是宝玉的生日。「斗草」是端午节的游戏,前文中没有描述过斗草,也没提到端午节,所以姐妹们大概率是在端午节前玩的。</li><li>还是六十二回「憨湘云醉眠芍药裀」,北京地区芍药的盛花期是阳历四五月间,芍药花飞了湘云一身,说明已经是凋谢期了,按照花期来推的话应该是阳历五月底。</li><li>网上还有很多证据说宝玉生日就是农历四月二十六,比如根据张道士说的话、送花神等等</li></ul><p>不管哪个证据,宝玉肯定是农历四月底五月初的生日,既阳历(公历)五月底六月初。我按照农历四月二十六这个日期,随机查了几个农历对应的公历(能力有限,只能查到1900年以后的),都是落在双子座的时间范围内。双子座的时间范围是公历5月21日~6月21日。</p><p><img src="Untitled.png" width="600px" style="margin: 0 auto;"></p><p><img src="Untitled 1.png" width="600px" style="margin: 0 auto;"></p><p><img src="Untitled 2.png" width="600px" style="margin: 0 auto;"></p><p><img src="Untitled 3.png" width="600px" style="margin: 0 auto;"></p>]]></content>
<summary type="html">
<p>我在上一篇流水账中提到贾宝玉是双子座,虽然宝玉生日在《红楼梦》原文中一直没有明确写明,但通过一些线索可以推断出宝玉生日是在夏天,且是在农历四、五月左右。证据如下:</p>
<ul>
<li>在六十三回「寿怡红群芳开夜宴」一回中,宝玉晚上想搞个 party,林之孝家过来象征性
</summary>
</entry>
<entry>
<title>投资组合理论 && 风险平价模型</title>
<link href="https://jiapan.me/2023/Portfolio-Theory-and-Risk-Parity-Model/"/>
<id>https://jiapan.me/2023/Portfolio-Theory-and-Risk-Parity-Model/</id>
<published>2023-10-09T13:45:39.000Z</published>
<updated>2026-02-04T04:48:13.331Z</updated>
<content type="html"><![CDATA[<p>投资组合理论和风险平价模型是两种与投资组合管理相关的重要概念,是金融领域中用于优化投资组合的方法。</p><h1 id="投资组合理论"><a href="#投资组合理论" class="headerlink" title="投资组合理论"></a>投资组合理论</h1><p>投资组合理论是由美国经济学家哈里·马科维茨(Harry Markowitz)于20世纪50年代提出的理论框架,也被称为现代投资组合理论(Modern Portfolio Theory,MPT)。该理论旨在帮助投资者在风险和收益之间取得最佳平衡。投资组合理论的核心思想是通过将多种资产组合在一起,以最小化给定预期收益水平下的投资组合风险,或在给定风险水平下最大化预期收益。</p><h2 id="辅助理解"><a href="#辅助理解" class="headerlink" title="辅助理解"></a>辅助理解</h2><p>想象一下,你有一个盒子,里面装着各种不同的玩具,比如娃娃、小车和积木。每个玩具就像是不同的投资。现在,假设你想保护你的玩具并确保它们的价值随着时间增长。</p><p>投资组合理论就像是一种决定你的盒子里应该有多少个不同玩具的方法。你要选择适当的玩具组合,这样你才能获得最好的结果。</p><p>但是,这里有个诀窍:不同的玩具有不同的风险和回报。有些玩具可能更有价值,但风险也更大,而其他的可能更安全,但增长速度较慢。所以你需要决定你愿意承担多少风险。</p><p>投资组合理论帮助你找到合适的平衡点。它建议你选择一种玩具组合,这样你就可以把风险分散开来。这意味着如果一个玩具表现不好,其他的玩具仍然可以让你获得回报。</p><p>简而言之,投资组合理论就是帮助你选择合适的玩具组合,以平衡风险,并让你的盒子里的玩具保持增值。</p><h2 id="如何工作"><a href="#如何工作" class="headerlink" title="如何工作"></a>如何工作</h2><p>假设你有1000美元,你有三种不同的投资选项:股票、债券和黄金。</p><p>现代投资组合理论认为,投资者可以通过合理地分配资金来平衡风险和回报。</p><p>首先,你需要了解每种投资的预期回报和风险。假设股票的预期回报是10%,债券是5%,黄金是3%。同时,股票的风险最高,债券次之,黄金的风险最低。</p><p>现代投资组合理论建议你根据你的风险承受能力和目标来分配资金。假设你对风险比较保守,你可以将60%的资金分配给债券,30%分配给股票,剩下的10%分配给黄金。</p><p>通过这样的分配,你在投资组合中平衡了风险和回报。债券的较高配比可以提供稳定的回报,股票的适度配置可以获得更高的回报,黄金的配置可以提供一定的保值功能。</p><p>现代投资组合理论的关键思想是通过将资金分配到不同的资产上,以实现风险的分散化。这样,即使某个资产表现不佳,其他资产仍可以为你的投资组合提供回报。</p><h2 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h2><ol><li>风险分散:投资组合理论强调通过将不同资产以适当的权重组合在一起,实现风险的分散化,从而降低整体投资组合的风险。</li><li>预期回报最大化:投资组合理论帮助投资者在给定风险水平下,寻找最优的资产配置方式,以最大化预期回报。</li><li>考虑相关性:投资组合理论考虑资产之间的相关性,通过选择不同相关性的资产组合,可以实现更有效的投资组合。</li></ol><h2 id="局限性"><a href="#局限性" class="headerlink" title="局限性"></a>局限性</h2><ol><li>基于历史数据:投资组合理论通常基于历史数据来估计资产的预期回报和风险,但历史表现不一定能准确预测未来。</li><li>忽略非系统风险:投资组合理论主要关注系统性风险,即与整个市场相关的风险,而忽略了非系统性风险,即与特定公司或行业相关的风险。</li><li>需要大量数据和计算:实施投资组合理论需要大量的数据和计算,包括资产的历史表现、相关性矩阵等,这可能对个体投资者或资源有限的投资者来说是一个挑战。</li></ol><h1 id="风险平价模型"><a href="#风险平价模型" class="headerlink" title="风险平价模型"></a>风险平价模型</h1><p>风险平价模型(Risk Parity Model)是一种投资组合管理方法,旨在通过平等分配投资组合中不同资产的风险,实现更平衡的风险暴露。与传统的投资组合管理方法相比,风险平价模型<strong>更加关注风险分散和资产间的相关性</strong>。</p><p>风险平价起源自一个目标收益率为10%、波动率为10%~12%的投资组合,是美国桥水创始人瑞·达利欧在1996年创立的一个投资原则,既全天候资产配置原则。</p><h2 id="辅助理解-1"><a href="#辅助理解-1" class="headerlink" title="辅助理解"></a>辅助理解</h2><p>现在,想象一下你有一张画纸,上面有很多不同的颜色。每种颜色就像是投资中的不同资产,比如红色代表股票,蓝色代表债券,黄色代表房地产等等。</p><p>风险平价模型就是一种方法,让你在画纸上均匀涂上不同的颜色。这样,每种颜色(也就是每种资产)都有相同的风险,就像画纸上每个区域的颜色一样多。</p><p>为什么要这样做呢?因为不同的颜色(或资产)有不同的风险和回报。有些颜色可能非常亮,表示它们的风险更高,但潜在回报也更大。而有些颜色可能相对较暗,表示它们的风险较低,但潜在回报也较小。</p><p>风险平价模型帮助你确保你的画纸上每个颜色(或资产)的风险都是一样的。这样,如果一个颜色表现不好,其他颜色仍然可以给你带来回报。</p><p>简而言之,风险平价模型就是让你在画纸上均匀地涂上不同的颜色,以确保不同资产的风险是平衡的,并让你的投资更加稳定。</p><h2 id="如何工作-1"><a href="#如何工作-1" class="headerlink" title="如何工作"></a>如何工作</h2><p>假设你有1000美元,你想将其投资于两种不同的资产:股票和债券。</p><p>股票通常风险较高,但潜在回报也更高,而债券被认为更安全,但回报较低。</p><p>在风险平价模型中,你不仅仅是平均分配你的资金到股票和债券上(各500美元),而是根据每种资产的风险来分配你的投资。</p><p>假设股票的风险更高,你决定将70%的投资分配给债券,30%分配给股票。这种分配是根据每种资产的风险贡献应该相等的理念来确定的。</p><p>通过这样做,你在投资组合中平衡了风险。如果股票表现不佳,对债券的较高配置可以帮助抵消损失,并为你的整体投资提供更稳定性。另一方面,如果股票表现出色,较小的配置也不会对整体投资组合的表现产生太大影响。</p><p>风险平价模型旨在通过考虑不同资产的风险来实现资产间的平衡。它帮助你进行投资多样化,并更有效地管理风险。</p><h2 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h2><ol><li>风险平衡:风险平价模型通过平衡不同资产的风险贡献,实现投资组合的风险均衡。这可以帮助投资者降低对任何单个资产的依赖,从而提高整体投资组合的稳定性。</li><li>简单易懂:风险平价模型相对较简单,容易理解和实施。它不需要大量的数据和计算,适用于个体投资者或资源有限的投资者。</li></ol><h2 id="局限性-1"><a href="#局限性-1" class="headerlink" title="局限性"></a>局限性</h2><ol><li>忽略预期回报:风险平价模型关注风险的平衡,但忽略了资产的预期回报。这可能导致在追求风险均衡的同时牺牲了潜在的高回报机会。</li><li>对某些资产不适用:风险平价模型在处理某些特殊资产类别(如复杂衍生品)时可能存在困难,因为这些资产的风险无法简单地衡量和比较。</li></ol><h1 id="投资组合理论与风险平价模型的主要区别"><a href="#投资组合理论与风险平价模型的主要区别" class="headerlink" title="投资组合理论与风险平价模型的主要区别"></a>投资组合理论与风险平价模型的主要区别</h1><h2 id="目标和重点:"><a href="#目标和重点:" class="headerlink" title="目标和重点:"></a>目标和重点:</h2><ul><li>投资组合理论的目标是在给定风险水平下,最大化投资组合的预期回报。它关注如何通过资产配置来实现最佳的风险-回报权衡。</li><li>风险平价模型的目标是平衡不同资产在整个投资组合中的风险贡献。它强调每个资产对总体风险的贡献应该是相等的。</li></ul><h2 id="风险分散方法"><a href="#风险分散方法" class="headerlink" title="风险分散方法"></a>风险分散方法</h2><ul><li>投资组合理论通过将不同风险和回报特征的资产组合在一起,以实现风险的分散化。它考虑资产之间的相关性,并通过优化资产权重来达到风险分散的目标。</li><li>风险平价模型通过平衡不同资产的风险贡献来实现投资组合的风险分散。它将风险分配给各个资产,以确保它们在整个投资组合中对总体风险的贡献相等。</li></ul><h2 id="考虑因素:"><a href="#考虑因素:" class="headerlink" title="考虑因素:"></a>考虑因素:</h2><ul><li>投资组合理论考虑了预期回报、风险和资产之间的相关性。它通过优化资产配置来平衡这些因素,以实现最佳的风险-回报组合。</li><li>风险平价模型更关注风险方面,特别是资产的风险贡献。它通过平衡不同资产的风险贡献来实现风险均衡,而对预期回报的考虑相对较少。</li></ul><h2 id="复杂性:"><a href="#复杂性:" class="headerlink" title="复杂性:"></a>复杂性:</h2><ul><li>投资组合理论在实践中通常需要更多的数据和计算,包括资产的历史表现、相关性矩阵等。它可能需要更多的复杂模型和技术分析来确定最佳的资产配置。</li><li>风险平价模型相对较简单,不需要大量数据和复杂计算。它可以作为一种直观且易于实施的方法,适用于个体投资者或资源有限的投资者。</li></ul><h2 id="关注点:"><a href="#关注点:" class="headerlink" title="关注点:"></a>关注点:</h2><ul><li>投资组合理论关注整个投资组合的特征和表现,它试图找到最优的资产配置,以实现预期回报和风险的最佳权衡。</li><li>风险平价模型更关注投资组合内部的风险分散,它强调平衡不同资产的风险贡献,以降低整体投资组合的风险。</li></ul>]]></content>
<summary type="html">
<p>投资组合理论和风险平价模型是两种与投资组合管理相关的重要概念,是金融领域中用于优化投资组合的方法。</p>
<h1 id="投资组合理论"><a href="#投资组合理论" class="headerlink" title="投资组合理论"></a>投资组合理论</h1>
</summary>
<category term="投资" scheme="https://jiapan.me/tags/%E6%8A%95%E8%B5%84/"/>
</entry>
<entry>
<title>4个最重要的企业财务指标</title>
<link href="https://jiapan.me/2023/The-4-most-important-business-financial-indicators/"/>
<id>https://jiapan.me/2023/The-4-most-important-business-financial-indicators/</id>
<published>2023-10-08T13:12:31.000Z</published>
<updated>2026-02-04T04:48:13.335Z</updated>
<content type="html"><![CDATA[<p>当谈到财务指标时,净资产收益率、毛利率、净利率和市盈率是经常被提及的几个指标,这些也是巴菲特最看重的4个指标。</p><p>巴菲特是历史上最伟大的价值投资者,他的交易逻辑的核心是:寻找优质企业并长期持有这些企业的股票。如何判断一个企业是否优质?这时就可以依据上边提到的4个指标来进行判断了。</p><h1 id="净资产收益率(Return-on-Equity,ROE)"><a href="#净资产收益率(Return-on-Equity,ROE)" class="headerlink" title="净资产收益率(Return on Equity,ROE)"></a>净资产收益率(Return on Equity,ROE)</h1><p>这个指标可以帮助投资者评估企业的<strong>盈利能力和管理效率</strong>。</p><p>净资产收益率是用来衡量企业利润与其净资产之间关系的指标。</p><p>它反映了企业利用所有者权益实现的盈利能力。</p><p>净资产收益率的计算公式为:净资产收益率 = 净利润 / 平均净资产。</p><ul><li><p>净利润</p><p>指的是企业在一定时期内扣除所有成本和费用后所剩下的利润。</p><ul><li>它是企业经营活动的最终利润。</li><li>净利润可以通过企业的损益表(或利润表)中的数据来计算得出。</li></ul></li><li><p>平均净资产</p><p>是指企业在一定期间内的</p><p>资产净值的平均值</p><p>。</p><ul><li><strong>资产净值</strong>指的是企业的资产减去负债,也可以理解为<strong>企业的所有者权益或净资产</strong>。</li><li><strong>平均净资产</strong>则是将期初净资产和期末净资产相加后除以2,表示在该期间内的平均资产净值。</li></ul></li></ul><h2 id="举例"><a href="#举例" class="headerlink" title="举例"></a>举例</h2><p>假设有一家公司,在某一年度的净利润为500万元,期初净资产为2000万元,期末净资产为2500万元。</p><p>首先,计算平均净资产: 平均净资产 = (期初净资产 + 期末净资产)/ 2 = (2000万元 + 2500万元)/ 2 = 2250万元</p><p>接下来,计算净资产收益率: 净资产收益率 = 净利润 / 平均净资产 = 500万元 / 2250万元 ≈ 0.2222 或 22.22%</p><p>在这个例子中,公司的净利润为500万元,期初净资产为2000万元,期末净资产为2500万元。</p><p>通过计算,得到平均净资产为2250万元。</p><p>最后,通过将净利润除以平均净资产,得到净资产收益率为22.22%。</p><p>这个例子中的数据说明了净资产收益率的计算方法。净资产收益率衡量了企业在一定期间内每单位净资产所创造的净利润水平。在这种情况下,净资产收益率为22.22%,表示该公司在该年度内每单位净资产创造了22.22%的净利润。</p><h2 id="为什么净资产收益率可以评估企业的盈利能力和管理效率?"><a href="#为什么净资产收益率可以评估企业的盈利能力和管理效率?" class="headerlink" title="为什么净资产收益率可以评估企业的盈利能力和管理效率?"></a>为什么净资产收益率可以评估企业的盈利能力和管理效率?</h2><ol><li>盈利能力评估:净资产收益率反映了企业在一定期间内每单位净资产所创造的净利润水平。较高的净资产收益率意味着企业能够有效地利用其资产创造盈利,表明企业在经营活动中取得了较高的利润回报。相反,较低的净资产收益率可能意味着企业的盈利能力较弱,资产利用效率不高。</li><li>管理效率评估:净资产收益率反映了企业管理层对资产的运营和配置能力。较高的净资产收益率表明企业管理层能够有效地管理和运营资产,实现更高的利润水平。这可能反映了企业在生产、销售、成本控制等方面的优秀管理能力。相反,较低的净资产收益率可能暗示企业的管理效率较低,资产配置和运营方面存在问题。</li></ol><hr><h1 id="毛利率(Gross-Profit-Margin)"><a href="#毛利率(Gross-Profit-Margin)" class="headerlink" title="毛利率(Gross Profit Margin)"></a>毛利率(Gross Profit Margin)</h1><p>这个指标可以帮助投资者了解企业的<strong>盈利能力和生产成本控制情况</strong>。</p><p>毛利率是用来衡量企业销售产品或提供服务后的毛利润与销售收入之间的关系的指标。</p><p>它反映了企业在销售过程中所保留的利润比例。</p><p>毛利率的计算公式为:毛利率 = 毛利润 / 销售收入。</p><ul><li><p>毛利润</p><p>是指企业在销售产品或提供服务后剩余的销售收入减去与销售直接相关的成本。</p><ul><li>它表示企业在核心业务活动中所保留的利润。</li><li>毛利润 = 销售收入 - 与销售直接相关的成本</li></ul></li><li><p>销售收入</p><p>是指企业在一定时期内通过销售产品或提供服务所获得的总收入。</p><ul><li>它代表了企业主要经营活动的收入来源,可以在企业的损益表中找到。</li></ul></li></ul><p>如果一个行业的毛利率低于20%,那么几乎可以断定这个行业存在着过度竞争。</p><h1 id="净利率(Net-Profit-Margin)"><a href="#净利率(Net-Profit-Margin)" class="headerlink" title="净利率(Net Profit Margin)"></a>净利率(Net Profit Margin)</h1><p>这个指标可以帮助投资者评估企业的<strong>盈利能力和经营效率</strong>。</p><p>净利率是用来衡量企业净利润与销售收入之间关系的指标。</p><p>它反映了企业在销售过程中实现的净利润比例。</p><p>净利率的计算公式为:净利率 = 净利润 / 销售收入。</p><h2 id="毛利润和净利润的区别举例?"><a href="#毛利润和净利润的区别举例?" class="headerlink" title="毛利润和净利润的区别举例?"></a>毛利润和净利润的区别举例?</h2><p>假设有一家制造公司,它生产并销售手机。在某一年度,公司的销售收入为1000万元。与销售直接相关的成本包括原材料、直接劳动和制造费用,总计为600万元。此外,公司还有其他间接费用和费用,如销售费用、管理费用和利息费用等,总计为200万元。</p><p>根据上述数据,我们可以计算毛利润和净利润:</p><p>毛利润 = 销售收入 - 与销售直接相关的成本 = 1000万元 - 600万元 = 400万元</p><p>净利润 = 毛利润 - 其他间接费用和费用 = 400万元 - 200万元 = 200万元</p><p>在这个例子中,公司的销售收入为1000万元,与销售直接相关的成本为600万元,其他间接费用和费用为200万元。</p><p>毛利润表示在销售过程中,公司通过销售所保留的利润。在这种情况下,公司的毛利润为400万元,即销售收入减去与销售直接相关的成本。</p><p>净利润则是在扣除所有成本和费用后所得到的最终利润。在这个例子中,公司的净利润为200万元,即毛利润减去其他间接费用和费用。</p><p>毛利润关注销售过程中所保留的利润,而净利润则考虑了所有与经营活动相关的费用和收入。</p><h2 id="毛利率与净利率的区别?"><a href="#毛利率与净利率的区别?" class="headerlink" title="毛利率与净利率的区别?"></a>毛利率与净利率的区别?</h2><p>毛利率和净利率是两个常用的财务指标,用于衡量企业的盈利能力。它们之间的区别在于考虑的成本因素不同。</p><ol><li><strong>毛利率</strong>是企业销售产品或提供服务后的毛利润与销售收入之间的比例关系。毛利润是指销售收入减去直接与销售相关的成本,例如生产成本、原材料成本和直接人工成本等。毛利率的计算公式为:毛利率 = (销售收入 - 销售成本)/ 销售收入。毛利率衡量了企业从核心业务活动中获得的利润比例,它反映了企业在销售过程中所保留的利润比例。</li><li><strong>净利率</strong>是企业净利润与销售收入之间的比例关系。净利润是指销售收入减去所有成本和费用,包括销售成本、管理费用、利息费用和税费等。净利润是企业最终实现的利润。净利率的计算公式为:净利率 = 净利润 / 销售收入。净利率衡量了企业在销售过程中实现的净利润比例,它反映了企业在营运活动中的盈利能力。</li></ol><p>总结起来,毛利率关注的是销售收入和与销售直接相关的成本之间的关系,它衡量了企业从核心业务中获得的利润比例。而净利率则考虑了所有成本和费用,包括销售成本以外的费用,衡量了企业在所有经营活动中实现的净利润比例。净利率相对于毛利率更全面地反映了企业的盈利能力和经营效率。</p><hr><h1 id="市盈率(Price-to-Earnings-Ratio,P-E-Ratio)"><a href="#市盈率(Price-to-Earnings-Ratio,P-E-Ratio)" class="headerlink" title="市盈率(Price-to-Earnings Ratio,P/E Ratio)"></a>市盈率(Price-to-Earnings Ratio,P/E Ratio)</h1><p>市盈率是衡量股票相对于每股盈利的价格的指标。</p><p>它是投资者评估一家公司的股票是否被低估或高估的重要指标。</p><p>市盈率的计算公式为:市盈率 = 股票市场价格 / 每股税后收益。</p><ul><li><p><strong>股票市场价格</strong>指的是股票在市场上的交易价格,也就是投资者购买或出售股票所需支付或获得的价格。</p></li><li><p>每股税后收益</p><p>是指企业每股普通股的税后净利润,也可以理解为每一股票所对应的盈利。</p><ul><li>计算公式为:企业的净利润 / 总发行的普通股数量</li></ul></li></ul><p>较高的市盈率可能意味着市场对该股票有较高的期望和溢价,而较低的市盈率可能意味着市场对该股票的期望较低。</p><h2 id="举例-1"><a href="#举例-1" class="headerlink" title="举例"></a>举例</h2><p>假设有一家公司,它在某一年度的每股收益为10元,而股票的市场价格为100元。</p><p>首先,计算市盈率: 市盈率 = 市场价格 / 每股收益 = 100元 / 10元 = 10倍</p><p>在这个例子中,每股收益为10元,市场价格为100元。</p><p>通过计算,得到市盈率为10倍。</p><hr><h1 id="优秀企业四个指标的参考值"><a href="#优秀企业四个指标的参考值" class="headerlink" title="优秀企业四个指标的参考值"></a>优秀企业四个指标的参考值</h1><ul><li>ROE > 20%</li><li>毛利率 > 40%</li><li>净利率 > 5%</li><li>市盈率 20-40 之间(按照A股标准计算得出)</li></ul><hr><h1 id="净资产收益率高和净利率高的公司是否一定是好的投资对象?"><a href="#净资产收益率高和净利率高的公司是否一定是好的投资对象?" class="headerlink" title="净资产收益率高和净利率高的公司是否一定是好的投资对象?"></a>净资产收益率高和净利率高的公司是否一定是好的投资对象?</h1><p>ROE高和净利率高通常被视为公司财务状况较好的指标,但并不意味着这些公司一定是好的投资对象。以下是一些需要考虑的因素:</p><ol><li><strong>行业和周期性</strong>:不同行业的盈利能力和周期性有所不同。即使一家公司的ROE和净利率很高,如果它所处的行业面临结构性问题或周期性低迷,那么这些指标的表现可能会受到影响。</li><li><strong>可持续性</strong>:高ROE和净利率可能是暂时的,而非持续的。投资者需要评估这些指标是否具有持续性,例如公司的竞争优势、市场地位和可持续的盈利模式。</li><li><strong>债务水平</strong>:高ROE和净利率的公司可能通过借入资金来实现这些指标,但高负债率可能增加公司的风险。因此,投资者需要关注公司的债务水平和偿债能力。</li><li><strong>估值</strong>:高ROE和净利率的公司可能被市场高度看好,导致其股票价格被高估。投资者需要综合考虑股票的估值水平,以确定是否存在投资机会。</li><li><strong>其他因素</strong>:除了财务指标,投资者还应考虑公司的管理团队、战略规划、产品竞争力、创新能力以及行业趋势等因素,这些因素对于评估公司的长期投资价值也是至关重要的。</li></ol><p>因此,高ROE和净利率只是投资决策的起点,而不是唯一的决策依据。投资者应该进行全面的研究和分析,以综合考虑多种因素,并结合自己的投资目标和风险承受能力做出决策。</p><hr><h1 id="ROE-和-ROI-的区别"><a href="#ROE-和-ROI-的区别" class="headerlink" title="ROE 和 ROI 的区别"></a>ROE 和 ROI 的区别</h1><ol><li>ROE是用来衡量企业利用所有者权益(净资产)创造利润的能力。它的计算公式是净利润除以平均净资产。ROE衡量了股东权益的回报率,反映了企业在投入资本的同时,通过运营活动创造的盈利能力。ROE通常用于评估企业的盈利能力和资产利用效率。</li><li>ROI(Return on Investment投资回报率)是用来衡量特定投资项目或资产的回报率。它的计算公式是净利润除以投资成本,并通常以百分比表示。ROI可以用于评估特定投资项目的经济效益,衡量投资的回报程度。ROI可以用于比较不同投资项目之间的收益率,帮助投资者做出投资决策。</li></ol><p>虽然ROE和ROI都涉及利润和投资,但ROE主要关注<strong>企业的盈利能力</strong>和资产利用效率,而ROI主要关注<strong>特定投资项目或资产的回报率</strong>,它们评估的对象和应用范围不同。</p><p>在评估企业绩效时,ROE和ROI通常会结合使用,以提供更全面的分析。ROE可以衡量企业整体的盈利能力和管理效率,而ROI可以帮助评估具体投资项目的回报情况。</p>]]></content>
<summary type="html">
<p>当谈到财务指标时,净资产收益率、毛利率、净利率和市盈率是经常被提及的几个指标,这些也是巴菲特最看重的4个指标。</p>
<p>巴菲特是历史上最伟大的价值投资者,他的交易逻辑的核心是:寻找优质企业并长期持有这些企业的股票。如何判断一个企业是否优质?这时就可以依据上边提到的4个
</summary>
<category term="投资" scheme="https://jiapan.me/tags/%E6%8A%95%E8%B5%84/"/>
</entry>
<entry>
<title>关于早会的思考</title>
<link href="https://jiapan.me/2023/thoughts-about-morning-meetings/"/>
<id>https://jiapan.me/2023/thoughts-about-morning-meetings/</id>
<published>2023-09-18T13:39:44.000Z</published>
<updated>2026-02-04T04:48:14.913Z</updated>
<content type="html"><![CDATA[<p>今天周一,照例我们下午开了全组的周会,我思考了很久决定取消每日晨会,下边是我准备的发言稿。</p><hr><p>本月最后一天是我入职 TT 的三周年,我依然向往我刚入职 TT 后近一年左右的时光,那个时候 TT 还有一点点外企文化,不具体展开讲了,用几个词形容就是:包容、信任、自驱、敢于试错。我那时也非常庆幸自己入职一家好公司,当时的 TT 被称为互联网最后一片净土,也确实对得起小而美的称号。</p><p>我一年半前主动要求过一次转岗,从直播转到推荐,刚来推荐组的时候,每次晨会听到大家工作那么饱和我都很焦虑,所以我也能体会大家现在的感受。</p><p>上周有一天kq因为白天开了一整天的会,但他手里的一个技术驱动项目进度还差一些,晚上下班后我问他走不走,他说得加班把技术驱动搞完,不然第二天早会没得说。我知道他是在开玩笑,不过那句「不然第二天早会没得说」这句话我确实也在心中说过好多次。</p><p>我不希望大家每天为了考虑早会上要说什么而有压力,甚至出现为了说点什么而被迫找点琐碎而无意义的事情做,也不希望大家靠堆砌很多工作量来证明自己的能力和重要性。我希望大家的工作可以更专注、聚焦、深入、认真、细致一些,不要东一榔头西一棒槌。我特别喜欢一句话:不要用战术上的勤奋,来掩盖战略的懒惰。</p><p>所以我打算尝试取消早会,取消也许是长期的,也许是暂时的,还要看取消后的效果和公司的要求。对于我来说开晨会是正确地做事,现在取消周会是做正确的事(大家可以想想这两句话的区别),结果是否正确现在不得而知。</p><p>不开晨会建立在大家自驱的基础上,也建立在我对大家充分了解和信任的基础上,我一直相信信任是促使人们进步的最大动力,因为信任能够让人们表现出自己最好的一面。</p><p>我们组内的方向比较多,每个人的工作内容不尽相同,每日同步给所有人的意义不是很大,靠每周周会来做一次相互了解和同步就够了。</p><p>我们现在早会最大的益处其实是收集大家日常工作中遇到的问题,我们取消了早会,大家的问题就不要再等到第二天早会上再提了,有了问题随时提,不要因为没了早会的要求就掩盖问题,如果后边发现出现了问题被掩盖的现象,我们还会恢复早会。</p><p>在团队划分上,为了便于管理和领域打通,jw 没有再把工程和核心拆成两条线,但大家也能看到kq在推荐工程上的经验比我多的多,而且在核心需求比较多的时候我也确实无法两头都顾及到。再加上由于取消早会后反馈周期的加长,项目的跟进上不可避免会相较之前难度更大,所以我在这里也给kq提个要求,后边我们两个做下分工,所有核心项目我这边都会去了解背景、方案、进度和风险,所有推荐项目kq也要做到这几点,包括内部、产品和对外支持的项目。</p><p>再回到大家的工作上,大家在有项目、有工作任务的时候就聚焦于手头的工作,力求完美。如果有几天真的没有那么忙时就适当放松,学习一些感兴趣的东西,工作应该有张有弛,一直紧绷和一直放松都不是正常的状态。大家学习的时候尽量学习和我们业务相关的东西,我们组包含了公司内两大块最重要的业务:推荐和 IM,所以要想学肯定是有的学的。我也非常鼓励大家去发现、解决、优化工作中遇到的业务和技术痛点,这会让大家获取更大收益,包括能力上的和绩效结果上的。如果公司内的业务无法满足自己,也可以学习其他自己感兴趣的东西,比如 Web3或者学一门新的编程语言等等。我推荐作为程序员的大家,有精力的话每年学一门新的语言。编程语言会限制我们的思维模式,如果你长期使用某种语言,你就会慢慢按照这种语言的思维模式进行思考。</p><p>除了工作还有大家的工作状态,每个月总有那么几天不想工作,实在不想工作的那一天就让自己松弛一些。我自己很容易焦虑,所以我很羡慕能拥有松驰感的人。根据我的经验,一个正常排期3-5天的项目如果在状态佳而且无打扰的情况下,大概率一天就能把代码写完,这种状态也叫心流,有本叫《心流》的书大家感兴趣也可以看看。</p><p>最后,希望大家未来有一天回忆起在 TT 的工作(或实习)经历觉得是有意义的,而不是给大家留下痛苦、无效忙碌的一段经历。</p>]]></content>
<summary type="html">
<p>今天周一,照例我们下午开了全组的周会,我思考了很久决定取消每日晨会,下边是我准备的发言稿。</p>
<hr>
<p>本月最后一天是我入职 TT 的三周年,我依然向往我刚入职 TT 后近一年左右的时光,那个时候 TT 还有一点点外企文化,不具体展开讲了,用几个词形容就是:包容
</summary>
</entry>
<entry>
<title>念念的房间</title>
<link href="https://jiapan.me/2023/nian-nian-room/"/>
<id>https://jiapan.me/2023/nian-nian-room/</id>
<published>2023-09-17T11:23:42.000Z</published>
<updated>2026-02-04T04:48:14.533Z</updated>
<content type="html"><![CDATA[<p>昨晚又是一整晚没睡,因为一家人来新家开荒,除了我爸,其他人都在这里过夜。由于新家有一个卧室还没有安床,所以我妈和念念就睡在我的床上了,我打的地铺。但因为不太适应,整晚都没睡着。</p><p>半夜睡不着时,我想起了白天一件有点内疚的事:</p><p>我们有个卧室是专门给念念准备的,墙壁刷成了淡粉色,还买了她喜欢的床。装好床的那天,她高兴极了,在自己的床上蹦了好久,一直想着如何装饰自己的房间。</p><p>这次回来,她看到自己的床上放了登登的衣服,地上也有一些其他的杂物。于是,她把那些不属于她的东西全都扔到了其他房间。我当时很严肃地批评了她,告诉她如果不让别人把东西放到她的房间,她以后也就别进其他房间了。她当时一脸惶恐,赶紧把她刚才扔出去的东西一件件搬回来,以讨好我。</p><p>深夜静悄悄的时候,我想到念念在这件事上并没有错。既然我已经告诉过她那是她的房间,那么她就有权利让自己的房间保持干净和整洁。再者,还有一个月念念就6岁了,我们之前蜗居在60多平的房子里,她一直没有属于自己的空间。第一次拥有自己的房间肯定是非常想占为己有的,我可以理解她,因为我小时候也有这样的想法。想想自己小时候,如果得到了自己非常喜爱的东西,肯定也不愿意让别人糟蹋。在拥有自己房间这一点上,我觉得非常亏欠她,在北京这个寸土寸金的地方只能委屈一下她了。</p><p>我们计划国庆节前带念念去趟上海迪士尼实现她的公主梦,我对自己的唯一要求是对她多一些耐心,不要因为她的一些小孩子的无理要求而对她发脾气。我就她这么一个女儿,不宠着她宠谁呢。去迪士尼的钱用的是我准备买摩托车的钱,之前因为考试失利,摩托车驾照考了两次,第二次考完后摩托车就对我没那么大吸引力了,所以也迟迟没有订车,这笔钱拿出来带念念去玩一趟把。</p><p>距上次去远的地方玩刚好过去3年,上一次是离职上家公司入职 TT 之前,到新疆玩了一个星期,一晃三年过去了,时间真快。说到这里,我奉劝各位还没结婚、没生娃的朋友及时行乐,趁着自由能出去玩就多出去玩。也奉劝那些不想结婚、不想生娃的朋友,如果一个人过得开心,请坚持你们的想法。</p>]]></content>
<summary type="html">
<p>昨晚又是一整晚没睡,因为一家人来新家开荒,除了我爸,其他人都在这里过夜。由于新家有一个卧室还没有安床,所以我妈和念念就睡在我的床上了,我打的地铺。但因为不太适应,整晚都没睡着。</p>
<p>半夜睡不着时,我想起了白天一件有点内疚的事:</p>
<p>我们有个卧室是专门给念
</summary>
<category term="个人反思" scheme="https://jiapan.me/tags/%E4%B8%AA%E4%BA%BA%E5%8F%8D%E6%80%9D/"/>
<category term="家庭生活" scheme="https://jiapan.me/tags/%E5%AE%B6%E5%BA%AD%E7%94%9F%E6%B4%BB/"/>
<category term="育儿" scheme="https://jiapan.me/tags/%E8%82%B2%E5%84%BF/"/>
<category term="父女" scheme="https://jiapan.me/tags/%E7%88%B6%E5%A5%B3/"/>
</entry>
<entry>
<title>乔迁第一顿饭</title>
<link href="https://jiapan.me/2023/first-meal-after-moving-into-a-new-house/"/>
<id>https://jiapan.me/2023/first-meal-after-moving-into-a-new-house/</id>
<published>2023-09-16T12:27:35.000Z</published>
<updated>2026-02-04T04:48:13.565Z</updated>
<content type="html"><![CDATA[<p>今天一家人在新家吃了一顿团员饭,作为我们拥有新家后的第一次正式庆祝。不过还并没有完全搬过来,小登还太小、小念还需要在现在的幼儿园上完大班,所以在之后的很长一段时间内还是只有我一个人在这边住😂</p><p><img src="0.jpeg" width="600px" style="margin: 0 auto;"></p><p>上午和路秘书一起送小念去上陶艺课,她和我一起来的原因是想给那个安排我们进来的老师送两盒月饼,她知道这个活我这种笨嘴笨舌的人肯定完不成,而且我一点都不擅长这些。一开始我也觉得她完不成,认为老师不会轻易收家长东西的。没想到在路秘书的再四推让下,那个老师最后还是接了我们的东西,还主动和我们说下学期可以再给我们推荐一些其他课程。我非常非常佩服路秘书这种有社交牛逼症的人。</p><p>距下课还有一个半小时,我和路秘书压了40分钟马路,走到了一个距离上课地点最近的一个瑞幸,中间经过铁路高架桥看到一列高铁经过,路秘书跟我讲了一个当年追她的男生后来进了铁路局工作的一段故事。我们到瑞幸后我点了一杯之前没喝过的咖啡,在那里歇了20分钟,之后一人骑了一个共享单车回到了上课地点。因为平时上下班路程上的需要,我开了哈罗和滴滴两个共享单车平台的月卡,所以今天我用每个平台扫了一个,骑车就没有花钱。</p><p>中午回家后路秘书亲自操刀给我剪了个头发,以后又可以在剪头发的开销上省下一笔钱了。过程中我爸作为有8年理发经验的人进行了友情指导。之后去稻香村买了些熟食,我还给自己买了三块在疫情居家办公期间发现的一个好吃的糕点——山楂锅盔,强烈爱吃山楂口味的小伙伴尝一尝。买完熟食回家收拾了一些东西就来新家了,吃饭过程中还喝了两盅酒,现在还晕乎乎的。</p><p>小念今天带回了她的第一件陶艺作品,一只啄木马笔筒,里边插了扭扭棒做的花:</p><p><img src="1.jpeg" width="600px" style="margin: 0 auto;"></p>]]></content>
<summary type="html">
<p>今天一家人在新家吃了一顿团员饭,作为我们拥有新家后的第一次正式庆祝。不过还并没有完全搬过来,小登还太小、小念还需要在现在的幼儿园上完大班,所以在之后的很长一段时间内还是只有我一个人在这边住😂</p>
<p><img src="0.jpeg" width="600px" s
</summary>
<category term="生活" scheme="https://jiapan.me/categories/%E7%94%9F%E6%B4%BB/"/>
<category term="日常记录" scheme="https://jiapan.me/tags/%E6%97%A5%E5%B8%B8%E8%AE%B0%E5%BD%95/"/>
<category term="家庭生活" scheme="https://jiapan.me/tags/%E5%AE%B6%E5%BA%AD%E7%94%9F%E6%B4%BB/"/>
<category term="乔迁之喜" scheme="https://jiapan.me/tags/%E4%B9%94%E8%BF%81%E4%B9%8B%E5%96%9C/"/>
<category term="亲子" scheme="https://jiapan.me/tags/%E4%BA%B2%E5%AD%90/"/>
</entry>
<entry>
<title>Go Struct 不指定 JSON tag 时的默认规则</title>
<link href="https://jiapan.me/2023/go-struct-with-json-tag/"/>
<id>https://jiapan.me/2023/go-struct-with-json-tag/</id>
<published>2023-09-15T07:15:57.000Z</published>
<updated>2026-02-04T04:48:13.674Z</updated>
<content type="html"><![CDATA[<p>Golang 在序列化和反序列化一个 Struct 时,如果指定了 JSON tag 会严格按照指定的 tag 内容来执行,在没有指定 tag 或 tag 大小写不精准时,会有一些默认规则。</p><h1 id="序列化"><a href="#序列化" class="headerlink" title="序列化"></a>序列化</h1><p>序列化的情况比较简单:</p><ul><li>指定了 tag 的可导出字段,按照 tag 的命名进行序列化</li><li>没有指定 tag 的但可以导出的字段(首字母大写)会完全按照变量命名来进行序列化</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> A <span class="keyword">struct</span> {</span><br><span class="line">Case <span class="keyword">int</span></span><br><span class="line">casE <span class="keyword">int</span></span><br><span class="line">Cas_E <span class="keyword">int</span></span><br><span class="line">CaSE <span class="keyword">int</span> <span class="string">`json:"ok"`</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line">a := A{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>}</span><br><span class="line">s, _ := json.Marshal(&a)</span><br><span class="line">mt.Println(<span class="keyword">string</span>(s))</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上边这段代码输出:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">{<span class="string">"Case"</span>:<span class="number">1</span>,<span class="string">"Cas_E"</span>:<span class="number">3</span>,<span class="string">"ok"</span>:<span class="number">4</span>}</span><br></pre></td></tr></table></figure><ul><li><code>casE</code> 这个字段没有输出,原因是因为他是个不可导出的私有字段,即使设置了 tag 也不可序列化。</li><li><code>CaSE</code> 序列话后的 key 为 <code>ok</code> 是因为我们给它指定了 tag</li><li>其余字段都是按照我们原本的拼写格式进行的输出</li></ul><h1 id="反序列化"><a href="#反序列化" class="headerlink" title="反序列化"></a>反序列化</h1><p>序列化的情况稍微有点复杂,其整体的优先级为:</p><ul><li>先按 tag 匹配,后按字段名匹配</li><li>有 tag 的仅匹配 tag,没有tag 的可参与字段名匹配</li><li>先精确匹配,后模糊匹配</li><li>多个模糊匹配的按照声明在前的匹配</li></ul><p>我们看几个例子:</p><p>情况1,带 tag 的两个字段都无法匹配上(精准匹配+模糊匹配),不带 tag 的两个字段都可以模糊匹配上,优先赋值给前边声明的字段:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> B <span class="keyword">struct</span> {</span><br><span class="line">Case <span class="keyword">int</span> <span class="string">`json:"a"`</span></span><br><span class="line">CaSE <span class="keyword">int</span> <span class="string">`json:"b"`</span></span><br><span class="line">CasE <span class="keyword">int</span></span><br><span class="line">CaSe <span class="keyword">int</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line">s := []<span class="keyword">byte</span>(<span class="string">`{"CAsE":2}`</span>)</span><br><span class="line"><span class="keyword">var</span> b B</span><br><span class="line">json.Unmarshal(s, &b)</span><br><span class="line">fmt.Printf(<span class="string">"%#v\\n"</span>, b)</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 输出:main.B{Case:0, CaSE:0, CasE:2, CaSe:0}</span></span><br></pre></td></tr></table></figure><p>情况2,带 tag 的其中一个字段可以模糊匹配上:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> B <span class="keyword">struct</span> {</span><br><span class="line">Case <span class="keyword">int</span> <span class="string">`json:"case"`</span></span><br><span class="line">CaSE <span class="keyword">int</span> <span class="string">`json:"b"`</span></span><br><span class="line">CasE <span class="keyword">int</span></span><br><span class="line">CaSe <span class="keyword">int</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line">s := []<span class="keyword">byte</span>(<span class="string">`{"CAsE":2}`</span>)</span><br><span class="line"><span class="keyword">var</span> b B</span><br><span class="line">json.Unmarshal(s, &b)</span><br><span class="line">fmt.Printf(<span class="string">"%#v\\n"</span>, b)</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 输出:main.B{Case:2, CaSE:0, CasE:0, CaSe:0}</span></span><br></pre></td></tr></table></figure><p>情况3,带 tag 的两个字段都可以匹配上,第一个模糊匹配,第二个精准匹配:</p>]]></content>
<summary type="html">
<p>Golang 在序列化和反序列化一个 Struct 时,如果指定了 JSON tag 会严格按照指定的 tag 内容来执行,在没有指定 tag 或 tag 大小写不精准时,会有一些默认规则。</p>
<h1 id="序列化"><a href="#序列化" class="h
</summary>
<category term="技术" scheme="https://jiapan.me/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Go" scheme="https://jiapan.me/tags/Go/"/>
<category term="JSON" scheme="https://jiapan.me/tags/JSON/"/>
<category term="struct" scheme="https://jiapan.me/tags/struct/"/>
<category term="序列化" scheme="https://jiapan.me/tags/%E5%BA%8F%E5%88%97%E5%8C%96/"/>
</entry>
</feed>