无价的价值

今天看到一则杂闻,摘抄简略如下:

假离婚变真离婚,真相原来这般残酷!在北京工作的小琴(化名)近日在网上发帖讲述自己的遭遇:“身为北大博士后的丈夫,以孩子户口为由骗我离婚,之后不肯复婚了,还很快跟女同事结婚,并且新欢已怀孕。”如今,她只有一个想法,就是要讨个说法!

看到之后,觉得其实有很多东西,如果你重视它,它就有自己的价值;如果觉得无所谓,那么它就是真的没什么价值。”无价“来表达这个意思最合适了。听起来有点像宗教,算是有点像吧,但是今天不谈宗教。我最近有这种想法主要还是来源游戏。

最近主要在玩《血源诅咒》和在手机上玩一个老游戏,《This War of Mine》。发现很多游戏有这么一个特点:如果有一天某个地方卡了很久打不过去,或者某个游戏玩的时间长了,你可能把手柄一扔,觉得“算了吧,这游戏也就这样,同样的技巧打打杀杀,找到 Boss 弱点慢慢磨最后过关”。基本上这是我玩腻大多数游戏的原因。一旦有这种想法了,再去玩就觉得真没意思,里面的怪物对我来说毫无吸引力,即使出现新的怪物也没有精神。相反,没有这种想法的时候,就觉得自己真的在这里世界里,享受跟怪物周旋的过程。有趣和无趣,有意义和没有意义,就在一念之间。

上面这一段好像很难把我在自己的意思表达清楚。《This War of Mine》这个游戏更典型一些。这是个战争生存游戏,你可以去各种地方搜刮物资,也会遇到各种各样的人。你需要经常做出抉择:比如在一个寂静的小屋里有一对老人物资非常丰富,抢了他们的东西他们就会死去。你可以把它当成一个纯粹的游戏来玩,按照收益的高低做出各种决定;也可以将自己代入到这个游戏中,依照自己的道德标准做决定。如果你真的认为是自己在这个战争中,那么游戏里面的人不再是只会说两句话的 NPC ,而是战争下受难的苍生。当然了,如果不管自己的道德观念,只是玩这个游戏的话,也可以体验到游戏的乐趣。

回到开头提到的这条新闻。世界上就是有这么两种人:一种人会计算收益行事,假离婚能买到房子,那就假离婚;一种人注重仪式,认为婚姻是一辈子的大事,必须认真看待。这里面有对错吗?我觉得没有。现在的社会后一种人已经很少了。新闻里的这个女人,只能怪自己笨加上运气不好吧。

现在的生活节奏太快了,赚再多的钱,买第二套房子,第三套,意义是什么呢?很少有人会再纠结这个问题了吧。在《血源诅咒》里面击杀怪物,获得血之回响(这个游戏中击杀怪物获得的经验),升级自己的属性,这意义是什么呢?这个游戏中并不需要那么高的属性就可以通关,属性的加成作用还是很小的,其实主要是看技术。我们的人生也一样啊!我们并不需要那么多钱就可以生活的很好。但是很多人都会没日没夜地拼命赚钱,游戏里也有玩家一遍又一遍地刷着血之回响。可能到了最后,这些玩家会发现自己所有的属性加到了最高,可以毫无压力地击杀 Boss ,回头来看却发现自己花费了如此多的时间在经验上,这么多的时间,没有在享受游戏!这样的玩家是不是和游戏中的“加斯科因”神父一样,沉迷于狩猎而迷失了自我?

那么人生的意义是什么呢?游戏的意义是什么?

可能这个问题本来就没有答案吧。如果你认认真真地玩这个游戏,可能会在游戏中体会到更多的乐趣,会体会到喜怒哀乐,会觉得游戏中的小女孩、神父、教堂的女人都是实实在在的人物,可能会为他们高兴、伤心或叹息吧!在生活中,如果认为仪式是一件重要的事情,认为家庭、婚姻是无法用金钱衡量的东西,如果有自己深爱的事业或兴趣,那么也可能体会到更多生活的乐趣吧!

这对我们有什么启发意义呢?好像并没有什么,可能这些都是我的胡思乱想。不过我以前有过一次经历,通过作弊器将某个游戏的经验直接升到最高,缺发现我马上对这个游戏失去了兴趣,觉得什么都有了,什么也都没啥意思。如果我在血源诅咒中有大量的血之回响,可能我也会觉得这个游戏没意思吧。

 

爬虫如何判断一个页面更新了?

