about.me/acgtyrant

2019 Feb 24
来,友情地交换 contact 吧!

如果你看到我发给你的这篇博文,这意味着你应该是用 non-86 手机号注册的 Telegram 用户,而我需要和你聊天,我请求你和我交换 contact。因为 Telegram 有个限制,用 86 手机号注册的用户无法单方面先向用 non-86 手机号注册的用户发消息,除非后者主动向前者发下消息,这限制才会解除,然而是暂时性的,如果彼此之间有很长时间没有互动的话,又会重新有这限制。我就是 86 用户,被这限制烦恼很久了。

不过其实还有一劳永逸的解决办法,就是 share contact。如果你愿意 share contact,在手机客户端里打开和我的聊天框,点右上角(大概),然后 share contact 即可。出于礼貌,我也会向你 share 我的 contact,然后你点击显示着手机号的消息,add contact 即可,这样我就成为你的 contact 了。contact 就如同字面上的意思吧,你的 Telegram 储存着你的 contacts,且里面的人哪怕修改了 ID 或手机号,他依然会在你的 contacts(大概?)。我的 Telegram ID 是 acgtyrant.

当然,你拒绝也没关系,不用做任何回应,我以后也不会在 Telegram 上找你聊天了。

我是分割上下文的文字版简陋分割线。

如果我通过私人联系渠道,比如推特,邮箱,个人博客留言板等,发给你网址从而你一个人看到该博文的话,那我确实是在请求你 share contact。反之则不是,比如通过 RSS 读到本博文的读者并不是我的请求对象,所以不要误会了。

2018 Dec 9
如何避免无谓的作死

自从开始健身并改善自己的身体后,我也开始关注生命健康起来,首当其冲地是避免无谓的作死。首先,医学界上并不存在所谓的「自然死亡」说法。那些老人在睡梦中安详逝世的死因实则应该是心力衰竭,即心脏老了,跳不动了。于是实际上所有器官衰竭导致的死亡都算病死。从长远来看,人迟早是要死的,所以最理想的情况下要么是为了充实且宝贵的一生,撑到器官衰竭,要么为了理想而作死,毕竟自由的黑暗真谛之一是人死得其所,比如参战保卫国家的士兵光荣牺牲,再比如为了挑战极限而在生死边缘运动且失手的冒险家。于是除此之外的死因都无谓。

不论和平时代还是战争时代,全球数一数二的死因均是心脏病和中风,事实上原理都很好理解,即高血压高血脂导致的心血管疾病,换句话说心血管被血栓之类的东西堵死,造成严重的缺血,而心脏和大脑又是最重要的血液需求器官,于是分别所发生的冠心病和中风致死率很高。常见的糖尿病也和肥胖有很大的关系。于是就该健康饮食,积极锻炼,避免变成高血压高血脂的胖子就行了,真不难,最简单的指标应该是 BMI 低于 24 即可,再具体点则是体检时测出的血压和血液里的相关脂肪含量。

再接下来的常见死因则是慢性病,其中又要数慢性阻塞性肺病最突出,顾名思义,肺部吸入太多阻塞性颗粒,慢慢被损坏,而且似乎是不可逆转的。于是最该避免的作死是吸烟了,空气污染也要注意。事实上烟草所导致的肺病和肺癌致死率加起来很高,大概仅次于心血管疾病。总而言之, 不要吸烟!二手烟也要远离,多支持爱国卫生运动委员会的禁烟运动。 其他慢性病没什么好说的,毕竟已经和上文已提到的高血压高血脂高度相关,比如糖尿病。

下呼吸道感染仍然是最致命的传染病,这个多注意新闻就行了,可以听相关卫生机构的指挥,每年打流感疫苗,戴口罩预防等。

癌症也很危险,要远离致癌物质,据我所知有石膏、酒精、烟草、甲醛、放射源和紫外线等。于是住房时要注意甲醛含量,有个阿里巴巴工程师住自如租房才半年就得了急性白血病;出门有必要时打伞防晒,别像倒霉的金刚狼患上皮肤癌;留意危险的放射源,少做 CT;饮食尽可能多样化,比单一营养更好;高度注意饮食卫生;我发现病毒还会致癌,包括乙肝、人乳头状瘤病毒和幽门螺旋菌等,事实上乙肝和人乳头状瘤病毒都可以打疫苗预防!不要以为成年后就不用打疫苗了;远离环境污染。

病因讲完了,还有人为死因和自然灾害也要避免。战争和凶杀之类没啥特别好的办法,其实 FBI 针对恐怖袭击而宣传的 Run-Hide-Fight 原则也挺适用于战争和凶杀上的,能逃命就逃,能避免起冲突就避免,不得已就只能正面搏命了,太危险。至于意外事故和自然灾害,可以看看《日常生存自救手册》或《浩劫求生》,也许比政府宣传的有意思多了……

