0%

玩僵尸毁灭工程,我学到些什么

Overall

  1. 效率 = 做的事情的总量 对 时间 的微分,即单位时间内做的事情。效率很重要。
  2. 时间往往意味着成本,更快的过冬、更快得饿、负面状态的下一个周期更快地到来。
  3. 效率也可以看成是正面成果的完成率,当正面成果的完成率大于负面状态的到达率,我就能活下来。简单说就是吃一口饭干一手活,这手活的回报比这口饭大,那么我就有了生存的剩余资源。
    游戏里达到短期内生存的剩余是很容易的,但长期来看,物资永远是短缺的,而且过冬、出远门探索等都是长期的大风险,因此,效率更显得重要。
  4. 效率越高越好,没有上限。

Knowledge

  1. 知识很重要
  2. 但知识没有真正动手重要
  3. 知识只能加快完成事情的效率,但只有知识,没有任何事情能够完成
  4. 带着知识来做事情,事半功倍
  5. 由于效率越高越好,因此必须带着知识来做事情。实在找不到书来学习的话,先把眼前的事情完成。

Risk

  1. 负面状态有风险,活动也有风险。
  2. 无论哪种风险,单独来看,都是有惊无险,很难造成实质性的危险。
  3. 真正的危险存在于两种风险交叉叠加的时候。
  4. 当下活动附带的风险,不必多虑,加以注意即可
  5. 不要带着一项活动的风险,去进行另一项活动。也就是说,先消除后顾之忧,再去做新的事情。
    例如,真正的危险在于:负重时恰好被尸群追逐。先把家务时产生的负面状态消除,清空物品,再去出门探索。
  6. 保证在进行每项活动时,成本都是最低的,且没有额外成本。这样能活下来的概率就大得多。

Emotion, Thinking and Sport

  1. 重复工作、专注某项技能的提升,会导致无聊、抑郁等负面情绪
  2. 应重视负面情绪
  3. 重视和解决负面情绪的方法,是劳逸结合。应注重动静搭配、手脑协调
  4. 脑力活动时,全神贯注,精益求精
  5. 体力活动时,要注重跟身体对话,感受控制性、协调性、畅快感
  6. 冥想可以沟通手脑,体会身体的控制感

to Put it even more Simply

  1. 知道自己面临的短期需求、长期需求
  2. 建立路线:先后需要完成的事情
  3. 每做一件事情,集中所有精力去完成,不要三心二意。即便可以三心二意,也不要这样做,因为每件都单独完成的效率要高多了
  4. 做事情精益求精,不留尾巴
  5. 明白做任何事情,都有成本和代价。正视这些代价。修复这些成本和代价是这件事情本身的一部分 ———— 不留尾巴
  6. 集中完成一件事情,达到目标即可,努力应该留有余地。越过目标的过度努力,会带来指数级增长的代价。剩余的内容留作新的目标来完成就可以了。
  7. 劳逸结合。路线应该是手脑协调的穿插着安排的。
  8. 干的时候,努力学习。当自我感觉很努力时,再努力一点。
    Live as if you were to die tomorrow. Learn as if you were to live forever.
  9. What the game doesn’t cover: Just Be Resourceful. Do anything you can to survive.

书和多媒体

我很喜欢看 Eli Bendersky 的博客,原因是多种多样的,一是技术上这位博主跟我有非常相似的兴趣点,二是他的文笔熟练、流畅,三是他的执行力很强,最后,这几点都是我希望自己能做到的,我自己正走在半路上,而他在每个方面都领先我一些,仿佛是一名老师在带领着我。因此我尤其喜欢他的博客。

但今天不是来介绍他的博客本身的。从 2003 年至今,这博客已经登了接近 1300 篇文章,每篇文章都会打上若干的 tag 标签,其中含文章最多的是多达 360 篇的“阅读”标签(”Book Review”, to be precise)。

上图是博主 Eli 在 2003 年到 2019 年间,每年所读的书的数量的统计,在 2019 年读了 63 本书,刷新了他的个人记录。即使是最少的 2011 年,他也看了 19 本书。在这些书里,并不都是薄薄的小文章,有长篇小说名著,也有经济类的政治类的,还有许多技术类的(他会对部分书写读后感)。

看到这个表,我不禁自省了两个问题:

  1. 我一年看了几本书?
  2. 他一年看了63本书,我用这同样的时间做了什么?

我是喜欢看书的,也能坐得下来看书,但一年下来也只能看 5 本书左右。我虽还不善于管理时间,但至少也善于规划时间了,我把一天24小时按最小半小时为单位画表,除开睡眠、通勤、上班的时间,一天下来能自由支配的时间里,规划用于健身、学习、娱乐等,大约在一个季度左右的周期会迭代一次,在不同时期,具体规划也会不同,归根结底是按照心情和习惯来更新。

总体上我自认为对自己要求不能算严格,但还是有追求的。一开始的时间规划里,是没有娱乐项的,当时实际的生活里娱乐的占比确实很低,后来发现,因由健身积累的皮质醇以及其他因素的影响,心情和乐观向上的动力会随着规划的持续执行而逐渐变差,然后逐渐反思发现,娱乐对人来说还是必须的,就像拉伸之于健身一样,所以在规划里为娱乐留下了位置。

稍微计算下就能发现,如果每天的自由支配时间都能用于不受打断的读书,那么一年下来读个十来二十本书,完全不是问题。一天大约能有三四个小时,有些书读得慢点每小时能读十到二十页左右,一个月即使只花二十天来阅读(同工作日)也能最少读600页了,如果是小说一类的,还能多接近一倍。但尽管我喜欢看书,认真规划时间,还是实际上做不到。时间都用来看视频、打游戏,花在多媒体资源上了。

其实看视频、打游戏,并不是什么无价值的事情。我喜欢看的视频分很多类型。旅行类的视频,例如旅拍、探店,身临其境仿佛我自己去旅行了一样,让我积累了对当地的知识,也是为以后自己到实地去提供了经验和材料。知识类的视频,例如网课,例如针对第三方材料的讲解,则很大程度上也可以看作是一种新类型的“书”了。当然也有很大一部分纯“奶头乐”性质的视频,不过这部分倒是落在”娱乐性“的时间规划内,与之前落在非娱乐性规划的种类不同。至于打游戏,我还因为在游戏战队里担任培训教官,学会了用 Manim 制作教案,学会了录屏剪视频、开通直播,更重要是锻炼了与人沟通交流、讲解的技巧等,比起毫无产出的纯娱乐,学习成果反而相当丰富。