一个网页距离爬虫上次访问是否更新了,这个是重要但是又非常难判断的问题。如果只是一次性抓取来一些数据来用,那这个问题无关紧要。但是通常情况下,我们抓到一个页面之后,还需要知道这个页面的后续变化,如果内容变了,必须再抓一次,让我们数据库保存的数据保持与目标页面的同步。

目前来说,我们使用的方法是定期(例如每天深夜)抓取。将所有的爬虫脚本用 crontab 的方式管理。这样是无状态的,我不需要之后目标网站到底更新了没有,定期抓一次保持同步就可以。

这样的问题就是浪费资源。无论对方更新没有,你都要像个傻瓜一样执行一遍同样的任务。如果能先知道这个网页是否更新了数据,就能停止后续的抓取工作,节省一些带宽和计算资源。如果你要监控1千个网站,那么每天去抓一整遍1000个网站所有页面,和每天抓取1000个网站更新的内容,区别是巨大的。

其实吧,这个是长久以来的痛点。早在远古时代,大家都耿直地在自己的博客上更新内容,订阅其他人的博客(而不必每天打开一下所有的页面)就成了一个硬性的需求,所以就有了RSS。可惜的是,并不是所有网站都提供RSS源的,这个功能指望不上。

在网上查了一下,最后找到两种思路,都不是非常完美。第一种基于 HTTP 协议,第二种基于网页内容。

1.使用HTTP状态码判断

HTTP状态码规定了304字段,在 Chrome 控制台可以看到很多 js css 文件都是 304 状态码的。这种方法优点是节省资源;缺点是极不可靠,因为并不是所有的服务器都会返回 304 状态码,况且现在很多网站是单页应用,动态加载。 index.html 是一直不会变的。知乎上有位同学描述了具体的做法,我直接抄过来了。

  1. 第一次先请求某个网页,抓取到本地,假设文件名为 a.html。这时文件系统有个文件的修改时间。
  2. 第二次访问网页,如果发现本地已经有了 a.html,则向服务器发送一个 If-Modified-Since 的请求。 把 a.html 的修改时间写到请求里。
  3. 如果网页更新了,服务器会返回一个 200 的应答,这时就重新抓取网页,更新本地文件。
  4. 如果网页没有更新,服务器会返回一个304的应答。这时就不需要更新文件了。

这种方法的弊端和 RSS 一样,你不能指望所有的网站都正确实现了 HTTP 。比较靠谱的、但是有些麻烦的是根据网页内容判断。

2.基于网页内容的比较

由于要纵向比较网页内容,所以不免这个思路是“有状态的”,需要数据库或类似的服务保存历史网页。

历史网页的对比也是个难点。有些网页有展示“访问量”,“当前时间”的内容。这些东西的变化不应该算作“网页更新了”。比较靠谱的是监控页面的某个 xpath ,而不是网页全部。不靠谱的是采用各种判断条件花式决定“网页更新没有”。比如:

  • 网页最大面试的 block 是否更新
  • 对网页进行截图,对比截图的相似度
  • diff一下相似率

还发现了一个“网页指纹+海明距离”的算法,大体看了一下这个算法貌似是判断海量网页中两个网页是不是相同的事情。比如同一条新闻被两个网站转载,两个网站 html 完全不一样,但是新闻的内容文字一模一样,采用 simhash 算距离就可以认为这两个网页说的是一件事(我不太确定)。

这方面有一些现成的监控服务,例如:

  1. https://sleepingspider.com/
  2. https://distill.io/
  3. 网页兵 page monitor
  4. 分秒数据

 

我自己也尝试写一个,打算用 webhook 的方式发送通知,用 Celery 固定频率访问网页。然后用自己的想象力写一个判断是否更新的决策。项目进度在这里:https://github.com/laixintao/page-watcher  (最后也可能编程一个坑)

 

Vim缩进有关的设置总结

  1. tabstop : 一个tab等于多少个空格,当 expandtab的情况下,会影响在插入模式下按下<tab>键输入的空格,以及真正的 \t 用多少个空格显示;当在 noexpandtab 的情况下,只会影响 \t 显示多少个空格(因为插入模式下按 <tab> 将会输入一个字符 \t )
  2. expandtab :设为真,在插入模式下按<tab>会插入空格,用>缩进也会用空格空出来;如果设置为假noexpandtab,那么插入模式下按<tab>就是输入\t,用>缩进的结果也是在行前插入\t
  3. softtabstop :按下 <tab> 将补出多少个空格。在 noexpandtab 的状态下,实际补出的是 \t 和空格的组合。所以这个选项非常奇葩,比如此时 tabstop=4 softtabstop=6 ,那么按下 <tab> 将会出现一个 \t 两个空格。
  4. shiftwidth :使用 >> << 或 == 来缩进代码的时候补出的空格数。这个值也会影响 autoindent 自动缩进的值。

