揭秘·变态的平方根倒数算法

zh

神的时代已离去 神的故事却化为传说 流落凡间 供凡人传颂、膜拜

这是什么

在上世纪 90 年代,出现过一款不可思议的游戏——雷神之锤(Quake series)。除了优秀的情节设定和精美的画面,最让人称道的莫过于它的运行效率——要知道在那个计算机配置低下的时代,一段小动画都是一个奇迹,但 Quake 却能流畅地运行于各种配置的电脑上。

直至 2005 年,当 Quake Engine 开源时,Quake 系列的秘密才被揭开。在代码库中,人们发现了许多堪称神来之笔的算法。它们以极其变态的高效率,压榨着计算机的性能,进而才支撑起了 90 年代 3D 游戏的传奇。其中的某些算法,甚至比系统原生的实现还要快!

我们今天的主角——快速平方根倒数算法(Fast Inverse Square Root)就是其中一个。

READ MORE

神坑·Python 装饰类无限递归

zh

《神坑》系列将会不定期更新一些可遇而不可求的坑 防止他人入坑,也防止自己再次入坑

简化版问题

现有两个 View 类:

class View(object):

def method(self):
# Do something...
pass

class ChildView(View):

def method(self):
# Do something else ...
super(ChildView, self).method()

以及一个用于修饰该类的装饰器函数 register——用于装饰类的装饰器很常见(如 django.contrib.adminregister),通常可极大地减少定义相似类时的工作量:

class Mixin(object):
pass

def register(cls):

return type(
'DecoratedView',
(Mixin, cls),
{}
)

这个装饰器为被装饰类附加上一个额外的父类 Mixin,以增添自定义的功能。

完整的代码如下:

class Mixin(object):
pass

def register(cls):

return type(
cls.__name__,
(Mixin, cls),
{}
)

class View(object):

def method(self):
# Do something...
pass

@register
class ChildView(View):

def method(self):
# Do something else ...
super(ChildView, self).method()

看上去似乎没什么问题。然而一旦调用 View().method(),却会报出诡异的 无限递归 错误:

# ...
File "test.py", line 23, in method
super(ChildView, self).method()
File "test.py", line 23, in method
super(ChildView, self).method()
File "test.py", line 23, in method
super(ChildView, self).method()
RuntimeError: maximum recursion depth exceeded while calling a Python object

【一脸懵逼】

猜想 & 验证

从 Traceback 中可以发现:是 super(ChildView, self).method() 在不停地调用自己——这着实让我吃了一惊,因为 按理说 super 应该沿着继承链查找父类,可为什么在这里 super 神秘地失效了呢?

为了验证 super(...).method 的指向,可以尝试将该语句改为 print(super(ChildView, self).method),并观察结果:

<bound method ChildView.method of <__main__.ChildView object at 0xb70fec6c>>

输出表明: method 的指向确实有误,此处本应为 View.method

super 是 python 内置方法,肯定不会出错。那,会不会是 super 的参数有误呢?

super 的签名为 super(cls, instance),宏观效果为 遍历 cls 的继承链查找父类方法,并以 instance 作为 self 进行调用。如今查找结果有误,说明 继承链是错误的,因而极有可能是 cls 出错。

因此,有必要探测一下 ChildView 的指向。在 method 中加上一句: print(ChildView)

<class '__main__.DecoratedView'>

原来,作用域中的 ChildView 已经被改变了。

真相

一切都源于装饰器语法糖。我们回忆一下装饰器的等价语法:

@decorator
class Class:
pass

等价于

class Class:
pass

Class = decorator(Class)

这说明:装饰器会更改该作用域内被装饰名称的指向

这本来没什么,但和 super 一起使用时却会出问题。通常情况下我们会将本类的名称传给 super(在这里为 ChildView),而本类名称和装饰器语法存在于同一作用域中,从而在装饰时被一同修改了(在本例中指向了子类 DecoratedView),进而使 super(...).method 指向了 DecoratedView 的最近祖先也就是 ChildView 自身的 method 方法,导致递归调用。

解决方案

找到了病因,就不难想到解决方法了。核心思路就是:不要更改被装饰名称的引用

如果你只是想在内部使用装饰后的新类,可以在装饰器方法中使用 DecoratedView,而在装饰器返回时 return cls,以保持引用不变:

def register(cls):

decorated = type(
'DecoratedView',
(Mixin, cls),
{}
)

# Do something with decorated

return cls

这种方法的缺点是:从外部无法使用 ChildView.another_method 调用 Mixin 上的方法。可如果真的有这样的需求,可以采用另一个解决方案:

def register(cls):

cls.another_method = Mixin.another_method
return cls