虽然一年看那么多书让我很羡慕,但除开自己的这股虚荣心,仔细想想,反而让我觉得自己现在的生活确实更适合自己。因为终究,这几个问题是很难回答清楚的:读书是不是一定有益?不读书是不是一定无益?看视频打游戏是不是一定比读书的益处少?究竟吸收什么对我们来说才叫有益?Eli 本人在 2007 年也提到,他发现自己也会因为读的书数量多沾沾自喜,而有时忽略书本身好不好的问题。当然我相信,这是他读了太多的书的甜蜜的苦恼,从客观上来说,并不能说明他为了看书而看书,就像我们跑步,有时跑着跑着发现忽然想不起来三分钟前看到什么了,但这不能说明我们跑得不认真,可能反而恰恰是因为很喜欢、很投入,所以对事情本质的反面更为敏感。

但不管怎么说,做一件事情做了多少,本身不应该成为一种衡量的标准。人生就是人生,人有兴趣去追求、有欲望得满足、有家人朋友要陪伴、也有各自不同的价值观,能实现自己的当然就是好的方式,这也不是毒鸡汤,而是一种与人比较时的释然的视角。对我自己来说,我更愿意从这几个方面来评价当前的实践对自己来说是不是有益:我是不是具有了更多的可能性,来完成可能的未来的新内容?我是不是建造了新的东西?我是不是补全了自己缺失的东西,对自己更加满意了?

人是无知的,但他又全知。“What I cannot create, I do not understand”,所以,唯有创造才能实现个人价值。感谢 Eli ,他的读书的记录没有真正教会我什么,但他的博客却教会了我许多,因为这是他的创造。

Use an IDE ?

写代码究竟是用文本编辑器好还是用 IDE 好?工作多年,已经很少听到有人争论了,但是在学生时代这话题还是很热门的,当时常看到有人讨论得乐此不疲,这当中也离不开自己跃跃欲试地想参与其中的兴致。

为什么有人推荐用 IDE?代码补全、符号跳转,这两个功能对初学者来说都是杀手锏,即使记不住接口长啥样、实现在哪里,写起代码来也能按自己思路流畅地展开。如果再提起 IDE 的 debug 断点、内存分析等等,那更不用犹豫了,想不用 IDE 都想不出理由。

那怎么有人推荐用文本编辑器?简单,大神都用这个,那能差吗?IDE 的功能,文本编辑器配上插件都能实现,插件实现不了的也肯定有 linux 工具——本来也是现有 linux 工具,再有 IDE 这些内置功能的嘛。再者,文本编辑器操作起来多炫酷啊,运行又快。以更炫酷的方式,用更快的工具,还能显得自己特有见识和能力,在这过程中也确实能接触、学习到很多新东西,这难道还需要别的理由吗。

乍看上去,像是公说公有理、婆说婆有理。

为啥现在我又提起这个讨论了呢?是因为最近在闫令琪大神的 Games101 课上,忽然又看到了这个内容。课程的家庭作业中包含了一些既定的代码框架,学生们只需要在框架内写二十行不到的代码就能完成一道题目。闫老师特意跟大家说,“请使用 IDE,千万别不使用”,而且不要用sublime text、notepad++、vi/vim、emacs这种文本编辑器。他说,“在学校里、学习期间,用文本编辑器开发程序以锻炼自己的技能,这是 OK 的,但是开发大型的工程,完全没有必要……使用 IDE 能够大幅提高我们的开发效率,为什么不去用呢?……而且 vi/vim、emacs 它们自己本来就要‘打架’了,我们为啥还得去纠结用它们呢?”,还有句话很有趣,“(大家)要了解,geek,是 genius + freak,我们要多学习其中 genius 的部分,不要去学 freak 的部分……“,有时坚持用文本编辑器可能是一种怪癖,去学习这个毫无必要。

我完全赞同闫老师的说法,没有任何反论辩驳的意图。我是担心可能闫老师的这个观点没有得到充分的说明,有些读者可能还没完全体会到,所以才写下本文,再详细说说。

IDE 好用吗?好用。能提升编程效率吗?能。但不用 IDE 能学到更多技术吗?能。这几个问题似乎又回到了大家争论的原点,说该用的有该用的理,说不该用的也有不用的缘由,但其实各自说的,都不在一个内容上。真正的区别是,在不同的编程水平,不同的开发目标,不同的 code base 上,有截然不同的回答。

在学习的时候。我们那时的编程水平还待提高,开发的目标本身就是学习,也不是一上来就在成熟的生产性工程化代码上工作,因此,鼓励使用文本编辑器 + 插件 + 各类工具进行开发,code base 小而简单,复杂度可控,又能在此基础上好好推敲每个环节涉及到的技术细节。这样确实能学到很多干货,所以推荐这种方式。也就是说,“以锻炼为目的鼓励使用文本编辑器,确实是OK的”。

在闫老师的课程上,首先就要求学生得具备编程的能力,换言之大家并不是新手,其次,编程只是一种手段,目标是学会图形学课程的内容,最后,作业本身具有了它自身特定的开发框架,学生撰写的代码,是在这个框架内借用它的功能实现的。在这种条件下,如果再去坚持一定要使用文本编辑器来开发,那恐怕是有点怪癖的嫌疑了。代码框架是作业独有的,完全去学习它、掌握它,是没有必要的成本,对学生来说,重要的是掌握其中的概念以及各部分之间的联系,而不是去把接口函数的拼写背诵下来,即使是使用插件来配置,恐怕也总得费那么点功夫,而在这上面哪怕花一分功夫,都是“完全没有必要”的。直接用 IDE 开箱即用地打开工程,直接写作业内容,验证自己对图形学课程的理解,就达到目的了。其余的东西,可以在其他方向、其他任务上再继续展开,但没有必要在作业的工程上死磕。

再展开说,在其他工程上,应该用文本编辑器还是用 IDE ?对于成熟的软件工程师来说,这只是个因目的而不同的选择题罢了。紧急的,抑或是快速迭代的任务,总之是要快速看到成果的,那么直接用 IDE 就好了,即使是不急着看到成果的,但如果我对过程并不关心,那么也直接用 IDE 就行。但同样的 code base,如果我需要仔细推敲,琢磨其中的部件、原理和设计模式,需要对比其他框架,以及最终可能为了我的需求作重构,那么此时,虽说 IDE 也能够满足使用,但归根结底还是看个人的使用习惯。如果很熟悉 IDE(而且 IDE 也可以添加很多功能强大的插件),那用 IDE 也无妨,如果虽然不抗拒 IDE 但还是文本编辑器来得更趁手,也可以用后者。

