为什么要“包含头不包含尾”?

我记得有一次面试的时候,面试官让我实现一个函数,get_num(start, end),要求返回start和end之间的数字。我给出了一个包含了start不包含end的版本。面试官说这是错的,我反驳道,Python内置的range()就是这样的。记忆尤新。最近终于知道了这样做的原因。

首先从实用方面来看,这样做有很多好处。当然,我会拿Python来举例。

  • 当只有最后一个位置信息的时候,我们可以快速看到到底有几个元素。比如range(4)就是4个元素,my_list[:5]是5个元素
  • 当看到开始下标和结束下标的时候,我们一下就可以算出其中有多少个元素,例如my_list[2:11]就是9个元素
  • 我们可以使用下标将序列表示成两部分,不会重叠。例如,my_list[:x]my_list[x:]是既没有重叠也没有遗漏的两个部分

其实这个问题就像下标应该从0开始还是从1开始一样,又像先有鸡还是先有蛋一样,是很容易引起争议的问题。但是Edsger Wybe Dijkstra对这个问题给出了一个无懈可击的答案,看了之后,我觉得包含头不包含尾就应该是天经地义的!

Dijkstra教授(是的就是那个Dijkstra,以下简称教授)曾手写过一份备忘录,题目是Why numbering should start at zero,虽然题目是“为什么下标应该从0开始”,但是教授在备忘录中很可爱又认真地解释了为什么2, 3, ..., 12就应该表示为2 ≤ i < 13,其他的都是错的,都应该绑在十字架上烧死(后半句是我加的)。此备忘录笔记清洗漂亮,pdf可以在 http://www.cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF 看到,文字版可以在 https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html 看到。

表示上面2到12之间的数据,无外乎以下四种:

a) 2 ≤ i < 13

b) 1 < i ≤ 12

c) 2 ≤ i ≤ 12

d) 1 < i < 13

首先,a)和b)有很大优势,因为a)和b)一眼就能看出元素的个数(上文也提到了);而且根据已知的推论,“两个相邻的序列”应该是,前一个序列的上界应该是后一个序列的下界,这样两个序列可以不重叠又相邻。从这两点看,a)和b)都更好。

然后,我们看下界。我们知道,存在最小的自然数,不存在最大的自然数。所以理应让下界应该是小于等于。如果是小于,那么下界就感觉不是从最小的自然数开始的,ugly!所以我们偏爱a)和c)。

最后我们来看上界。我们已经让序列从最小的自然数开始了,如果上界使用小于等于,那么如果序列收缩为空的时候,怎么表示? 2 ≤ i ≤ ? ugly!如果上界使用小于的话,我们就可以正常的表示成 2 ≤ i < 2 (不存在)了。

综上,a)得到4分,以巨大优势胜出。另外,教授提到有些人对理由充分但是没有实际应用的证据的结论感到不舒服,所以教授在REMARK提到,Xerox PARC开发的Mesa编程语言就讨论了这个问题并且强烈建议使用第一种,后三种及其的UGLY的!

最后教授引申除了为什么将0作为下标(好像教授一直在跑题哦)。我们一定确定要使用a)的表示方法,那么表示长度为N的序列如何表示呢?我们有两个选择: 0 ≤ i < N 1 ≤ i < N + 1。教授认为从0开始更好看,因为这样元素的下标就能表示在它前面的元素的个数。教授说,“这个故事告诉我们,最好将0作为一个自然数。(过了十几个世纪人们才意识到!)”

有意思的是,备忘录后面还有一段,讲了讨论这件事的起因。教授的一个数学同事(当然也是个教授,不过不是计算机的)对他的一些学生发飙了!因为数学教授发现一些年轻的搞计算机的竟然从0开始数数!这不是挑衅嘛!

教授很赞同Antony的一句话:在宗教方面,异教徒必须被烧死不是因为异教徒错了,而是因为“异教徒”有可能是正确的。

 I think Antony Jay is right when he states: “In corporate religions as in others, the heretic must be cast out not because of the probability that he is wrong but because of the possibility that he is right.”

参考资料:

  1. 本文实用角度的三个优点来自于Fluent Python一书第一章
  2. 感谢德州大学保存了教授珍贵的手稿
 

奇葩网站吐槽第三弹

本文是吐槽奇葩网站的第三弹,主要是吐槽网站制作者偷懒或者智商低留下的设计缺陷,可能需要一定的web开发知识才能看懂。之前两篇:

  1. 吐槽一些神奇的政府网站
  2. 奇葩网站吐槽第二弹

170814更新:是一个行政处罚的网站,打开一看,网站给处罚人的身份证打了码,嗯,挺好的,没毛病。

但是打开网络一看……

大哥好歹打码用心一点啊……


20170908更新:一般来说,爬虫都是通过列表页拿到详情页的url,然后主要是从详情页抓数据的。