即通过赋值的方式为 cls 添加 Mixin 上的新方法,缺点是较为繁琐。

两种方法各有利弊,要根据实际场景权衡使用。

READ MORE

Python“黑魔法”之 Encoding & Decoding

zh

写在前面

  • 本文为科普文
  • 本文中的例子在 Ubuntu 14.04 / Python 2.7.11 下运行成功,Python 3+ 的接口有些许不同,需要读者自行转换

引子

先看一段代码:

example.py

# -*- conding=yi -*-

从 math 导入 sin, pi

打印 'sin(pi) =', sin(pi)

这是什么?!是 Python 吗?可以运行吗?——想必你会问。

我可以明确告诉你:这不是 Python,但它可以用 Python 解释器运行。当然,如果你愿意,可以叫它“Yython” (易语言 + Python)。

怎么做到的?也许你已经注意到第一行的奇怪注释——没错,秘密全在这里。

这种黑魔法,还要从 PEP 263 说起。

READ MORE

Ubuntu 重新映射键盘布局

zh

键盘持续失灵,已经到了让我忍无可忍的地步了。

刚开始只是方向键失灵,好在可以用小键盘替代;后来右 Ctrl 和 Alt 也失灵了,好在可以用左边的替代;直到最近 Fn 键也失灵了,终于逼疯了我——因为这意味着 F1 ~ F12 都将不能使用。

我曾试图寻找方法将 CapsLock 键映射为 Fn 键映射,但失败了——Fn 键消息是由 BIOS 拦截的,无法被操作系统捕获。

但今天我找到了一个更好的替代方案:

  • 交换 Fn 和 Ctrl。这是唯一一种能让 Fn 键移位的方式,在所有的 BIOS 中都可以设置。
  • 将 CapsLock 映射为 Ctrl。反正 CapsLock 闲着也是闲着,不如用它代替坏了的键。

ubuntu 下需要执行:

setxkbmap -layout us -option ctrl:nocaps

参考: How do I turn Caps Lock into an extra Control key? - Ask Ubuntu

READ MORE

为什么我要翻墙

zh

很多人不解为什么我要翻墙。他们认为,国家明令禁止的东西就不应该去做——而更何况,墙内的世界已经足够精彩,异世界的天空也不一定令人神往。

在中国大陆,能上网的人被分为两类:翻墙的 与 不翻墙的。这里说的“翻墙”不是“有能力翻墙”,而是“有意愿翻墙”。这两类人奇怪地构成了一个鄙视环。翻墙的 看不起 不翻墙的,认为后者甘于“虚假”的现状。其中不乏有狂热的传教者,不遗余力地宣传墙外的世界,并对不接受者嗤之以鼻;同时,不翻墙的 又鄙视 翻墙的,认为后者崇洋媚外,被国外反华势力蛊惑,以致对 翻墙 产生如信仰宗教般的虔诚。

对于不理解我的人,我表示理解。如果我也以你们的身份生活在你们的环境,墙内的世界的确很大、很精彩,的确足够了。再者,翻墙 很麻烦、很不稳定,牺牲流畅性与易用性去浏览一些无趣的内容,确实不值得。

但我想说的是:墙内的世界对于某些人而言,贫乏得可伶。

我喜欢编程。与写作不同的是,编程不仅要靠思考,更需要一些外部工具的辅助。思维是编程工作的灵魂,但思维仅仅提供了一个脉络,在思维的引领下,我需要组合使用各种工具来解决问题。工具的数量很多,细节很复杂,我不可能记住,也不应该都记住。因此,我需要搜索它们的使用说明。一个好的搜索引擎是编程时必备的利器,而我选择了 Google。不是因为我崇洋媚外,而是 它 的确 能高效地让我找到答案,解决我的问题。

我要翻墙,因为我想要的工具在墙内不存在。同时,有一些我想要的答案也在墙外(如 Wikipedia)或部分在墙外(如 引用了 Google CDN 的 StackOverflow)或时不时在墙外(如 Github)。当你的合理需求无法满足时,理智的人都会想方设法地去满足——这很自然,就像吃完了一片草地的羊群会自动迁徙至另一片草地一样。

是的,我翻墙,只是因为我有翻墙的需求。对于某些翻墙发烧友所推荐的某些“真相”,我不感兴趣。隐匿的东西之所以被隐匿,总有它的理由;而被揭露的东西之所以被揭露,也总有他们的目的。