总之,总是纠结该用哪个,说明还停留在初级的编程学徒的阶段,而意识到两者其实都是工具,“因地制宜”地灵活使用,这是到了成熟的软件工程师的阶段。而用着 IDE 却能看到每个功能背后对应是在做什么,用着文本编辑器又能知道 IDE 中是怎样封装提供这样功能,这样就是高手的阶段了,高手哪天开发了个 IDE 插件,或者发布了文本编辑器的插件,也不足为奇。

用武功来打比方,入门弟子可能会争论练剑该用真剑还是木剑。而三师兄告诉他们,出门在外自然用真剑,练习时用木剑也无妨。大师兄在旁说,我用木剑也能杀人,用真剑也能跟你们的木剑过招。最后师傅说,不管什么剑,跟我手里这根树枝比起来,也不过如是。大概是这个道理吧。

So the lesson learnt at the end:

  1. Always remember your goal, and commit to it. (用 IDE,快准狠)
  2. Love what you do, and love is insane. (Insane enough to 用文本编辑器)
  3. Getting things done may even make you hate them. (该用 IDE 还得用)
  4. Love never let go. (真的热爱编程,还是放不开文本编辑器,愿意去折腾)
  5. Love rationally, and passionately. (成熟的热情不是盲目,而是理性 + 稳定地热烈)

附录

GAMES101: 现代计算机图形学入门,by 闫令琪老师

课程主页

GAMES101官方Bilibili视频

“正确”的时间性

我最近在人间地狱这款游戏里,担任起了战术培训营的教官,向别的玩家传达我对这个游戏的团队战术的思考和理解。这是一款二战模拟游戏,100名玩家在广阔的战场上对垒并分出胜负,以硬核和战术见长,因此,玩家之间的配合尤为重要。我作为战队的战术培训营的教官,常要收集战例、思考战术,然后梳理为成体系的战术打法,再通过教案和在线会议的形式,把战术教给新加入战队的队员。

难得有机会将自己认真思考的结果,向信任自己的许多陌生人传达,担任教官的这段经历让我学习到很多,其中最重要的,是我意识到”正确“是具有时间性的。这里的时间性,不是指一个正确的结论会随着时间的推移而发生内在的变化,一个结论能被称为正确,当然应是不变的真理。所谓的“时间性”,是体现在我们需要时间才能了解到、体会到这结论在具体的事情当中为什么正确。这个时间,对学员是如此,对教员也同样如此。


在游戏中想找到所谓“正确”的结论,是很难的,尤其是在众说纷纭,大家都没有定论的时候。因为在游戏中,人是自由的,我控制一名游戏中的步枪兵,想往左走就往左走,想往右开枪就点鼠标开火。究竟要怎么打才能叫做是正确?思考战术就更难了,游戏里一个步兵小队共有6人,每个人都有自己的想法、忽高忽低情绪和不同的判断,玩家要怎么配合才能叫做是正确?更何况,即使真的有“正确的战术”,如果玩家的水平不够,被另一个不成战术但是个人能力很强的队伍打败了,这难道能证明战术是错误的吗?

这样的道理在生活中比比皆是,大家应该都有充分的经历。例如开车转弯的时候,我们可以转大弯,也可以转小弯,也有人看似要转大弯,但是车头突出去后却原地磨轮胎转起了小弯,而赛车手则是外内外的走线,非切着弯心不可,但又有某些赛道的弯道,反而是不切弯心的。什么样的转弯方式是对的?结合路线、安全意识、乘坐舒适度、车身动态,甚至结合物理学的原理,在具体场合下一定存在一个“正确的“转弯方式,但想让一名新手自己琢磨出来,是很难的,即使把所有客观的正确的知识都告诉他,让他直接驾驶出来,也还是非常难,非得让他自己开车开得足够久,开成了“老司机”,才有可能掌握。

想解释清楚这样的“正确”,本来已经够困难了,但更困难的是遇到了“杠精”,这点相信大家更会有自己的体会了。有时候“杠精”说的话,绝不能说错误,在客观上很有可能也是正确的,但是正因为所谓的“杠精”还没有花时间去真正了解所讨论的事情,还未了解到其中包含的所有方面,以及这些方面之间的一切联系,因此他们往往也只有空洞的“正确”,却没有真正符合实际情况的,实际的正确。


话题的概念有点大,想让读者朋友产生点共鸣,实属不易。接下来我还是尝试用两个例子,来详细说明说明。


第一个例子还是先以游戏来说吧。每个玩家都是一名步兵,每个步兵小队人数为6人。考虑如下两个问题:是否存在一个最优的战术配合打法?如果存在的话,是怎么样的?

如果按照科学研究的角度,那我们需要对步兵、战场以及步兵战斗过程建立相应的数学模型,每个步兵会被抽象成视线、枪线(枪所指方向、射击范围)、命中率。战场会被看作是几何区域的集合,有些区域可供移动,有些则是阻挡视线枪线的掩体等等。最后战斗过程,则需要建立从发现敌人、沟通交流、锁定敌人、开火击杀的逻辑流程,还需要考虑交叉枪线的对战优势,还有敌我双方在近距离时可以听到对方的脚步声等等更复杂的战场信息。然后在这个模型之上,按照双方各自的博弈目标,回归整体的博弈平衡,还得考虑一些博弈的奇点,从而得出整个战术的可能执行策略。

如果是从玩游戏的角度,我们却大可不必这么麻烦。最直观的做法,找到玩得特别好的玩家,向他学习,观察他在什么状况下、在不同的时刻,做了什么动作,再去揣摩他做这个动作之前,都做了哪些主观判断。看他一次的游戏过程是不够的,在每个战场信息发生变化的时候、在他执行每个动作时,都有很多可能。但如果看得足够多,我们就能逐渐揣摩出来,前面问题的每个答案是什么,从而掌握了他玩这个游戏的经验和理解。但不论如何,最后总会有个问题:他的玩法,是最优的玩法吗?这个问题,单从他一个人身上是不能得到回答的,于是我们还得再去揣摩许多其他的高手的打法。

用一点术语来概括,前一种研究方法,是自底而上的,先把基础的元素识别出来,研究清楚,再研究这些元素结合起来会发生什么,最后概括到完整的整个我们关心的事情上,得出令人信服的无可辩驳的结论;后一种研究方法,则是自顶而下的,我们从令人疑惑的整个事情出发,通过控制变量法,或者其他“迭代-反馈”的方法,从事情的变化中认识它,虽说其中很多细节,可能并不一定能认识清楚,但换过来说,我们也往往并不关心那点细节本身是什么特点——我们只需要认识事情本身就足够了,而还有什么方法比直接看这个事情更有效呢?