如果你还想学会基本的急救知识的话,可以考虑去美国 AHA 认证的机构或红十字会培训,杭州也有杭州市急救中心初级救护员证书可以考。Coursera 也有相关中文课程,虽然讲课很生硬。

最后表扬下世界卫生组织,它真心在改善全球人类的健康状况,挺了不起。

参考资料:

Written with StackEdit.

2018 Oct 2
Linux 剪贴板・终阶

旷野之息 力之试炼 终阶+

我用 Linux 好多年,依然没真正理清 Linux 下剪贴板的用法,因为很多程序的复制粘贴行为都不一样,何况X还有两种剪贴板,再加上我经常跨机编辑,即 termite+ssh+tmux+neovim,这么多程序叠加在一起,就一直不知道到底怎么复制粘贴好。这不,今天我要在远程主机 yy 上通过 NeoVim 选中里面的内容,复制到另一个远程主机 103 的 NeoVim 上,乱按快捷键一通也没解决。于是终于彻底下定决心搞清 Linux 以及众多程序的剪贴板原理,在此整理并归档研究结论。

原理

按这两篇的说法,所谓两种剪贴板 Clipboard 和 Primary selection 都是X的功能。区别是前者用 Windows 那样的复制粘贴键 ctrl+c/ctrl+v,后者只需选中内容,就可以在别的地方用鼠标中键粘贴。还有 shift+insert 能起到 ctrl+v 的作用,但这键太冷门了就不记了。

此外其实这两个剪贴板都是异步的——只有在粘贴时,才会真正触发复制!我实践发现,哪怕在 Gedit 主动用 ctrl+c 复制后,关掉 Gedit,在 Google Chrome 的地址栏就粘贴不出来任何东西了。 于是 ArchWiki 建议改用剪贴板管理器来解决。此外为了措辞上的方便,下文假设复制粘贴是同步的,比如「复制东西进剪贴板」。

选中也不一定可以复制,除了依云提到的,我还发现 Google Chrome 按 alt+d 选中地址栏的 URL,不能复制;但鼠标双击地址栏全选,能复制。

简化原则

为了解决如此复杂的剪贴板问题,我需要设定一些简化原则,减轻记忆负担。

  • 只用 clipboard!从此以后就当 Linux 只存在一种剪贴板。自然不用记住选中却无法复制的例外情况了。primary,不存在。

  • 尽可能地让所有程序的复制粘贴键接近 vim-binding。

程序

开始整理并归档御用程序如何使用剪贴板的解决方案。

御用剪贴板交互命令・xclip

除了用程序本身的复制粘贴键、鼠标选中复制与中键粘贴行为,其实也有现成的命令,能在 Shell 里与X剪贴板互动。最常见的有两个命令: ‘xclip’ vs. ‘xsel’

由于 xsel 已经两年没更新了,而且 GitHub 官方帮助又钦定 xclip,再加上 xclip 比 xsel 顺耳许多了。我决定 xclip 作为御用剪贴板交互命令。

xclip 默认复制进 primary,坑。

御用虚拟终端・Termite

https://wiki.archlinux.org/index.php/Termite

Termite 像 Vim,有两种模式:Insert 和 Selection。

Insert 模式下,可以直接用鼠标选中内容,但它的行为和 Vim 不一样:仍然处于 Insert 模式!用户可以一边选中内容,一边输入内容。由于虚拟终端和 Shell 紧密相关,不能直接用 ctrl+c/ctrl+v,只能用 ctrl+shift+c/ctrl+shift+v 代替复制粘贴。

ctrl+shift+space 可以进入 selection 模式,行为和 Vim 一样,不赘述。

复制会复制到X剪贴板,我测试了下发现包括 clipboard.

由于我习惯用 Tmux,Termite 的复制粘贴功能其实不重要,无需记忆。不过,我发现 Termite 的 ctrl+shift+v 在 Tmux 一样有效

御用网络传输协议・SSH

既然剪贴板由X负责,那么我们需要 SSH 能够转发远程主机上的X程序到本地上,从而让远程主机能与本地的剪贴板互动。信任远程主机的话,alias ssh='ssh -Y' 即可。

实践证明,我 SSH 到远程主机并开启 Tmux 后,我可以把 clipboard 的东西通过 Termite 的 ctrl+shifht+v 粘贴进该 Tmux Session 的某 pane 里。

御用终端多路复用器・Tmux

由于 Termite 的复制粘贴直接作用于整个虚拟终端的界面上,不分 Tmux 里的 pane,于是需要掌握 Tmux 下能在 pane 复制粘贴行为。

Tmux 也有两种模式。一是常规模式,即用户在 pane 里的行为和单个虚拟终端一样;另一是 copy-mode,为能够在 pane 下滚动历史或复制而服务,其又有两种 binding,一是 Emacs 另一是 Vim,默认用 Emacs binding。

我习惯 vim-like 步骤与行为,自然要重新设置为 Vim Binding: set-window-option -g mode-keys vi