今天遇到一个列表页的url超级复杂的,根本不知道这些参数怎么得到的。而且请求时不时地返回500(这说明网站开发者也不知道这些参数怎么来的)。网页虽然不是单页应用但写的很烂,嵌套的frame。

就在要放弃的时候,我打开详情页看了一眼……详情页url长这个样子……

这个pkid不就是“primary key id”吗?于是改成1试了一下,果然有数据。大兄弟你列表页的反爬白做了,我直接从pkid=1抓到33w就可以了。


171011更新:今天抓的一个网站上有验证码,不过这个是可以通过机器学习训练模型解决的。有个同事专门负责这个,于是我把这个验证码地址告诉他。奇怪的是,下午继续研究这个网站的时候打不开了…… 后来同事告诉我,他下载验证码太快(4000个),网站挂掉了……

弱不禁风啊。


171024更新:今天遇到一个网站,在meta里面写上了页面的所有内容……大哥您这SEO强势!


20172025更新:复制粘贴编程……

 

参加中国pycon2017

今天一天都在浦软大厦听pycon2017的演讲。一天听下来,觉得还是有收获的,演讲质量好坏不一,挑几个我觉得不错的分享一下。

质量比较好的两个长演讲,侯凯老师,分享了爱因互动的搭建对话机器人,介绍了他们公司的架构,问答机器人的基本功能、难点等。包括后端的架构,消息如何路由,如何统一不同平台消息的格式。消息服务主要有消息通道(缩短消息的传输耗时,使用Amazon SQS等)和消息路由(基于消息内容的路由器)。对于IO密集型的任务使用异步Asyncio。但是需要显式的协程切换,对代码的入侵性大(这个之前也有人吐槽,asyncio很有传染性,一不留神整个项目都是异步代码了。另一种方案是Gevents,对代码的侵入性小,可以屏蔽细节,但是缺点是对代码细节的掌控就没有了。

其余的演讲内容是灵活性以及部署上线。这部分我笔记不详细,就不说了。

最后侯凯老师分享了向Python3的迁移的一些经验,鼓励使用Python3.吐槽一下,这竟然是一整天唯一一次听到python3的地方……

达达的廖瑞奇,介绍了达达平台的搭建。达达去年的演讲质量也很高。内容也挺有意思的,主要分了三个部分,包括动态定价,时间预估,路径规划。

动态定价主要是因为供需不平衡,目前美团、滴滴、饿了么等众包平台都有动态定价,目的是为了(优化目标)提高接单率,缩短接单时间。影响一个订单的因素有空间(空间热力图)、时间、天气等,另外订单的因素比如距离、重量、楼层等也会影响供需。

路径规划这个很有意思,是一个NP-hard问题。规划一个路径需要满足1.必须先取件再送件;2.在满足1的情况下总路径越短越好。主要用了遗传算法(启发式算法,比较复杂,解决方案没有听懂……)。

订单指派提到了一个技术,基于地理位置的hash:GEO-hash。

其他的王剑锋老师讲了GOODERP开发历程,这是一个开源的ERP。感觉此人有很深的执念啊,多次被别人劝退(不要开源了)还是走到了现在,佩服佩服。

Ethereum on Python演讲感觉比较乱,有时将区块链原理有时将他们自己的技术,没怎么听明白。感觉如果不明白区块链听了这个演讲也是白听,明白了更是白听。

华为有一个快速演讲,分享了一套能分析Python性能的工具,不过还没开源,听说明年春天能开源。现场还来了CPython唯一的中国core开发者,可惜没有演讲。

其他没啥好说的了。都是一些tensorflow的我也听不懂。pycon被人吐槽了不是一次两次了,主题跟python都没什么关系,哎。

另外本次我唯一想吐槽的就是七牛的姚唐仁,主题是《富媒体机器学习平台搭建》。演讲混乱,语无伦次。一直在重复为什么要机器学习,为什么要用多媒体(富媒体忽悠人的吧),人工审核不过来啊,他能展开说成:如果人工审核,需要多少多少时间,我们有多少数据,用机器学习怎么样,加上用人工…… 巴拉巴拉……。完全没干货!根本没用心准备,就是靠着七牛大厂来吹牛来了。

贴几张活动图片,主办素质还是可以的,真心希望国内的python发展好,那些目的不纯(不是本着分享技术)的演讲还是别来了。

华为大牛 定制CPython解释器

抽奖ps4

茶歇蛋糕

志愿者合影

全体参会人员和嘉宾合影

 

爬虫常用的工具

前面写过一篇《如何成为一名爬虫工程师》,这篇博文介绍一些写爬虫的神器。比如查看网络请求,模拟网络请求等。本文不会涉及到编程工具例如vim,tmux等(虽然这些工具我也极力推荐,应该人手一份)。

curl

cURL

curl是在命令行用url语法传输文本的工具。用curl可以模拟任何从浏览器发出的请求,并且是以纯文本的形式,所以任何header、cookies都一目了然。有时候你碰到“为什么在浏览器请求能成功,在代码中就不行”的问题时,用curl验证一下是最靠谱的办法。

curl的用法非常简单,curl + "url"就可以向一个url发送请求,显示服务器响应。-H "header"可以给请求加上headers。-i参数可以打印出响应头,大写的-I只打印响应头。-L参数开启301,302跳转等。学习会这些基本就可以上手了,希望详细的了解curl可以阅读《everything curl》这本书(免费书籍:PDF在线阅读

一个请求一般带有很多headers,一个一个输入太费劲了,型号,我们可以使用chrome的copy as curl功能。

此命令会将一个网络请求转换成curl放到剪切板,直接在命令行粘贴就可以了。

对了,写爬虫chrome是最大的神器,推荐阅读酷壳的《CHROME开发者工具的小技巧》

但是如此长的一个命令在命令行上修改起来特别麻烦(要让光标跳来跳去的),所以再推荐一个GUI的模拟发送请求的工具postman

postman

postman可以让你像在chrome中修改css那样,修改请求的header,cookies等任何东西。除了全平台的app之外,还有chrome app可以用。可以将请求保存下来,下次继续工作的时候可以使用。当然postman也支持从curl粘贴导入,这样我们可以把任意chrome的请求导入到postman。不过地方比较坑,我第一次花了很长时间才找到,在 import > Paste Raw Text (竟然不是Paste cURL)。这里提醒一下,我有一次用postman的请求怎么也不对,但是用curl直接在终端就对了,这说明postman有一些bug,不能完全模拟请求。同事推荐了paw,还没用过,下次用过再谈(好像是mac专用的)。

curl to requests

这是一个在线的工具,可以将curl命令转换成python的requests库代码写成的命令(还支持node和PHP)。这样就省去了很多手工排格式的麻烦操作(其实可以做成vim插件的讲)。

python -m “json.tool”

这是python自带的json库,不用任何安装,直接用echo str | python -m 'json.tool'就可以在终端格式化json(之前我一直傻傻的粘贴到vscode里面……)

可以配合curl使用,直接让返回的json可读。

如果你使用Vim,用Vim打开一个file.json文件,可以直接在buffer中输入:%!python -m 'json.too'来将当前buffer的json内容格式化。非常有用。

EditThisCookie

顾名思义,这是用来修改cookies的。如果你要模拟登陆,可以尝试一个一个删掉cookie看哪一个是起作用的。

Proxifier

写爬虫被封ip是很正常的事情。被封了就得挂代理去访问目标网站。浏览器可以用switch Omega,终端可以用proxychains。但还是有些不方便。p4又有一些bug,并不是所有终端程序都可以走代理的。

这个时候就可以使用proxifier自定义流量规则,甚至可以全局转发到代理,一个都跑不了,非常稳!

暂时就想起来这个多,以后有别的还会在这篇博客更新。

 

介绍Python2和Python3的兼容库six

Python2和Python3有很大的不同,six这个库为此提供了一个兼容的方案。使用six写的代码可以不用修改就运行在Python2或Python3上。注意这个six并不是让Python2写的代码兼容Python3,如果你需要迁移工具,那么你要找的是2to3。如果你的代码需要同时需要运行在Python2和Python3上,那么你就需要six!

原理其实很简单,six对Python2和Python3的名字等做了统一,比如Python2的字符串叫str,Python3叫unicode,那么就可以使用six.text_type。如果运行在Python2上,six.text_type就是unicode,如果运行在Python3上,six.text_type就是str

six库只有一个文件:six.py。这样做可以方便地拷贝到你的项目中。也可以使用pip安装。

(为什么叫six? 因为2 * 3 = 6。为什么不是+,因为*更牛逼)

下面讲一下six有哪些功能。

统一了Python2和Python3的类型

如上文提到的,使用six中的类型,能同时在Python2和Python3中正确运行,就不用自己在写代码的时候自己判断到底是Python2的unicode还是Python3的str了!

Python2:

Python3

(由于iPython不再支持Python2,所以博主用的REPL for python2是默认的,但是在这里不影响结果)

内部对象属性的重命名

Python3修改了一些解释器内部属性的名字,例如Python2的dictionary.iterlists()在Python3中变成了dictionary.lists(),(讲真我觉得Python3的名字改得好!)。

使用six可以同时兼容这两种名字,但是需要将调用的格式改为 six.iterlists(dictionary, **kwargs)

全部的名字兼容可以参考文档。(话说我不太明白为什么保留的是Python2的名字,不用Python3的)

移动模块的位置

Python3重新组织了很多模块的位置,例如Python2的HTMLParser,在Python3中是html.parser。

我们可以使用six导入:

其他

其他的内容可以在官方的文档找到,基本上就是通过six来调用,而不是自己对Python判断。包括:

  • 提供了二进制和文本数据的兼容
  • uniittest assert的兼容
  • urllib库改动的兼容
  • 高级的自定义move

参考

https://pythonhosted.org/six