两种方法都很有道理,而如果读者有过科学研究或者工程设计的经验,或者虽然没有但在某一件事情上有很瞩目的成就,又或者单纯是阅历十分丰富、见过风风雨雨的大世面,又哪怕仅仅是非常善于思考,那么咱们都应该明白:只有两种方法的结合,才是最有效的认识事情的手段。以游戏来说,研究战术的过程中,一边去参数化地分析单个步兵,去设计逻辑流程分析敌我交火的过程,一边去大量的复盘,学习优秀玩家的游戏经验,两个拳头都要硬,这样下来,我们的结论,既能在细节处有充分性,又能在大局上有必要性,理论和实践相互验证,最重要的是,在研究的过程中遇到任何令人疑惑的新问题,我们都能在两条线索上寻找新的充分和必要,交叉对比验证,从而得到不管在理论上还是在实践上都令人信服的结论来。


第二个例子是烧菜。每个人都有从不懂烧菜到能烧菜的阶段,至于能烧好菜,那虽绝不是稀罕事,却仍然不是每个人都做得到的。假设自己现在还不懂烧菜,现在我想做一道回锅肉,应该如何做?按自底而上的方法,我们恐怕得了解猪肉部位都有什么特点,一般的去腥方法都有哪些,焯水后要过冷水的物理原理,切肉都要怎么切才能又均匀又薄,形成灯盏窝的物理因素有哪些,不同火候对肉会有什么影响,究竟怎么判断辅料已经断生,怎么量化调味料等等,视“完美主义”的程度(或者说“杠精”的程度),要了解的项目还可以增加许多。按自顶而下的方法,我们只需要找到会做这道菜的人,跟着他多做几遍就好了。

单纯按自底而上的方法,可能半年过去了我们还没做出一道能吃的,而只按自顶而下的方法,可能换个炉灶、换个调料勺、换个锅,甚至哪天买到一块不规整的二刀肉,都做不出最初那道似乎已经学会的灯盏窝回锅肉。不必说,两种方法的结合,一定是最好的,既能直接开始学怎么做这道菜,以使我们不至于偏离最开始的目标,同时又能在各种细节处,学习厨艺的基本功,让我们能更得心应手的去对付可能遇到的任何变化。


介绍到这里,相信大家开始有点明白了。什么是“正确”?一个能被证伪的观点,被证明无误之时,就是正确了,这是科学哲学教给我们的道理。但是本文却不是在讲这种“正确”。当我们学会了许多科学道理,掌握了许多能被证伪的观点之后,就能够正确地认识世界、认识我们周围的生活了吗?相信在我们每个人切身回顾看来,未必如此。

比起“能否证伪”,在生活中,我们可能更为关心“是否符合”。一方面,只有“是否符合”,才贴近生活,才吻合我们关心事情的本来动机,另一方面,生活中恐怕大多数事情,我们没法对它进行科学的建模,没有能力把问题限制到“能否证伪”的范围当中来。

不少结论,我们似乎可以通过“证伪”或“证确”来得到其正确与否的结论。但是,在认识一件事情的过程当中,结论能够被证伪,是否真的意味着它不正确?在实践中,否定的情况比比皆是。就拿回锅肉来说,火大了、炒久了,肉是不是就干了?但从这句描述来说似乎是的,但如果换个炉灶、换个锅,肉切得厚了,实际上却谁也得不出什么结论,非得真的去炒出来才能知道。这当中,出问题的环节不是“证伪”这个手段本身,而是所证的“伪”,往往并不符合事情这个上下文,从而产生了概念的错换。火大炒久,肉确实容易干,但那也得是将其他的所有条件都固定下来之后,才能下的结论。这最后导致证伪是对其他事情的证伪,而本来事情当中的结论,没有得到有效的说明。这种现象可不是逻辑上的纸上谈兵,而是无时无刻不在发生的我们每天的经历。

我们刻苦学习、努力钻研、实践总结,获得了许多科学的、可以信赖的、可证伪的正确认知,但在生活中,却还是遇到这么多让人无法把握的真实,未免不让人感觉沮丧。这其中出了什么问题?”正确“的时间性,一定是这当中最重要的回答。这里的时间性,是指我们把这些科学的正确认知,与事情的实际逐渐结合的过程,在这时间里,我们用正确的认知不断推敲事情的细节,同时又投身到事情当中,去体会、去感受,又在大局和细节的结合处发现新的迷惑,然后重复这个过程。在这不断的重复之中,我们可能没有获得任何新的科学的可证伪的正确认知,可能还是没有求得事情的”科学解“,但是,我们获得了大量的对事情的经验,了解到了事情都有哪些细节,每个细节都有哪些可能的变化,以及何种变化会给事情带来怎么样的影响,在事情的整个运转过程中,哪些细节是符合哪个科学的正确认知的。这些东西,我称之为”事情的正确“,因为它们是我们对事情的唯一的正确认知,而这个”正确“,非得通过时间才能获得不可。


最近看了一篇文章,“How to Get Rich (without getting lucky)”,作者 Naval Ravikant。其中提到:

“Arm yourself with specific knowledge, accountability, and leverage.”

那么什么是 “Specific Knowledge” 呢?接下来有解释:

  1. “Specific knowledge is knowledge that you cannot be trained for. If society can train you, it can train someone else, and replace you.”
  2. “Specific knowledge is found by pursuing your genuine curiosity and passion rather than whatever is hot right now.”
  3. “Building specific knowledge will feel like play to you but will look like work to others.”
  4. “When specific knowledge is taught, it’s through apprenticeships, not schools.”
  5. “Specific knowledge is often highly technical or creative. It cannot be outsourced or automated.”

可能第4句最为贴合本文吧,也或许是其他的解释蕴含有原文的一定上下文。我认为,Specific Knowledge,就是我所说的,“事情的正确”,是需要大量时间,需要很强的动机驱动才能去了解到的,也是没法由别人交给你、只能靠自己去总结体会的。真要教、真要学,也得通过工坊式的学徒制,由老师傅手把手地教,手把手地带着自己去体会。当然,我也不希望矫枉过正,在此过程中,学校里学到的科学道理,一样能起到很重要的作用,只是离开了具体事情的道理,就像是一块二刀肉,离成菜回锅肉,还差的很远。

Paul Graham 也说过,“Do Things that Don’t Scale.”,不过他没有解释为什么,只是用很多实在的经验来介绍,有哪些东西是“Don’t Scale”的。我想,“事情的正确”,也正是针对这些“Don’t Scale”的事情的,它们之所以“Don’t Scale”,也正是因为需要时间才能掌握到“正确”吧。