百度作的恶,不能原谅。但抛开这一点,我并不排斥百度。百度不如 Google 科学,但它更适合中国。西方人更注重逻辑思维,同时他们也有更高的受教育程度,科学的 Google 是他们所需要的。而在中国,真正需要科学的只有很少一部分人,更多的人需要的只是科普,一个浅显的答案,只要结论正确,甚至不需要完全严密的推理,足够了。更何况,中国的网络是一个娱乐至上的环境,一个八卦的搜索引擎也许会比一个严谨刻板的搜索引擎更受国人青睐。

对于 GFW,即 墙 本身,我也不怨恨。起初接触到这样一个存在时,我很惊讶,感叹国家还有这样的一面。这个无形的庞然大物产生于旧世纪末,有其特定的时代背景,被人诟病的同时却也有着它的合理性。有人认为,思想自由是人权的一部分,GFW 的存在是反人类的。有段时间我信以为然,并传播这样的思想。但当我真实地接触到那些“被禁锢的人”时,我却犹豫了。前段时间的“诺贝尔哥”事件就是一个很好的样例。游荡在网络中的许多中国人是不理智的——尽管他们也受过教育,但接受的只是知识,而不是思维。他们甚至不知逻辑为何物。这些人看起来是无害的,可一旦他们接触到所谓的“真相”,或说是一些人想让我们接触到的“真相”时,感性会使他们意气用事,造成社会的不稳定。诚然,人权至上,但社会稳定更重要,人权在乱世只是一纸空话。历史的对错,国家自己知道。民众应该知道历史,但如果真相会对现实产生不可预测的坏影响,倒不如让它烂在土里。

我不嘲笑 不翻墙 的人,他们扮演着自己的角色,有着自己的需求与选择;我也会继续 翻墙,因为我有这样的需要。

驱使一切的,只是需求而已。

READ MORE

Python“黑魔法”之 Generator Coroutines

zh

写在前面

学过 Python 的都知道,Python 里有一个很厉害的概念叫做 生成器(Generators)。一个生成器就像是一个微小的线程,可以随处暂停,也可以随时恢复执行,还可以和代码块外部进行数据交换。恰当使用生成器,可以极大地简化代码逻辑。

也许,你可以熟练地使用生成器完成一些看似不可能的任务,如“无穷斐波那契数列”,并引以为豪,认为所谓的生成器也不过如此——那我可要告诉你:这些都太小儿科了,下面我所要介绍的绝对会让你大开眼界。

生成器 可以实现 协程,你相信吗?

READ MORE

数学美 之 判断线段相交的最简方法

zh

解析几何的巅峰 是 向量 那无关过程的狂妄与简洁 映射着大自然无与伦比的美

引子

如何判断两条直线是否相交?

这很容易。平面直线,无非就是两种关系:相交 或 平行。因此,只需判断它们是否平行即可。而直线平行,等价于它们的斜率相等,只需分别计算出它们的斜率,即可做出判断。

但倘若我把“直线”换成“线段”呢——如何判断两条线段是否相交?

这就有些难度了。和 直线 不同,线段 是有固定长度的,即使它们所属的两条直线相交,这两条线段也不一定相交。

也许你会说:分情况讨论不就行了嘛:

  • 先计算两条线段的斜率,判断是否平行。若平行,则一定不相交。
  • 若不平行,求出两条线段的直线方程,联立之,解出交点坐标。
  • 运用定比分点公式,判断交点是否在两条线段上。

的确,从理论上这是一个可行的办法,这也是人们手动计算时普遍采用的方法。

然而,这个方法并不怎么适用于计算机。原因如下:

  • 计算中出现了除法(斜率计算、定比分点),因此每次计算前都要判断除数是否为 0(或接近 0)。这很麻烦,严重干扰逻辑的表达。
  • 浮点精度丢失带来的误差。人类计算时可以采用分数,但计算机不行。计算机在储存浮点数时会有精度丢失的现象。一旦算法的计算量大起来,误差会被急剧放大,影响结果准确性。
  • 效率低下。浮点乘除会十分耗时,不适用于对实时性要求较高的生产环境(如 游戏)。

那么,有更好的方法?

当然有。

READ MORE

除夕杂感

zh

直到下午四点,供桌才摆上。

冒着热气的鸡,整只;两尾鱼,张着嘴卧在盘中;一摞柑,愣是叠成了宝塔的形状;此外,还有一包包花花绿绿的零食。一米见方的桌子被塞得满满的,全是贡品。

母亲忙得团团转。本来是不想拜天公的,但转念一想,不是太好,便摆上了。阳光下,热气一直往上冒,往上冒,直到看不见的地方——那里,想必就住着那位神。

看来,这位神一定是个重要人物了——不然,母亲怎会遗忘了其他的几位,而只供奉他一个?