此外 Tmux 2.4 有重新定义 Key Binding 语法,本文不考虑 Tmux 2.4 之前的旧 Key Binding 语法。

Tmux 原本进入 copy-mode 的默认 key binding 为 prefix+[,太蠢,为保持与 Vim Binding 一致,可以直接修改: bind-key Escape copy-mode # enter copy mode (prefix Escape),模仿在 Vim 从 Insert 模式退出,进入 Normal 模式的行为。

再继续改造 key bindings:

1
2
3
4
5
6
7
bind-key -T copy-mode-vi 'v' send-keys -X begin-selection
bind-key -T copy-mode-vi 'V' send-keys -X select-line
bind-key -T copy-mode-vi 'r' send-keys -X rectangle-toggle
bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel "xclip -in -selection clipboard"

需要注意的是 Tmux copy mode 不支持 Vim Visual 模式 ctrl+v 那样的 block selection,但是可以通过 r 改变 v 的 selection 行为,即从跨行 selection 和 block selection 之间转换。y 则是粘贴进 clipboard 了。

更进一步地像 Vim 那样能滚动浏览 pane 的历史:

1
2
3
4
5
6
7
8
9
# scroll like vim
bind-key -T copy-mode-vi f send-keys page-down
bind-key -T copy-mode-vi b send-keys page-up
bind-key -T copy-mode-vi d send-keys halfpage-down
bind-key -T copy-mode-vi u send-keys halfpage-up

其实本来还可以加 bind-key p run "xclip -o -sel clip | tmux load-buffer - ; tmux paste-buffer",不过还是 ctrl+shift+v 粘贴更方便,就不加了。

御用文本编辑器・NeoVim

Vim 要有开启 clipboard 编译选项,才支持剪贴板(大概)。据说 Arch Linux 的 vim 就没开启!只能改装 gvim 包了。

好在 NeoVim 支持,但需要额外的依赖,help clipboard 指出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
The presence of a working clipboard tool implicitly enables the '+' and '*'
registers. Nvim looks for these clipboard tools, in order of priority:
- |g:clipboard|
- pbcopy/pbpaste (macOS)
- xsel (if $DISPLAY is set)
- xclip (if $DISPLAY is set)
- lemonade (for SSH) https://github.com/pocke/lemonade
- doitclient (for SSH) http://www.chiark.greenend.org.uk/~sgtatham/doit/
- win32yank (Windows)
- tmux (if $TMUX is set)

显然装 xclip 就行了。

Vim 默认 VISUAL 选中内容不会复制进 primary,这样正好,毕竟 Vim 重在编辑。可以通过 shift+"+y 复制进 clipboard。

至于到底怎么复制粘贴,看 How to make vim paste from (and copy to) system’s clipboard? 就够了。

最后,如何在 SSH 到远程主机再 Tmux 后再开 NeoVim,如何复制粘贴到本地?简单,ssh -Y,本地和远程主机都装 xclip,跟平常一样复制粘贴。

御用剪贴板管理器・Flicx Clipboard

参见 Fcitx Clipboard,在其插件的高级设置里可以把 primary text 关掉。作为剪贴板管理器已经足够好用了。

Written with StackEdit.

2018 Jul 3
我又双迁移到 NeoVim 了

两年前,我曾在技术博客发表过《用 Neovim 取代 Vim》,但当时我很快又放弃 NeoVim 了,继续用 Vim。因为我始终没找到令我满意的 NeoVim 的 GUI;再次,Ubuntu 16.04 没有打包 NeoVim,每次在新开发机上部署开发环境编译安装 NeoVim 也很麻烦;最后,Vim 7.5 也支持异步并发 API,以致我不太青睐 NeoVim 了。

其实我之所以一定要用 GUI,因为我一直没真正配置好虚拟终端,终端多路复用器和文本编辑器的色彩,导致 Vim 在虚拟终端和多路复用器会话里显示的色彩非常怪异,所以我实际上过去一直只用 GVim,不爱用 Vim。一月半前,在机缘巧合的他人帮助下,我正确配置好了 Vim 的 256 color,以致以后不论在本地还是远程主机上,都可以直接在虚拟终端下愉快地用 Vim 开发了,这也间接地为后来的迁移大作战扫除了障碍。

一月前,我偶然读到Vim 8 下 C/C++ 开发环境搭建,里面介绍了众多令人叹为观止的现代 Vim 插件,有些需要在 NeoVim 上才能用。于是我心血来潮,又双迁移到 NeoVIm 了,并且迁移大作战成功!

其间我发现 NeoVim 本身已经编译好了 Linux 上通用的二进制包,直接下载 nvim.appimage(版本为目前最新的 3.1)到 $HOME/.local/bin 并命名为你喜欢的即可,当然别忘了修改其为可执行文件。这样以后只要如此少量的命令,就可以快速在开发机上部署新开发环境了,毕竟不比安装发行版的 Vim 二进制包麻烦多少,何况 Ubuntu 16.04 发行版提供的二进制包 Vim 版本还处于 7.4 时代,老掉牙。

我再重新解说下迁移到 NeoVim 的好处:

全面并发

虽说 Vim 8 也支持了,但这当初应该是 NeoVim 倒逼 Vim 出来的,不可忽略。此外在全面并发的支持下,vim-plug 安装我高达三十多个的插件,不到半分钟!这可怕的速度在以前 Vim 时代是不可想象的。Vim 8 下 C/C++ 开发环境搭建 里面提到的很多强大插件,也实实在在得到了全面的速度提升好处。

默认配置更加友好

说起来 Vim 的一些过时默认配置会让你大吃一惊,比如它默认的 encoding 至今依然是 latin1 !Neovim 当然早改用 utf-8 了。还有一些不合时宜的默认 setting 也纷纷得到了修改,免去用户手动配置之苦,我就在迁移原 .vimrc 时删去了 25 行多,净化完毕,更加极简!

充分遵循 XDG 规范

Vim 默认的 .vimrc.vim 均一般在 $HOME 下,Neovim 则全挪为 $XDG_CONFIG_HOME/nvim/init.vim$XDG_CONFIG_HOME/nvim.

Vim 编辑文件时,可以有多达四个的数据文件:backup, swapfile, undofileviminfo. Unix 下的 Vim 分别默认存在 ".,~/tmp,~/", ".,~/tmp,/var/tmp,/tmp", ".", 其中 viminfo 的具体储存位置我一时还查不出来,就懒得深究了。后来依云指出:undofile 默认没有值,不保存撤销记录,viminfo 位于用户目录。

NeoVim 则全改储存在 $XDG_DATA_HOME/nvim/ 下各自的目录里,此外 viminfo 更是被抛弃,被叫 ShaDa 且更为先进的二进制文件所代替,后者位于 $XDG_DATA_HOME/nvim/shada/main.shada.

再加上用户自行安装的 NeoVim 二进制包也在 $HOME/.local/bin 里,一家人更加整整齐齐。

完美无瑕的真彩

具体详见在 Linux 下全面使用真彩

看!我的括号会发光!

那么问题来了:怎么迁移?

Arch Linux 用户都装 neovim, python-neovimpython2-neovim. Ubuntu 用户下载官方编译好的二进制包并更改为 $HOME/.local/bin 的可执行文件。

除非你用干净的 dotfiles 管理 Vim, 否则自行清理插件;再迁移到 XDG 目录下;改用支持 Neovim 的 vim-plug 并重新安装所有插件,可以先按 https://github.com/junegunn/vim-plug/wiki/tips#automatic-installation 加入相关代码,以致只要一打开 NeoVim 它就会自动安装包管理器本身和所有包,超方便的;按 vim-difference 来打扫 init.vim 中已无用的设置。

Written with StackEdit.

2018 Jun 26
在 Linux 下全面使用真彩

Termite, Tmux and NeoVim in true color.

我过去一直没真正弄好虚拟终端、Tmux 和 Vim 的配色,今天下定决心,终于彻底理清了它们在颜色上的来龙去脉。本文目标如题,那些本来支持真彩的成熟 GUI 程序就不说了,重点便是如何配色好 Termite,Tmux 和 NeoVim 等。

颜色有色彩深度之分。

8-bit color 又名 256 color,用于最早期的彩色 Unix 工作站,于是一些古代虚拟终端连同这种局限也虚拟出来了,即它们只支持 8 位颜色,比如 xterm 和 urxvt。还有古老的终端多路复用器 screen 和 Tmux 也默认用 256 颜色。

24-bit color 又名 true color,一共有 16,777,216 colors,于是有人习惯用 “16 million colors” 称呼它,就像先前大家有时用 256 color 称呼 8-bit color 一样。它的表示形式要么是 a 24-bit hex value (e.g. #4a32b1),要么是 an rgba vector (e.g. rgba(16, 32, 64)

32-bit color 基于 24-bit color 而生,增加了 8-bit 透明通道。真・现代虚拟终端 Termite 就支持,以致它的背景可以变成半透明,阿宅喜欢靠它来看 waifu。此外 Termite 还可以直接把 8-bit color 单射到 24-bit color,比如 color0 = #073642 意味着把第一个 8-bit color 映射到某 24-bit color #073642

有个环境变量叫 TERM,contains the type of the running terminal, e.g. xterm-256color. It is used by programs running in the terminal that wish to use terminal-specific capabilities. 我猜它主要是用来告知程序当前虚拟终端支持的颜色,好让后者自动选择相应合适的颜色空间。我发现 Termite 和 Tmux 会自动设置相应的 TERM,即分别为 xterm-termitescreen,由此看来用户不需要手动指定 TERM 了。

不过上面既然提到了 Tmux 的 TERMscreen,而后者又只支持 256 color,于是得让它改用 true color,好在 Tmux 2.2 已经支持了,设置也不难

最后的关键便是 Vim 的配色了,256 colors in vim 发表于二〇〇六年,它还把支持 256 color 的 XTerm 当成所谓的现代虚拟终端,还说若 vim 想在 256 color 虚拟终端使用 256 color colorscheme,需要在 .vimrc 显式设置 set t_Co=256,这说明 Vim 本身默认的颜色空间可能都小于 8-bit!它不愧比虚拟终端还要古老。

五年后,依云发表《让 Vim 在终端下和 GVIM 一样漂亮:gui2term.py 更新至 3.0 版》,按这篇的说法,Vim 在 256 color 虚拟终端里没法像 GVim 用 24-bit color 即 True Color ColorScheme,需要通过某脚本把 True Color ColorScheme 转换成 Vim 可加载的 256 color ColorScheme,一劳永逸解决配色问题,虽然是近似的。

如今,@NanozukiCrows 发了一条推:

就是这推促使了我彻底折腾并搞定颜色的来龙去脉。现在御用真・现代虚拟终端 Termite 本来就支持 32-bit color,御用终端多路复用器 Tmux 2.2 也支持 True Color。尽管我不清楚 Vim 对 True Color 的支持如何,但御用现代文本编辑器 NeoVim 设置 termguicolors 就可以支持了,再用任意 True Color ColorScheme,比如 Plug 'morhetz/gruvbox, colorscheme gruvbox 即可,当然,别忘了去掉 set t_Co=256 这蛋疼的古代颜色支持方案。

True Colour (16 million colours) support in various terminal applications and terminals 也科普得很棒,里面有若干 console programs 实在让人眼一亮,比如 timgls-icons,原来真彩支持好了,在虚拟终端看图像和 Icons 也不难。

Written with StackEdit.

2017 Sep 24
无视原则

遥想还呆在 Acfun 的当年,大家在关于自杀现象的某文章里正战得欢时,一条评论留给了我很深的印象,原话已不可考,按照模糊的印象大概是:日本社会算是彻彻底底冷漠到了极点,同情也好,挖苦也好,都意味著被自杀者触动了。然而日本大多人已经冷漠到对愈演愈烈的自杀现象依然无动于衷,该干嘛的就干嘛,不管自杀者在人生尽头故意能闹得有多惊天动地,也很快地在一阵喧嚣后被世人尽数遗忘。于是自杀现象俨然成了日本人中平淡无奇,再熟视无睹不过的日常。

此后很长时间里,它成了我一直所发现的「最残忍的被动反应」,注意这行为只属于被动激发的反射行为分类。若要考虑主动层次上的恶意行为,方法多得是,但这并不在本文的讨论范围之内。

我并不是左右脸均给敌方扇的圣人,若必要时,便只能以敌意对抗恶意。不过我贯彻更为中庸的「无视原则」,即遭到来自敌方的恶意攻击行为,若物理上的损失可忽略,就自动激发无视其的被动反应。注意,这里的「无视」纯粹就是字面上的意思,并不掺带多余的任何其他反应。而且,无视既是高贵的蔑视,又是宏大的宽容。无视原则能够化来恶意为虚无,且不作出任何攻击反弹,「我蔑视他人这恶意的攻击,但我还是宽恕他人这愚蠢的行为」,很符合我的「和」价值观。

恕我再改言之,无视意味著绝对无比的「无动于衷」,算是绝妙的蔑视态度了。那些并不造成任何物理上损失的恶意攻击,可以粗糙地归为一类:「挑衅」。这行为上的的确如同字面意义,为的是想激怒受害方,使后者产生不悦感,那么「无视」反应恰恰就能很好地挫败敌方的这一意图。心境如水,霸气十足。我来示范下:我在知乎上拉黑用户的原则是一旦遭受来自恶意用户的人身攻击,就直接「静默拉黑」,这举动在敌方看来就仿佛当事人泰然不为所动,没有任何反应,只轻轻地把他一手指弹到黑名单池,一劳永逸且不可逆转地切断了一切接触途径。毫无疑问,这脑补能当场给予敌方99999伤害,后者越中二伤害就越高。

于是,我反而奇怪 Real Life 中为什么仍旧有那么多人热衷于讨论令人反感的事物,例如几年前天天输的国足,这货臭名昭著得不能再臭,于是满口国足国足的观众,就如同满口排泄物排泄物一样令我厌恶。国足已经差劲到没有再被关注的任何价值,就让它自生自灭去,还偏要提,不光自我弄脏嘴巴,还影响他人心情。

我说过,欣赏也好,厌恶也好,都意味著被触动,有触动就意味著关注,而而大量的关注就能创造经济上的价值二〇一二年年初归真堂活取熊胆的事件闹得很火,其实这可以算是对官方非常有利益的炒作,试想,如果哪天某人的亲人突然处于生命危险期,要被救活只有一个办法:「使用熊胆产品」,那么他人最先想到的会是什么?没错就是那因活熊取胆而一举臭名昭著的归真堂,但当事人一般情况下会不管一二,就消费熊胆产品先救活了亲人再说,于是说到底赢家还是归真堂。凤姐,芙蓉姐姐等人颇为使人反感,却笑到了最后,也同理。

最后,我并不提倡对一切来自敌方的恶意一律采取「无视原则」,否则有时就和阿Q的「精神胜利法」没两样。那么什么情况下不便采用被动的「无视原则」呢,那便是遭到物理上的实际损失时,这时就要站出来主动维持自己的利益了。对于归真堂案例,若要惩罚这家公司,理想的情况便是舆论批评得严重到后者被法院执行经济制裁,或是遭到市场上的抵制而损失惨重。后来这家公司上市曲折无比。

2017 Sep 19
只有 Python 高手知道的 tuple

熟悉 Python 的人都知道,它从语法上支持的内置数据结构一共四种,即 tuple, list, dict, set 等, sequence comprehension 一共有三个,唯独没有 tuple comprehension, 其形式被解析为生成器表达式,而且表示单元素的 tuple 也很不一样,需要额外添加一个在新手看来十分碍眼的逗号(1,).

但其实我们只简单地把 tuple 理解为一个 immutable 版 list 了,并没有深刻理解到它的本质。推上有人如此一针见血地指出:

我终于发现了,tuple 既可以当数据结构,又可以当模式来匹配变量。所以 tuple 说穿了是一个「不规则又不可变的数据结构或模式」,自然没有「推导规律」介入的余地,也难怪没有所谓的 tuple comprehension 了。

一旦洞察 tuple 的本质后,你就发现,tuple 原来大有文章:

其一,事实上,返回多个值的函数实则是在返回一个 tuple, 只不过可以省略括号而已。一旦理解透这个,就不会再写出 tp, label_tp = [], [] if len(result) == 0 else zip(*result) 这大坑了。

其二,只有一个变量的 tuple 在模式里可谓等价于变量本身,于是不是说 (1,) 里的逗号多余,而是它本身的括号就多余,毕竟没人爱写 (a,) = (b,); 其实 () 才是唯一真正特殊的 tuple; 于是纯括号就可以被名正言顺地赋予数学上的意义了,即 (a) 等于 a, 而且其实这还带来了方便 wrap line 的额外好处:

1
2
3
hfm_dir_str = (
'/home/acgtyrant/Projects/HamsterForMTK/'
'Hamster_Android_SDK/src/com/mapbar/hamster')

其三,虽说没多少人喜欢 (1,) 这写法,但我们可以分拆 sequence 值成多行,且每行后更有一个逗号!这对强迫症患者真是终极福音:

1
2
3
4
5
6
7
8
9
10
11
12
phodopus_evaluate_command = (
command_pathname,
'-v', video_pathname,
'--proto', proto_pathname,
'--model', model_pathname,
'--mean', mean_pathname,
'-c', cascade_model_pathname,
'--lf_proto', lf_proto_pathname,
'--lf_model', lf_model_pathname,
'--lf_mean', lf_mean_pathname,
'--noshow',
)

于是我习惯分拆函数的超长形参列表多行了,且最后一行以右括号结尾;多行的 sequence 值则倒数第二行以逗号结尾,倒数一行以右括号结尾且不缩进。

其四,tuple 不光可当安全的 immutable 数据结构,而且效率公认比 list 好

此外,tuple 在模式匹配上也威力十足:

其一,方便交换变量:a, b = b, a, 毕竟等价于 (a, b) = (b, a) 模式匹配嘛。

其二,PEP 3132 – Extended Iterable UnpackingPEP 448 – Additional Unpacking Generalizations 也是模式匹配。此外,我们不能直接用星号 unpack generator, 但照样可以模式匹配 a, *_ = range(10), 毕竟只要后者能迭代就行了。现在看来,由于我已经洞察了 tuple 的本质,对它原本的坏印象也没有了。

其三,如果你想要可读性更好的模式匹配,可以试试 collections.namedtuple 库。

2017 Sep 12
「听力残疾」不完全科普

中国残疾人联合会指定的听力残疾等级标准指出:

听力残疾,是指人由于各种原因导致双耳不同程度的永久性听力障碍,听不到或听不清周围环境声及言语声,以致影响其日常生活和社会参与。

听力残疾的分为如下几级:

听力残疾一级:

听觉系统的结构和功能方面极重度损伤,较好耳平均听力损失≥ 91 dB HL
,在无助听设备帮助下,不能依靠听觉进行言语交流,在理解和交流等活动上极度受限,在参与社会生活方面存在极严重障碍。

听力残疾二级:

听觉系统的结构和功能重度损伤,较好耳平均听力损失在 81~90 dB HL
之间,在无助听设备帮助下,在理解和交流等活动上重度受限,在参与社会生活方面存在严重障碍。

听力残疾三级:

听觉系统的结构和功能中重度损伤,较好耳平均听力损失在 61~80 dB HL
之间,在无助听设备帮助下,在理解和交流等活动上中度受限,在参与社会生活方面存在中度障碍。

听力残疾四级:

听觉系统的结构和功能中度损伤,较好耳平均听力损失在 41~60dB HL
之间,在无助听设备帮助下,在理解和交流等活动上轻度受限,在参与社会生活方面存在轻度障碍。

我属于一级,但有很多人难以暸解其具体困难之处,以致有相当多不准确的解读,我便感到很有必要一次性澄清了。

所发现的解读主要有:「听不见」、「听不清」和「听不懂」等。若你语文嗅觉足够敏锐,当然容易一眼看出其不同:

  • 听不见:又称「听不到」,即几乎感觉不出一切声音
  • 听不清:听得见声音,但是「干扰」很严重,从而只接收到「失真」的声音,听障人本身的听力缺陷有可能会引发此现象。
  • 听不懂:听得见又听得请,但是不知要如何理解其意思。就像小孩听不懂大人的高深话题,中国人听不懂老外说啥一样。

不过,就我的经验来看,实际情况是要更复杂些,以上三种并非彻底彼此独立,即彼此有多为交集:

其一,若听不见的缺陷并不完全,也就是说还是能听得见部分声音,就会产生由「听不见」缺陷所导致的「听不清」障碍。打个比方,一个听障人能明确听得见「aoe」,但是几乎听不见「iu」。于是若一个人对ta说了「啊噢呃咦唔」,但是ta是「听不清」完整的句子的,因为ta只能明确地听得见「啊噢呃」,「咦唔」就听不见,于是ta或许可以通过对方的口型看出有发出了五个字的声音,ta 还知道前三个字是什么,但是就是不清楚后两者又是什么。于是此谓由并不完全的「听不见」缺陷,所导致的「听不清」缺陷

其二,若一个人听不清对方的话,当然就会不好理解对方所表达的,即由「听不清」所导致的「听不懂」的障碍。我猜学过英语的诸位应该深有体会:在正式学习英语之前,当听见一段纯粹的英语对白时,只会觉得它如同「呼噜呼噜」一样足够「模糊不清」的一段声音,要模仿发音更是不可能,因为我们连其所包含的每一个音标单位的发音都不知道。于是我们若要听得懂这段对白,就得弄清楚句子的完整读法,即把它分割成众多独立的单词,再一个一个地弄清楚其意思,其发音又是什么。当我们熟悉了每一个单词,于是要听得懂完整句子就不难了,理解其意思也水到渠成。所以结论是:若要听得懂外语,就必须先克服「听不清」的障碍,而这恰恰是需要练习的。其实对母语的学习也是如此,只不过因为在母语环境中,实在足够「耳濡目染」,以致大家长大了,都会几乎忘了自己对母语的掌握,其实是有「练习」过的印象。

我说过自己属于听力残疾一级级别,当然也就处于纯粹的「听不见」处境了,听力损失有一百分贝多,是什么样子的?家庭聚餐时,我扭头看电视,全然听不见位置相邻的父亲狂涛怒吼过。若你靠近我耳旁用力尖叫,能听得见一点点点。不过如今可不好说,因为听力衰退越来越厉害了,其实据母亲说,一开始听力分贝损失是 85-110, 可至少在两年前衰退到 100-120 了。

好在「助听器」能打破这困境,原理应该是通过耳背上的仪器收集物理上的外部声音,并放大声音响度并输送到耳部,突破听力损失的阈值,从而让用户「听得见」了。

仍无法解决「听不清」的障碍,我说过听力缺陷比较复杂,其实上面所用到的「啊噢呃咦唔」例子还不够贴切,用颜色来类比更好:正常人能看到五颜六色的世界,但听障人就像色盲一样,只能看到有颜色偏差的世界。什么颜色看得见,什么颜色看不见?这个就因人而异了。

于是就轮到我正在使用的「人工耳蜗」隆重登场了。听障人的种种问题,大多应该可以归结于「耳蜗」的生理缺陷,但人工耳蜗就可以取而代之,即通过手术,把它嵌入到耳蜗处,并通过「电磁感应」现象,来接收附在外部的「言语处理器」所在物理上所收集到的外部声音信号,转化为神经上的脉冲信号,绕过了原本功能缺失的耳蜗,直接发送给大脑,从而让大脑直接感到「听觉」。

既然彻底绕过了原本有缺陷的「耳蜗」,则「听不清」障碍就几乎全无,用户能像正常人一样直接听到极其不失真的「原声」。所以如今我实际上只处于「听不懂」处境,即对汉语和英语的掌握,前者需要进一步的练习,后者需要从零开始,这些都只是如同小孩牙牙学语的另一外回事了。

当然,就目前的技术来说,人工耳蜗并不能像天然耳蜗输送纯粹的「原声」,只能尽可能地减少「失真度」而已。就目前来说,我的困难之处如下:

  1. 需要花时间从头开始学习英语听说,很久以前我就开始上某英国绅士的英语口语课,当前只听得懂大概六分之一;该绅士真是好人。
  2. 几乎没法进行「多人对话」,很多饭局就只能默默地低头吃饭……
  3. 在空间相当大却又嘈杂的地方,很难听清对方的话。
  4. 没法一边注意力分散在别处时,一边听同行者的话,比如上下楼梯。
  5. 很疲劳的时候,听力效果也会大幅度下降,可谓累得不想再听了……
  6. 普通话口音很怪,先天没训练好,矫正要付出大量精力。

2017 Sep 4
单位元对编程的启发

最近,我在处理一个文本文件,它有三列,分别为文件名,数字,字符串,且其中第一列中有若干重复了的部分,于是我需要把这些重复的行合并起来,即第二列合并为总和,第三列合并为一条新字符串,且用空格分隔。

我一开始想如此合并:

1
2
3
4
5
sum = 0
string = ''
for duplicate_line in duplicate_lines:
sum += int(duplicate_line.split()[1])
string = ' '.join(string, duplicate_line.split()[2])

但迭代完后 string 并不符合我的期望,它包含了一个 heading space! 这令我想起了抽象代数学上的「单位元」,即它在某二元运算中,与任意元素运算后,结果的值恒为后者本身。比如加法中的 0 与任何数字相加,恒返回后者的原值;乘法中的 1 也同理。

于是我们可以说,把 ‘ ‘.join() 当二元运算符来看时,不存在单位元,即 ‘ ‘.join([a, b]) 中 b 为任意字符串时,不存在 a 变量能使这函数返回 b 本身,哪怕变量 a 为空字符串时,也会返回包含了一个 heading space 和 b 值的新字符串!就像我上面的代码一样。不过 ‘’.join() 当然就有单位元即空字符串。

反观 Python 的 int 加法,它就有单位元,即 sum 的初值 0, 也难怪它和悲剧的 string 君不同,可以安全地返回我期望的值。

这启发了我又一条编程规范:要小心那些没有单位元的函数或运算符,并处理得当。其实上面合并 string 的代码可以妙用 list comprehension, 回避掉 heading space.

1
' '.join([duplicate_line.split()[2] for duplicate_line in duplicate_lines])

举一反三,Python Sequences 的 index 为什么从 0 而不是 1 开始计呢?您倒不如想想另一个问题:Sequences Index 的单位元是什么?即对 Index 进行二元运算时,加多少就返回原 Index? 当然是 0 了!再看 C 指针,它和 0 相加时就返回原指针,也难怪 C 的 Array Index 和 Python 的 Sequences Index 都从 0 开始,这是为了当直接把原 Index(甚至 C 的指针即地址)和 Index 初始值(即单位元)相加时,能方便且直接地得到原 Index(甚至 C 的原指针即原地址)

2017 Sep 3
什么情况下 map 比 list comprehension 好?

事实上,list comprehension 公认效率比 map 好所以当 map 比 list comprehension 可读性好且不在乎性能时,可以优先用前者。

那问题又来了:什么时候 map 的可读性比较好?当且只当低阶函数是你所熟悉的 Python 函数,便利的 sequence 对象也够一目了然时。比如我就经常用它转换命令列表,够一气呵成:

1
2
3
4
5
6
7
8
traincascade_command = [
command_pathname,
'-w', weight,
'-h', height,
'vec', vec_pathname,
]
traincascade_command = map(str, traincascade_command)
subprocess.call(traincascade_command)

妈妈再也不用担心我不小心传 int, pathlib.Path 进命令列表了,写的 list comprehension 又长又臭得超出 79 个字符了。同理,rects = map(int, rect_strs), positives = map(abs, integers) 之类的精炼表达式也可以。

因为你很清楚 str, int 和 abs 等是一元函数,作用是什么;反之,即用你所不熟悉的 Python 函数,比如 a = map(b, c), 这时你往往会产生四重疑惑:

其一,b 是一个 callable 对象吗?
其二,b 可以只接受一个形参吗,即它到底是不是一元函数?
其三,c 是否为 sequence 对象?
其四,c 的 iterable 元素又是什么鬼?

更别说用雪上加霜的 lambda 了。

但换用 list comprehension 就没这问题:a = [b(d) for d in c], 显然 b(d) 表达了 b 彻彻底底是个一元函数,且 c 作为 sequence 对象时 iterable 元素是 d, 最终 a 会被 bind 到一个 list 对象。可读性更胜一筹。

举一反三,你可以琢磨什么情况下 filter, funtools.reduce 等也能如法炮制。