提起教学,本文开头提到,我最近担任了人间地狱里战队的战术培训营教官,却正正是把战术这门 Specific Knowledge 教导给别人,就跟老师傅给下厨的新人讲解怎么炒一盘回锅肉一样。在教与学上,“正确”的时间性更是体现的尤为明显。

自己去认识一件事情的“正确”,已经如此困难,带着别人去认识,还会遇到新的困难。自己在认识的过程中,遇到困惑,得自己去琢磨答案,于细节处跟客观的道理做对应,于大局上找常见的经验去找直观。但是带着别人去认识的时候,别人的困惑,可是有千千万万种不同。别人在学习的过程中,不了解事情的来龙去脉是自不必说,有哪些细节也当然是不曾明白。学员之所以为学员,就是想省去那一段费心摸索的时间,直接把教员所已经了解的学到手,而教员想拔苗助长,却是无从下手、无可为之。把细节直接一一告诉学员,他们也无法轻易把这些细节跟事情本身联系起来,盖因他们缺少时间去体会细节与事情变化之间的直观联系。细节都不清楚,就更不用提细节之间的联系,孰高导致孰低、这长引起那短的影响了。缺少对细节的理解,对大局的观察,也只能流于模糊印象了,说不清所以然来。就像做回锅肉,如果大厨一上来,就把各项细节道明白,二刀肉要选哪一段最好,这又是源于猪的何种生理构造和运动规律,调料要使怎样的独门手法,掌握火候又得怎样去观察肉的颜色变化等等,学厨怎么能一下子接受的过来?学厨也只能记着,背诵下来,但缺乏每个环节的种种细节、种种变化,轮到他自己真正实施的时候,他还是得不免疑惑,我这方多做一点、那方少抖一些,似乎都是完全一样的,为什么成菜就是差别这么大呢?到头来,还是什么都没学到手,还是得通过时间,一点一点的琢磨。

然而学习就是不可能的吗?不。教与学,仍然可以是比起自己去摸索而效率更高的方式。为了把“事情的正确”教给别人,教员往往需要,把事情的本来面目,按照某种典型固化下来,假定下来,然后再根据这种典型,细划分为更小的典型,而这些小典型之间,遵循着事情内部的最典型联系。学员先把事情的典型学会,然后再通过时间,在教员的带领下去逐个体会个中的变化,从而逐渐掌握整个事情本来的面目。这样学员省去的是自己摸索琢磨、拨开迷雾的过程,而拨开迷雾之后,需要花费时间去了解的事情内部的各项变化,这个时间是省不了多少的。

这个过程,挺矛盾的,因为教员为了教导事情的”正确“,反而是先通过构造的”假定“,甚至是”错误“,来让学员认识一个事情的”假想“,然后再通过此来最终到达”正确“。就像是一部三小时长的电影,如果想知道电影是怎么样的,非得三个小时一分不落地看完才行,如果想省点时间,一下子知道电影讲了什么,那么就只能在三小时中,把几个关键的镜头节选出来,以作概括。如果不容易概括,万一听者看过同类电影,那么还可以用类似的故事打比方。这跟教员的教学,其实是一样的。就像教回锅肉,大厨一开始,干脆就固定炉灶、固定锅碗瓢盆,每次都用质量相当的二刀肉和辅料,让学员把步骤全都记下来,先练熟了,然后从火候、调料、原材料,逐个地变化,让学员逐渐体会,最终学员自然也就真的学会了这道菜了。学会之后回头看来,最开始大厨说的盐必须放多少、火必须调多大,学员势必会觉得,那应该都是错误的,只有在各种条件都固定成那样之后,这种限定才合理。所以说,教员对整体事情的假定、固化,是一种不真实的临时快照,但这恰恰是通往真实的捷径。

作为学员,这过程中也有甚多可以出力的地方,或者干脆说,学员自己的出力才是最重要的,毕竟要从无到有地最终掌握事情的“正确”的,是学员自己。如果学员已然明白“正确”的时间性,那是最好不过,那样他就能明白,“正确”不在咫尺之遥,不能一步到达,非得通过逐渐摸索,才能越来越清晰。教员所能做的,只是在浓雾迷宫里伸出一只手来,领着走上迷宫中最有代表性的一条道路,而道路旁边无数的分叉,风景都不尽相同,而又都是事情真实会发生的模样。知道这些,学员就能尽快吸收事情的典型,而不会被细枝末节的各种变化绊住手脚,同时,他又能留心这些变化,在事情的典型和细节的矛盾处继续推敲,逐渐绘出自己脑里关于这事情的图画来。若真能这么做,那么教员和学员的配合,是真走出一条”正确“的时间性的捷径来了,借助教员的引导,学员避开了盲人摸象的摸索,又能在事情的典型里最快的抓住概貌,还能通过典型发现事情的细节、变化的窍门,从概貌自顶而下,从窍门自底而上,事倍功半的探究属于自己的、事情的“正确”。

以往武侠小说,写道收徒都得挑选悟性好的,为师常常只指点一招一式,待学员自己冲破了任督二脉,修为就一飞冲天。我想,这样的描写,也是对“正确”的时间性以及教、学过程的一种典型概括吧。


由此可见,学习是一个发展性的过程。在学习中有正确和错误之分。所谓正确的内容,在完全学习到之后,学员就能自主的判别。但是在这之前,在学习的过程中,尤其是在施教的过程中,为了教导真正的“正确”,教员往往需要将内容片面化、固定化,在这当中正确性往往会被丧失。也就是说正确性,只有在获得后才存在,而在获得正确的过程当中,是否正确,是一个伪命题:学习时还没法去谈论这件事情正确还是错误。

写文章不也是吗?说几句自认为的道理其实十分简单,只是没有读者能有共鸣,没有人能读懂。只有自己能读懂的文章,还能叫文章吗?但要让别人读懂,光讲道理是没有用的,读者只会觉得枯燥乏味。于是写文章就得把自己想说的话,又凝聚在一起,又得一份份的拆开,每一份还得琢磨合适的例子,让读者基于他自己的经验能够明白,然后还得把每一份小道理之间的关系明白说清楚,最后又把道理凝聚到一起,带着读者自己想明白作者一开始想说的话。可见,我和你,也经历了一番如此的“错误”与“正确”。


参考文章:

How to get rich

Do Things that Don’t Scale