搬家之前,家里可还是住着许多神的:门口有一个,阳台上有一个,厨房有一个,抽屉下有一个,就连洗衣机上也有一个。听母亲说:每一家都有,每一位神,都在守卫着这个家。

神们也不是白干活,也是要吃饭的。不只是过年,每个月中总有那么几天,神们会一起来要吃的。每到这时,母亲便会忙活起来,为他们张罗吃的。好在他们并不挑剔——生的,熟的,速食的,神们都默默地收下了。当然,贡品在屋子里转了一圈,自然又都进到了我们的肚子里。

每换一个地方,母亲便上一炷香,虔诚地跪下来,轻轻地拜两拜,口中念念有词。

接着,她又拉我一起来。

我不懂,她便教我——想着自己想要的事就好了。

我学着样子跪下来,胡乱说了几句,装模作样地拜了两拜,余光却仍盯着那食物。

听母亲说,这是老家带来的习俗。对神的敬畏,在那个古老的地方,说着那古老语言的人们,已经沿袭了很久很久了。

人终是怕神的——以前是,现在也是;中国是,外国也是。神像一群喜欢偷窥的人,在这里,也在那里,在每个角落偷窥着你。你受欺负时,神会给你庇护;你做坏事时,神会予你惩罚。因此,怕神的人,多是善良的。

说是怕神,倒不如说是怕天,敬畏变幻莫测的未知。活物总是怀着对死亡的恐惧,人类也不例外。未知中蕴藏着杀机,使愚昧的人类感到不安,转而求助于那假象中的造物主,那超能力者,那个开着全局视角看戏的“人”,这便成了“神”。神为人类抵挡着未知,人类也因此安分守己,深怕触犯了神,再次被暴露于未知的荒野中。

然而,神正在离去,因为未知正在散去。

但藏在那未知背后的是什么,谁又知道呢?未知的背后仍是未知,现实可以是虚幻,真理也可以是谬误,时间洪流夹带着未知,使任何人都只能屏息,任何人,都不可以妄自尊大。

好在,还有神——尽管神正在离去。

但终究,怕神的人是善良的。

READ MORE

17 行代码实现的简易 Javascript 字符串模板

zh

这是源于两年前,当我在做人生中第一个真正意义上的网站时遇到的一个问题。该网站采用前后端分离的方式,由后端的 REST 接口返回 JSON 数据,再由前端渲染到页面上。

同许多初学 Javascript 的菜鸟一样,起初,我也是采用拼接字符串的形式,将 JSON 数据嵌入 HTML 中。开始时代码量较少,暂时还可以接受。但当页面结构复杂起来后,其弱点开始变得无法忍受起来:

  • 书写不连贯。每写一个变量就要断一下,插入一个 +"。十分容易出错。
  • 无法重用。HTML 片段都是离散化的数据,难以对其中重复的部分进行提取。
  • 无法很好地利用 <template> 标签。这是 HTML5 中新增的一个标签,标准极力推荐将 HTML 模板放入 <template> 标签中,使代码更简洁。

为了解决这个问题,我暂时放下了手上的项目,花了半个小时实现一个极简易的字符串模板。

READ MORE

Python“黑魔法”之 Meta Classes

zh

接触过 Django 的同学都应该十分熟悉它的 ORM 系统。对于 python 新手而言,这是一项几乎可以被称作“黑科技”的特性:只要你在 models.py 中随便定义一个 Model 的子类,Django 便可以:

  • 获取它的字段定义,并转换成表结构
  • 读取 Meta 内部类,并转化成相应的配置信息。对于特殊的 Model(如 abstractproxy),还要进行相应的转换
  • 为没有定义 objectsModel 加上一个默认的 Manager

开发之余,我也曾脑补过其背后的原理。曾经,我认为是这样的:

启动时,遍历models.py中的所有属性,找到Model的子类,并对其进行上述的修改。

当初,我还以为自己触碰到了真理,并曾将其应用到实际生产中——为 SAE 的 KVDB 写了一个类 ORM 系统。然而在实现的过程中,我明显感受到了这种方法的丑陋,而且性能并不出色(因为要遍历所有的定义模块)。

那么事实上,Django 是怎么实现的呢?

自古以来我们制造东西的方法都是“自上而下”的,是用切削、分割、组合的方法来制造。然而,生命是自下而上地,自发地建造起来的,这个过程极为低廉。 ——王晋康《水星播种》

这句话揭示了生命的神奇所在:真正的生命都是由基本物质自发构成的,而非造物主流水线式的加工

那么,如果 类 也有生命的话,对它自己的修饰就不应该由调用者来完成,而应该是自发的

幸而,python 提供了造物主的接口——这便是 Meta Classes,或者称为“元类”。

READ MORE