Vim的官方文档给出了4种常用的设置:

作为一个 Pythoner ,\t 和空格混用的应该拉出去烧死。所以我推荐的配置是:

然后对于下列文件类型,4个空格太宽了,看起来比较累,可以换成2个空格。

相关参考: https://stackoverflow.com/questions/30408178/indenting-after-newline-following-new-indent-level-vim

 

最近硅谷第五季回归了,不知道大家记得不记得 Hendricks 和女朋友因为tab还是空格吵架的事情,我觉得 Hendricks 是对的啊,如果用 \t ,那么可能不同的IDE对 \t 可以更本地化地对齐一些,但是明显四个SPACE更稳啊,如果混用,到时候你咋看出来空的地方是 \t 哪个地方是SPACE呢。以前碰到很多下载下来代码打开,对齐乱七八糟的情况,简直十恶不赦。

 

Shell单引号、双引号和反引号的区别

每次在shell用到引号的时候,都会因为用单引号还是双引号纠结不已。最近看了《Shell十三问》,终于弄清楚了它们的区别。

反引号

首先,反引号是明显与单双引号有区别的。放到这篇文章里面一起写可能是因为我觉得它的名字里面有引号二字吧。命令行是支持拼接的,而反引号的作用就是“执行反引号内的命令,将结果返回到字符串”。比如在下面的命令中,反引号内的命令就会先执行,得到结果a,然后反引号的内容会被执行结果替换,组合成最终命令cat a,执行。

这样就可以很方便地批量执行命令,例如删除所有docker的容器,可以使用:

这个作用基本等同于$(),上面的命令等价于 cat $(find . -name a) 事实上,$()更好一些,因为它支持嵌套,反引号不支持。

其实这个功能也可以使用管道配合xargs实现(例如上面的打开文件命令,可以用 find . -name a | xargs -I {} cat {} ),但是xargs要更繁琐一些。直接使用反引号这种替换更加直观。而且对于多个命令的结果组合成一条命令来说,反引号要更方便。

单引号和双引号

Shell中有些字符,是不表示字符意义的。比如说,你想 echo 一个 > ,你需要 echo \> 进行转义。

对于字符的意义和转义的意义,我经常搞混:这个字符需要转义之后是特殊意义还是字面意义呢?后来相出了一个窍门:如果一个字符是shell中的meta字符,那么它不表示字面的意义,需要转义之后表示字面的意义。这个窍门也适用于其他需要转义的地方,例如正则表达式的 . ,它因为是特殊字符所以不表示字面的意义 . ,如果你想要匹配一个 . 字符,就需要在写正则的时候加上一个 \ ,来表示它的字面意义。这段对某些朋友来说可能是废话……但是确实是让我纠结了好久的。剩下的就是需要记住环境下的特殊字符了。

So,你肯定不想在命令行写很多很多 \ 来转义,所以就出现了单引号和双引号。其实就类似于Python的字符串前面加 r ,表示引号内的字符全表示字面意义。在shell中,如果字符在引号内,就不会被shell解释成特殊意义。

例如空格表示分隔符(IFS),cat a b的意义是打开文件a和文件b。但是 cat "a b"的意义是打开文件名为 a空格b 的文件。等价于 cat a\ b 。

单引号和双引号的区别都是protect,保护字符串不被shell解释。但是区别就是,双引号不会保护三个字符:反引号字符、反斜杠字符\,以及$

这正好可以方便我们一些操作,比如将命令的结果当做字符串。或者在字符串内引用环境变量的值。下面的例子可以简单地展示它们的区别:

这个地方有个关键,就是你要明白你想要的是一个表示字面意义的字符串,还是一个命令组合。如果你想给一个命令传入参数,例如 awk 或 grep ,那么你的参数一般是字符串;如果你是要在shell上执行一连串的命令,那么可能不需要转义。其实就是区别 shell meta 和 command meta, 这个可以参考文章开头的《Shell 十三问》。

参考资料:

  1. shell metachar
 

副作用还是Feature?

我们的Python应用需要一个全局变量保存一些公用的值,但是不希望其他人随意往里面添加属性,导致这个对象很乱。于是我们是这样定义的:

很多人提到“限制类的属性”就会很自然的想到__slots__,我认为这并不合适。__slots__的初衷是节省对象占用的内存,如果我们的app中某个对象可能有上百万个,就要考虑到将该对象变成__slots__定义的了。禁止赋予对象__slots__声明之外的属性名,这只是节省内存的Feature所带来的一个副作用。它是用来优化程序的,并不是来约束程序员的。

如果这么写,可能带来的缺点有:

  1. __slots__并不会继承,也就是说,如果子类继承了有__slots__的类,子类不会有__slots__存在,你要记住在每一个子类都写上。
  2. __slots__存在之后,该类就不能成为弱引用的目标(具体原因可以看弱引用的原理),除非将__weakref__加入到__slots__中。但是这样做将会污染__slots__变量,其他看到这个东西的时候需要分辨哪些是app的变量,哪些语言需要的变量。
  3. 前面已经说到了,__slots__存在的目标是为了优化存储空间。如果有一天,Python发现可以动态地向对象添加属性而依然节省内存的方法,可能就会破坏我们的程序。换句话说,Python是不会保证未来依然保留“节省内存”所带来的这个副作用的。

我觉得这个地方争取的实现应该是用魔术方法 __setattr__每次赋值都会经过该方法:

是不是更加Pythonic?

另一个例子是Python的dict,在Python3.6中,提到内置的dict遍历的时候会保持插入的顺序,但是又强调这是一个为了节省dict内存而带来的一个副作用,并不是语言设计的标准,不应该被依赖:

The dict type has been reimplemented to use a more compact representation based on a proposal by Raymond Hettinger and similar to the PyPy dict implementation. This resulted in dictionaries using 20% to 25% less memory when compared to Python 3.5.

The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon。

有关是否能使用Python3.6的dict保持插入顺序,这里有个很好的讨论。我也认为依赖这个“副作用”是个严重错误。

  1. 这不是Python语言的标准,不兼容其他解释器
  2. Python并不知道你是否依赖了dict的顺序,如果有错误,Python解释器层面不会报出错误
  3. 鉴于这不是一个语言标准,很可能被碰巧知道这个“实现细节”的人运用了这个“副作用”,但是别人却不知道(不知道实现细节也可以是一个合格Python程序员),未来修改代码可能不会注意其实个地方遍历是要根据插入顺序的,留下了隐患。

Python3.7中,这成为了一个语言特性,所以3.7+我们就可以放心使用啦!

Make it so. “Dict keeps insertion order” is the ruling. Thanks!

Guido van Rossum


 

今天在论坛看到一个有意思的问题:if foobar != None 和 if foobar is not None 是完全等价的吗?

挺有意思,这个问题我又想了一下:为什么大家比较 None 的时候用 is ,但是比价字符串(字符串也会有驻留)却用 == 。我自己的思考是:None是文档写明的全局变量,而字符串的驻留确是一个解释器为了优化而带来的副作用,不能依赖,解释器可能在某个时候决定不再缓存某个字符串,所以这是不可靠的。正好这个问题和本文的主题比较切合,我就贴一下自己的回答:

楼上 @gwki 说的很清楚了!

但是对于 None 来说有一点区别,你看很多 Python 代码就会发现:大部分情况下我们用 if foo is None 来做判断,因为 None 在 Python 中是一个全局唯一变量。官方文档中说:Since None is a singleton, testing for object identity (using == in C) is sufficient. 所以官方是推荐用 id 来 check 的。

即:None 只有一个,不存在值为 None 但是与 id(None) 不相等的情况。

写作 if foo != None 有点不 Pythonic (反正我是没这么见过哈哈哈)。

问题 2:

foo = 0
if foo 判断为假,
if foo is not None 判断为真。所以 is 判断的是 id 相同(对于 None 来说判断 id 相同和判断值相同没有太大区别,反正只有 1 个)。

所以二者是不一样的,除了 None 之外,文档( https://docs.python.org/3.6/library/stdtypes.html#truth-value-testing )还有下面的判断为假:

– constants defined to be false: None and False.
– zero of any numeric type: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
– empty sequences and collections: ”, (), [], {}, set(), range(0)

再啰嗦一点,对于不可变对象,为了避免重复创建,Python 做了驻留处理。比如下面代码:

>>> s1 = “ABC”
>>> s2 = “ABC”
>>> s1 is s2
True

但是我们实际比较二者的时候,应该用 s1 == s2。因为驻留操作是 CPython 的实现细节。副作用不应该被依赖。