大家都知道一个问题吧:自来水不能直接喝,苹果不能直接吃,但为什么自来水洗的苹果就能直接吃?今天我就来较较真,讲讲这个问题。

自来水不能直接喝,自然是因为喝了容易生病。为什么容易生病?因为里面含有了容易致病的细菌,但是,都是含有了容易致病的细菌,比起喝了三大杯,我只喝了一小口,最终得病的可能性是不一样的,再极端一点,假设我只抿了一两滴,那几乎是不会得病的。假设人只抿了一两滴自来水就会得病,那自来水恐怕得称作“自来毒”了。因此,考虑自来水能不能直接喝,得考虑一个度的问题,诚然,自来水里含有了容易致病的细菌,喝得多了,确实有可能会得病,但是喝得少了,致病的可能性还是比较小的。

苹果不能直接吃,是因为它表面脏了,有些灰尘、泥土或者其它眼睛看不到的等等异物附着在上面,说到底,还是因为苹果皮上有容易致病的细菌。那为什么自来水洗过的苹果就能直接吃了?咱们知道,自来水能够把果皮上的脏东西洗掉,那么也就能顺带的把脏东西附带的容易致病的细菌给洗掉。果皮上细菌减少了,咱们吃起来也就不容易生病了。

总还是有朋友想不明白,两个脏的东西,怎么合在一起之后反而就变干净了?卫生这回事难道还能有负负得正一说?这里面的关键,还是一个度的问题。我们洗完苹果,果皮上原来有的脏东西,已经被洗掉了,还残留的已经少之又少,到了不足以让人生病的程度。而果皮上残留的自来水,能有多少?而且因为我们是用流水冲洗的,果皮上残留的自来水,也不会再含有被洗掉的脏东西,最后充其量也就是几滴、一小小口的量,这一点点自来水,也是不足以让人生病的。所以,最后吃起来就比较卫生、安全。

有些附着的细菌、异物,不溶于水,那自然就不容易被洗掉。即使我们用很多水去洗,最后可能还是大量地残留在果皮上。这种情况,即使是用水洗了,也是不适宜直接吃的。所以有人会用蔬果洗洁精去洗,这些细菌异物能够被蔬果洗洁精洗掉,最后又能直接吃了。蔬果洗洁精不能直接喝,水果也不能直接吃,但是两者结合一起洗过之后,苹果又能直接吃了,也是一个度的问题,现在相信读者朋友们能够知道这其中的缘故了。

Matplotlib 一文上手

0. 前言

不知道有多少朋友跟我一样,matplotlib 的介绍看了一篇又一篇,但是每次看完了把手上的代码调试通过后,下次遇到了新的画图需求,捡起来发现又全部忘记了。

学习的时候总是觉得,怎么看都只是学了个皮毛,需要设置坐标轴就去查一下怎么设置坐标轴,想要加一个颜色标尺就去查一查怎么加,知识总是东一块西一块。倒不是说查到的资料很零散,而是即使查到的资料把该讲的都讲到了,到自己要用的时候,脑子里还是没有形成一个完整的知识脉络,不能做到 “我想这么画,既然 matplotlib 的设计是这样子,那么可以这么画” ,而只能是 “我想这么画,搜搜相近的效果,发现这在 matplotlib 里面要这么画”

更糟糕的是,查到的资料,这么画也行,那样画也可以, 完全看不出来章法,这般死记硬背完全没办法记住,以至于每次遇到的代码片段都得像秘宝一样揣在某个角落,下次要用的时候再翻出来,此时肯定要重新理解一遍,往往倒不如直接拷贝过来接上同结构的数据,然后求神拜佛地试着运行一下,边调试调试,说不定又要再去搜下一段秘宝。

最近腾了一点时间,认真学习了 matplotlib 的用法,发现实际上它的逻辑概念是非常清晰、简单的,当然也非常容易理解、一下就能掌握。到网上各种找快速入门、拷贝即用的代码,反倒是舍近求远了。我既惊讶于其如此容易理解,又惊讶于,可能是它太易用了,导致太多为了使用而用的教程如此泛滥,反倒埋没了它本来的精炼的面目;又因为,特别是现在这时代,一旦掌握了可视化数据的能力,对任何与数据沾边的工作都会带来非常大的提升。因此,我决定写下这篇总结分享,希望跟我有同样疑惑的朋友也能够解决这个问题,能够专注在如何分析、展示数据的思考上,而不是被工具绊住了手脚。

1. 基本概念和API结构

闲话说完,来看看应该怎么来理解 matplotlib,以及如何正确使用。

1.1 MATLAB ?

matplotlib 实际上有两套绘图接口,两者是等价的,但是接口表现形式不同。一种是类MATLAB的接口,一种是面向对象(Object-Oriented,简称OO)形式的接口。

第一种类MATLAB的接口,实际上就是matplotlib.pyplot,也就是常见的 “plt”。官方称它为“有状态的接口”,意思是绘图状态是全局共享的,接口中函数都会改变它,类似 如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt

def f(t):
return np.exp(-t) * np.cos(2*np.pi*t)

t1 = np.arange(0.0, 5.0, 0.1)
t2 = np.arange(0.0, 5.0, 0.02)

plt.figure()
plt.subplot(211)
plt.plot(t1, f(t1), 'bo', t2, f(t2), 'k')

plt.subplot(212)
plt.plot(t2, np.cos(2*np.pi*t2), 'r--')
plt.show()

这里plt.subplot()函数切换了plt.plot()所作用的区域,因此同样都是plt.plot(),但却能画出两个图来。

如果是熟悉 MATLAB 绘图的朋友,相信对这套接口当然能信手拈来,很自然地就能用上 matplotlib 来进行绘图操作。但如果是不熟悉 MATLAB 绘图的朋友,例如我,则很容易被这套接口绕得糊里糊涂的。

这套接口很不 “Python” ,十分有理由相信它只是用来兼容 MATLAB 绘图的语法,让较大基数的人群可以从 MATLAB 无缝地迁移到 matplotlib 上的一种做法。实际上并不鼓励在 Python 中使用它。

1.2 OO, the correct way

Matplotlib 的第二套绘图接口可就 “Python” 多了,官方直接称为 “OO 接口” ,这套接口中,整个绘图被分为了几个对象,图中的元素都是依附在这些对象上的一些子对象或者子属性,只要我们知道了整张图在概念上是怎么通过这些对象组织起来的,那么很容易就能知道应该如何实现我们想要的效果。对象如下:

1
2
graph LR
Figure(Figure, 全图, 缩写 fig) -->|包含一个或多个| Axes(Axes, 子图, 缩写 ax)

也就是说:

例如

1
2
3
4
import matplotlib.pyplot as plt

fig, ax = plt.subplots() # fig 就是 Figure,ax 就是 Axes
ax.plot([1, 2, 3, 4], [1, 4, 2, 3]) # 画一些数据,前面是 x 数据,后面是 y 数据

既然是一对多,再来一个多Axes的例子:

1
2
3
4
5
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(1, 2) # nrows=1, ncols=2
ax1.plot([1, 2, 3, 4], [1, 4, 2, 3])
ax2.plot([1, 2, 3, 4], [2, 1, 4, 3])

是的,就是这么简单。使用 matplotlib 常用到的属性,都是依附在 Figure 或者 Axes 中。通常只要下面三步,就能实现自己想要的效果:

  1. 找到想修改的属性叫什么
  2. 在 Figure 或者 Axes 中找到对应的 API
  3. 看看 API 怎么用

其中,针对第1步,常用的属性已经列在下面了:

针对第2步,直接到官网上查,已经足够快方便了,当然要上网搜搜代码片段也没问题:

针对第3步,这才终于回到我们手上的“秘宝”——各类画图功能、样式的示例代码片段。当我们已知所画的图是由 Figure 和 Axes 所组成,并且常用属性都可以通过他们来配置时,再来读这些代码片段,我们就能跳过片段前后的繁琐或不相关内容,直接去读真正使得效果起作用的代码,究竟是对 Figure 还是 Axes 的什么属性,利用什么 API,起到了什么作用。这样既提高了对“秘宝”的利用效率,也加深了对 matplotlib 的认识,这样长久以往,我们就能对如何画图的理解越来越深入、经验越来越丰富,而不会反而越来越疏离。

怎么理解这个 Axes 呢?考虑到 “Axes” 是 “Axis” 的复数形式,有人建议将其理解为“一套坐标轴/坐标域”,虽然在官方文档里没有明确这么解释,但我觉得这么理解还是非常到位、准确的。

有时,我们查到的代码片段,是以 “类MATLAB”的接口给出的,不容易直接在“OO接口”中找到对应内容。不用着急,只需记住以下原则,就可以有迹可循:

  • “类MATLAB”接口更高层,实际上是使用“OO接口”来实现并封装的,没有包含什么特别的、“OO接口”实现不了的内容;
  • “类MATLAB”接口所谓的“有状态”,就是指它默默的帮我们在 Figure 和当前生效的 Axes[x] 上进行了操作而已;
  • 通常 plt.xxx() 的 API,都能在 Figure(即 fig)或者 Axes(即 ax)下找到同名的 API。试一试,或者搜一搜,很容易就能找到。

2. 举个例子

本文不是 matplotlib 哪个具体 API 的教程,只希望在指出整体性的概念,以便于理解。但为了说明地更充分些,还是需要举个具体的例子。

既然是例子,那就直接用上面的示例图吧,正好能覆盖这些常用的属性。成果如下:

首先,观察到是散点图,那么先来点数据,大约在 0.5 到 3.5 的范围内(x),生成 0.5 到 3.5 的值(y),简单估算下约 50 个样本数:

1
2
3
4
import numpy as np
np.random.seed(20200802)
x = np.random.rand(50) * 3 + 0.5
y = np.random.rand(50) * 3 + 0.5

然后套用上面例子先画个默认图:

1
2
3
4
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.scatter(x, y)

这里可以看到,因为要画散点图,所以原来例子中的 ax.plot() 已经换为了 ax.scatter()。想画其他类型怎么办?或者想知道有哪些类型可以选?查一下就知道了

接下来,我们按如下顺序,一项项把其他元素加进来吧:

  1. title
  2. tick 和 tick label
  3. axis label
  4. grid
  5. legend

2.1 title

简单搜一下,就能看到如何设置title。我们这里想设置整图的 title,那自然是设置 fig

1
fig.suptitle('Anatomy of a Figure')

实际上,官网的教程完整多了,还有示例代码可以下载。

2.2 tick 和 tick label

观察目标图,tick 设置的目标是:

  • x 轴和 y 轴都是从 0 到 4;
  • Major tick 间隔为 1;
  • Minor tick 间隔为 0.25,这两个术语也是示例图告诉我们的
1
2
3
4
5
6
7
8
9
10
11
12
13
import matplotlib.ticker as mticker

ax.set_xlim([0, 4]) # 设置x轴范围
ax.set_ylim([0, 4]) # y轴同理

ax.xaxis.set_major_locator(mticker.MultipleLocator(1)) # 设置x轴主坐标间隔为1
ax.xaxis.set_major_formatter(mticker.FormatStrFormatter('%d')) # 设置x轴主坐标显示格式
ax.xaxis.set_minor_locator(mticker.MultipleLocator(0.25)) # 设置x轴副坐标间隔为0.25
ax.xaxis.set_minor_formatter(mticker.FormatStrFormatter('%.2f')) # 设置x轴副坐标显示格式,留2位小数
ax.yaxis.set_major_locator(mticker.MultipleLocator(1)) # y轴同理
ax.yaxis.set_major_formatter(mticker.FormatStrFormatter('%d'))
ax.yaxis.set_minor_locator(mticker.MultipleLocator(0.25))
ax.yaxis.set_minor_formatter(mticker.FormatStrFormatter('%.2f'))

首先,我们的需求是实现了,但是显示上显然有点问题:

  • 副坐标和主坐标重叠了
  • 而且主坐标的刻度也不够突出

我们后续会解决这些问题,先继续。

官网教程在此

2.3 axis label

这个简单,前面都查到了:

1
2
ax.set_xlabel('X axis label')
ax.set_ylabel('Y axis label')

2.4 grid

至此应该轻车熟路了,找到 Axis 对象的API,注意我们需要画虚线:

1
2
ax.xaxis.grid(True, which='major', linestyle='--')
ax.yaxis.grid(True, which='major', linestyle='--')

2.4.x Axis

上述 4 节,实际上都属于 Axis 的一部分,比较完整、统一:的示例在此

回到 2.2 的问题,要解决的话,我们可以给 minor ticks 的显示增加一个逻辑:如果跟 major ticks 重合的话就不画了。当然方法有很多,这里以示例目的为主,最重要是向大家介绍 matplotlib.ticker.FuncFormatter ,利用它,我们可以在 ticker 上基本做到随心所欲的格式定制:

1
2
3
4
5
6
7
8
def minor_skip_format(val, pos):
if val % 1 == 0:
return ''
else:
return '%.2f' % val

ax.xaxis.set_minor_formatter(mticker.FuncFormatter(minor_skip_format))
ax.yaxis.set_minor_formatter(mticker.FuncFormatter(minor_skip_format))

最好把图的大小调整一下,更方正一点,坐标也不用挤在一起:

1
2
3
#fig, ax = plt.subplots()					# 把一开始的实例化换一下
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(1, 1, 1) # nrows=1, ncols=1, index=1(1x1,取第一个)

最后处理坐标刻度。默认估计是 1 左右,试验了一下,8 差不多:

1
ax.tick_params(which='major', length=8)

2.5 legend

要引入 legend,直接 ax.legend() 一句话就可以了,legend 的内容是由所画的图所决定的。这里还要额外加上要画的曲线:

1
2
3
4
5
6
X = np.linspace(0.5, 3.5, 100)
Y1 = 3+np.cos(X)
Y2 = 1+np.cos(1+X/0.75)/2
ax.plot(X, Y1, 'b-', label="Blue signal")
ax.plot(X, Y2, 'r--', label="Red signal")
ax.legend()

colors, line type and markers

上面 legend 的例子里,我们看到在 ax.plot() 参数里有比较特别的参数 'b-''r--',这是什么意思呢?这其实也是从 MATLAB 继承过来的,示例这里是指定了颜色和线条样式。这种参数在网上很多的示例里面都有,也不太容易记忆。为了避免频繁查阅,这里给出了备查表:

colors 颜色
b blue,蓝,默认
g green,绿
r red,红
c cyan,青
m magenta
y yellow,黄
k black,黑
w white,白

颜色有很丰富的设置接口,可以参考这个示例,关于各类颜色名字,这里列得更全。

line type 线条样式
- 实线,默认
虚线
-. 横-点交替
:

线条样式也有很灵活的设置方法,参考这里

除了线条以外,其实还能以其他符号来画我们的曲线,例如如果把最开始的散点图改成 ax.scatter(x, y, marker='s'),那么就可以得到:

markers 标记
. 圆点
, 一个像素点
o
v, ^, <, > 三角,分别指向下、上、左、右四个方向
1, 2, 3, 4 三叉形,分别指向下、上、左、右四个方向
s, p, 8 方形(square),五边形(pentagon),8边形
*, +, x 分别就是*,+和x
h, H 竖向6边形,横向6边形
d, D 瘦型钻石和钻石
|, _ 竖线,横线

标记也有很多类型,这里列得更全。

总结

上述是我自己一步步模仿着示例图搭起来的,其实自始至终就是找到绘图的元素对象,而绝大多数情况都是 Axes,然后用 API 实现自己想要的效果而已,常见的属性也确实都是前面示例图给出的那些,这个示例只是实践了一下。

最后,在搭建示例的过程中,还干脆直接发现了官方的示例代码:Anatomy of a figure,可以看到,legend 示例中的曲线,我用了官方的示例然后做了点简化。主要区别:

  • 官方示例包含 Text Annotate 文字标注的方法,值得一看;
  • 官方示例实现了很多圆圈,利用的是 ax.add_artists() 接口,属于比较高级的手法了。关于 Artist,可以继续看下文的介绍。

3. 官网资料

官网资料非常详尽、非常细致,而且循序渐进、分类合理、导航充分,我获益良多,觉得大部分情况都没有必要再去找别的资料了。

教程,即“应如何使用”,包含“介绍”、“中级”、“高级”三大内容,还有“颜色”、“文字”两大专题,以及“额外工具集”一节。

  • “介绍”主要就是讲本文介绍的基本概念,以及基础使用。其中这一篇很值得读,对常见样式的使用给出了很好的例子。
  • “中级”进一步介绍了几个概念,有常用到的 legend,还有各类 Layout(也就是多个 Axes 在 Figure 中的排布方法),还有 “Artist” 这个概念,很值得想了解 matplotlib 绘制逻辑的朋友一看。
  • 颜色是画图中很重要的一个维度,相当于在数据坐标轴外平添了一个正交的坐标轴,而且人对颜色也更为敏感,对读者理解要展示的内容尤其有帮助。有需求的话可以参看颜色专题
  • 灵活的文本设置也能给图提供精准的注解,也有对应的专题可以参看,其中还包括对 TeX 公式的支持。

教程入口:https://matplotlib.org/3.2.2/tutorials/index.html

整体文档入口:https://matplotlib.org/3.2.2/contents.html

3.1 Artist

Matplotlib 实际的绘图概括为三个抽象

  • Canvas,画布,实际绘画的区域
  • Renderer,应该怎么在画布 Canvas 上绘画
  • Artist,应该怎么利用 Renderer 在画布 Canvas 上画

其中 Canvas 和 Renderer都属于 Backend,什么是 Backend 呢?Matplotlib 支持在很多平台进行绘图输出,例如在 python REPL 里弹出绘图框,在 Jupyter notebook 里嵌入绘图结果,或者将绘图写成不同格式的静态图片文件,它还支持在 GUI 应用里直接嵌入绘图,显然,在各类绘图应用场景下,matplotlib 实际依赖的绘图接口肯定都是不一样的,在不同的操作系统下肯定也不同。这些都属于 backend。Canvas,就是实际画的图数据的抽象,Renderer,就是绘制动作的抽象,两者类似于画布和画笔,给实际要画的内容提供了工具。谁知道实际要画什么内容?“艺术家” Artist 咯。

Artist 就是实际的线条、矩形、圆等等有内在逻辑的绘图元素,以及这些元素的容器,也就是常见的自嵌套的结构。我们作为用户,只是利用预设好的这些绘图元素,拼接设置成我们想要的样式,从而获得想要的绘图而已。

1
2
3
4
graph TD
Artist --> Primitives元素
Artist --> Containers容器
Containers容器 .-> Artist

我们画图一般的流程是:

  1. 首先通过 plt.figure() ,先创建出 Figure,并将其绑定到 Canvas 上;
  2. 然后通过 fig.add_subplot(),在 Figure 中添加 Axes;
  3. 由此可以看到,上述两步通常也有其他等价的步骤,可以是一步到位的 plt.subplots(),也可以是另外的API如 fig.add_axes() 等。实际用哪个 API 可能没那么重要,关键是知道它们背后做了什么;
  4. 接下来利用 Axes 提供的各类方法,添加一个个的 Artist,为我们的图添加一系列绘图元素。

知道这一层抽象可能不见得在我们平时画图过程中能产生什么作用,但是在遇到问题时还是有一点帮助的,起码查资料时能有一个模糊的方向,指不定就能一下子节省好几天没头苍蝇乱撞的时间呢。