Follower me on GitHub

前言

之前也发布一篇招聘贴. 这里只说一些不一样的. 不一样的是现在我已经开始负责条目组的技术. 在这里我主要写给想来条目组的同学.

为了让想来豆瓣的人对我们组有个了解. 我还是先说一下我们组的情况.

负责的产品线

东西, 读书, 电影, 音乐 以及条目, 条目就是www.douban.com/subject/xxx这样的地址. 目前主要包含游戏和移动应用, 正在扩展更多玩法和种类.

工作范围

组内的同事都是前后端都可以, 只有一些比较大的前端需求需要前端部门的同事来协作. 目前就是人少事多. 组内招聘严格, 本次豆瓣实习生招聘挖掘没有合适的人

这个组不太一样的地方

  1. 上面说的本组维护了多个知名的产品线. 你也可以看到非常多的知名id的代码.
  2. 本组是全豆瓣对代码要求最严格的组, 强制要求flake8. 且有很多自动化的工具帮你改正 - 比如你的PR不符合flake8, 那么CODE上对应位置就会出现对应规范的评论(创意源自quora的qlint)
  3. 本组的code review非常严格, 我们只欢迎最正确, 最有效率的方式写代码. 尤其对职业初期/实习生来说是一个非常好的机会. 我们可以帮你养成非常多的代码习惯的python开发能力.
  4. 我们欢迎使用新技术, 在时间容许的前提下, 鼓励使用更潮流的工具/库 - 这也是对我们组内成员最大的压力. 我们保证你会一直进步,而不是来豆瓣养老
  5. 我们组准备在豆瓣内率先支持es6

负责技术的我

  1. 我参与很多开源项目, 因为之前是运维开发, 所以主要领域都在devops上. 通用的知名的项目包括ipython/pip, 我也给python标准库贡献过代码
  2. 我个人在做一个python的社区 firefly. 未来会一直坚持下去
  3. 我接手了CODE(2014 pycon第一个议题)在豆瓣内部的迭代, 以及开源计划. 我的目的就是让豆瓣内部和开源项目的版本一致
  4. 个人其他信息: 使用emacs, 曾经看<黑客与画家>后用commonlisp写了第一个网站,从此决定做产品开发. 目前在用react做一些事情

联系我

qq: 61966225 mail: ciici123#gmail.com, dongweiming#douban.com

前言

学common lisp(以下除非特殊需要说明的都简称lisp)以及用emacs的人都有一个体会 - lisp无所不能, 可以使用lisp修改lisp的行为. 什么意思呢?

我来举个例子. 我希望重置+的行为为实际意义的减法-. 看起来这是语言不可能完成的任务, 对lisp来说很简洁(我使用sbcl):

* (+ 1 1)

2  ; 正确结果
* (shadow '+)

T
* (defgeneric + (a &rest b))

#<STANDARD-GENERIC-FUNCTION + (0)>
* (defmethod + ((a number) &rest b) (apply 'cl:- a b))

#<STANDARD-METHOD + (NUMBER) {1002E43E73}>
* (+ 1 1)

0  ; 这里的加号的意义其实是我们所理解的`减号`

是不是很神奇?

那么对于python这种高级语言能不能做到呢? 答案是肯定的. 我们马上就来实现它

In [1]: import ast

In [2]: x = ast.parse('1 + 1', mode='eval')

In [3]: x.body.op = ast.Sub()

In [4]: eval(compile(x, '<string>', 'eval'))
Out[4]: 0

我想大家开始明白AST有多大能量了吧?

AST的故事

AST中文叫做抽象语法树, 也就是分析当前版本的python代码的语法, 用一种树的结构解析出来. 这个模块提供给我们一个在编译代码之前, 用python语言本身去修改.

它的作者是Armin Ronacher. 如果你听过或者觉得似曾相识, 对. 他就是mitsuhiko - flask的作者. 也是pocoo的leader之一(另外一个是看起来不知名的birkenfeld - 对我来说他很有名).

那么AST有什么意义呢? 但是有绝大多数人其实不了解也用不到这个模块, 为什么呢?

  1. 出现需要对代码默认行为做更改的场景很少
  2. 它主要用来做静态文件的检查, 比如pylint, pychecker,以及写flake8插件. 而我们平时的写代码都是在运行不需要进行预先的语法检查之类, 那么实际接触它就很难得了.

一些文章的索引

为了对本文有更深的理解可以看看以下文章

AST 模块:用 Python 修改 Python 代码这里对流程说的很好了. 可以直接读一下

模块代码也写得非常精炼, 可能不直接让你明白, 那么这时候可以看看

Abstract Syntax Trees, 这个时候我再强调一下作者吧, takluyver是ipython的核心开发成员, 他也参与了很多我们常用的开源项目, 比如pexpect和pandas

上面的2篇文章写了很多, 既有理解, 也有一些初级的用法.

我个人用它的例子

最近做的slack-alert. 先说它和AST的关系:

  1. 我没有使用注册或者import的方式,而是直接去遍历文件, 找到符合我要求的函数当做一个任务需要执行的任务
  2. 任务就要设置间隔, 那么会加某种格式的装饰器, 装饰器的参数就是间隔类型, 比如@deco(seconds=10)表示没十秒跑一次的意思
  3. 我这样就可以放心的写plugin就好了, 我只关注任务本身的逻辑. 而这个装饰器(类似上面说的@deco), 它其实是不存在
  4. 这个特殊格式的装饰器本身不存在没有关系, 因为我不会直接运行代码, 我只是把代码通过AST的处理, 解析出我要的任务和任务的执行间隔. 再去编译代码.

上代码:

class GetJobs(ast.NodeTransformer):

    def __init__(self):  # 原来的ast.NodeTransformer其实没有__init__方法
        self.jobs = []

    def get_jobs(self):  # 一个方便的获得任务的方法
        return self.jobs

    def get_job_args(self, decorator):  # 这属于解析装饰器这个结构, 拿到执行的间隔
        return {k.arg: k.value.n for k in decorator.keywords
                if k.arg in ('hours', 'seconds', 'minutes', 'days')
                and isinstance(k.value, ast.Num)}

    def visit_FunctionDef(self, node):  # 这个visit_xxx的方法被重载的时候, 就会对这个类型的语法加一些特殊处理. 因为我设计的时候只有函数才有可能是任务
        decorator_list = node.decorator_list  # 或者一个函数的装饰器列表
        if not decorator_list:
            return node  # 没有装饰器明显不是我想要的任务, 可能只是一个helper函数而已
        decorator = decorator_list[0]  # 这里我把最外面的装饰器取出来看看是不是符合我要的格式
        args = self.get_job_args(decorator)
        if args:  # 当获得了适合的参数, 那么正确这个格式是正确的
            node.decorator_list = decorator_list[1:] # 最外面的装饰器就是语法hack, 它不存在也没有意义,以后完成历史任务 去掉之
            self.jobs.append((node.name, args))
        return node


def find_jobs(path):
    jobs = []
    for root, dirs, files in os.walk(path):
        for name in files:
            file = os.path.join(root, name)
            if not file.endswith('.py'):
                continue
            with open(file) as f:
                expr_ast = ast.parse(f.read())  # 读文件, 解析
                transformer = GetJobs()
                sandbox = {}  # 其实就是把执行放在一个命名空间里面, 因为最后我还是会把任务编译执行的, 我在这里面存了执行后的环境
                exec(compile(transformer.visit(expr_ast),
                             '<string>', 'exec'), sandbox)
                jobs.extend([(sandbox[j], kw) for j, kw in transformer.jobs])
    return jobs

其实看起来不能完成的事情, 就是这么简单.

前言

为什么要做slack-alert呢? 成因其实是我一直想做的AST相关的一个项目. 正好是一个机会. 其次也是业务需要:

工程师都比较懒, 在每天的各种问题邮件几百几千封的时候会经常遗漏重要的邮件. 会经常被用户以及公司其他同事发现问题时@到时候才发现. 现在大家也接受了slack这个工具. 那么我就在想何不把更重要的消息发送到slack里呢.

原来也做过, 就是写一些脚本, 会把运行的结果通过slacker里面封装的postMessage发消息进来. 先说一下为什么我不用Incoming-webhooks. 最主要的, 因为通常slack都是用的免费版, 我担心对webhooks会有限制或者未来会有限制.

但是为什么做这个微框架呢? 一则这样的脚本和任务会越来越多, 脚本需要设置的定时任务的间隔也会各不相同. 用系统自带的crontab, 每次新增修改删除都要去做调整. 代码还需要做抽象; 用celery, rq这些其实一是很浪费, 二也不怎么应景. 所以我写了这个工具.

优点和特点

  1. 只支持python3
  2. 支持多种调度模式: AsyncIOScheduler, BackgroundScheduler, GeventScheduler, TornadoScheduler. 默认是AsyncIO
  3. 使用AST, 而没有直接import代码.
  4. 代码和项目无关, 没有register. 只需要按格式编写plugin脚本即可, 要被执行的任务加一个装饰器(只需要他是一个装饰器, 不需要存在). 指定运行的间隔就好
  5. 附带常用的配置

安装

pip install slack-alert

slack-alert有个默认的配置slack_alert.conf. 使用OS X或者linux可以拷贝到 ~/.config/slack_alert.conf

编写插件

PS: 插件目录默认是当前目录的plugins子目录下.

tree plugins
plugins
├── examples.py

0 directories, 1 files
# coding=utf-8
import os


@deco(seconds=2)  # 这个一个正确格式的任务, 因为装饰器里面包含`hours|seconds|minutes|days`这样的键.
def a():
    print(1)  # 插件也需要python3的语法
    print(os)  # 可以使用外部的变量
    return 1  # 这个返回值最后会被发送到slack的channel里面


def b():  # 这个函数因为没有对应的格式的装饰器 他不会被当做一个任务
    print(3)
    return 2


@deco2(xx=1)
@deco(minutes=2, seconds=30)
def c():  # 这个函数也不是一个正确格式的装饰器. 我需要把`deco`放在最外面
    print(4)
    return 3

PS: 大家不要迷惑为什么deco这个函数没有介绍. 因为我只需要这个代码文本. 我其实不直接运行这个脚本. 所以不需要装饰器存在.

使用

slack-alert

这样就启动了.

a这个函数就会每隔2秒运行一次, 运行三次后, 停顿60分钟… 这样循环(高级用法看下面的参数). 并且把结果发送到slack里面

原理

  1. 启动slack-alert 我首先加载配置.
  2. 我会根据配置--path去找我应该去哪个文件目录下(默认是当前目录)的plugins子目录下去找符合的任务程序.
  3. 加载符合的目录下的每个python文件. 生成一个AST(抽象语法树).
  4. 遍历这棵树. 找到符合我需要的语法的函数. 去掉这个实际不存在的装饰器. 编译源码并且执行. 生成一个命名空间
  5. 获取这个命名空间的参数, 通过apscheduler. 添加一个任务
  6. 添加全部任务. 再添加一个监控这些任务的任务(主要是用来在某些时机停掉/启动其他某些任务)
  7. 启动任务调度. 直到Ctrl+C

高级选项

可以参照源码的选项:

  1. Config file”s Options
  2. Command Options

前言

Slack是一个最近一年兴起的团队沟通协作平台. 程序员对工具其实是最有品位和要求的,相信国内很多公司和团队都在使用,豆瓣也算比较早在用. 相信用过的人会理解本文. 如果你还没有接触它, 尤其是对现有的团队沟通协作工具有些不满, 可以试一下.

本文就是介绍我们在最近做出来的一个bot - slack_bot. 它的口号是: 立志成为一个可被调戏且有用的Bot

为什么

无论是运维还是开发, 某些时间都会收到一些邮件/提醒, 可能是固定的时间的定时任务跑出来的结果, 也可能是某个故障引起的问题邮件. 我们都很烦手头有好几个东西来接收这些消息, 而slack可以使用某些方法让这些问题集中起来.

比如很常用的, 使用chat或者incoming-webhooks接收消息. 做什么呢? 我可以把一些昨天的数据汇总或者汇总的地址在今天早上发到某个channel下, 这样想要看这个数据的人直接点进去就好了. 当然啦, 也可以直接发消息到某个人. 一个具体的例子: 豆瓣的同事都知道, 我们的测试环境时间过期就会被删除, 在某段时间里面每天都有很多人在关注我们要新上的一个测试的地址, 但是经常被同事@, 说地址怎么找不到了, 那么我就要抽出精力再去创建一个. 后来一想, 索性跑个定时任务, 拉最新的代码, 走逻辑, 生成一个测试环境. 每天都会在channel里面发消息给对应关心这个消息的人. 一段时间以后, 我们的工作完成了, 同事都有点不适用, 哎, 怎么早上看不到那个消息了…

但是今天说的是什么呢? 我们的slack_bot主要借用outgoing-webhooks. incoming表示把消息推到slack里面, outgoing其实就是当有人在某些符合的channel里面说了什么, 会跳到我们提供的回调地址, 根据内容返回对应符合的回复. 大家是不是有点懂了, 这就是小黄鸡的原理.

说到这里大家应该明白了. 其实slack给我们提供了很大的想象和实现的空间. 我立刻想起来做运维时候做的一些事情:

比如当时有时候要去机房, 路上没有网(2g上网太慢), 有时候想看个监控状态, 切个负载均衡, 重启个服务什么的, 后来做了一个东西, 就是给某些号码发某些特殊的短信内容就可以获得想要的结果, 执行想做的事情. 其实slack_bot只是一些爬虫的集合, 一些功能实现的例子. 在实际开发和工作中可以做很多更有意思的, 公司内部的插件. 和公司的业务相关, 和自己做的事情相关. 这样在路上就能用. 比如我的地图功能, 我可以在网络不好的时候找到行程的最好路线; 我用公交插件可以实时的看我要等的公交需要多久到达, 我可以踩着点去站牌….

小黄鸡原理

小黄鸡很火的时候, 我还是一个perl运维…

小黄鸡其实调用的是simsim的接口. 大家都可以去注册一个key来玩,也可以用项目自带的.

其实就是post一个数据到simsim的api上, 他会返回你对应的小黄鸡的回复.

项目介绍

很多人听过甚至曾经用过小黄鸡. 你可以把本bot理解为增加了很多 功能, 只保留小黄鸡交流的基本逻辑, 用于slackchannel里面小黄鸡

项目使用flask, 目前本项目是一个heroku应用. 地址是 https://slack-bot2.herokuapp.com/slack_callback 但是你 也可以直接裸跑

gunicorn wsgi:app localhost:5000 --timeout 240 --log-file -

你也可以使用heroku创建一个新的应用, 直接用我们的项目代码就可以.

我们使用slack的特性

  1. 可以把消息私聊发给自己
  2. 可以使用带图的方式接收结果(支持canvas, 以下会看到例图)

继承自小黄鸡的模块们, 但是都做了对应的修改:

  1. 空气插件
  2. wikipedia
  3. 地震了
  4. 糗百
  5. orz # orz需要自定义emoji

新加的模块们:

地图插件 - 你可以输入我想从哪去哪, 会给你个路线(步行/开车/公交)

天气插件 - 使用了百度api, 获得城市的天气情况

北京公交插件 - 可以查询北京公交线路, 获取实时的到某站的信息(有些线路站点不准)

美食插件 - 使用大众点评网api, 获取附近餐饮信息, 评分, 电话地址,距离等等

技术活动插件 - 从segmentfault/csdn/车库咖啡/活动行获得最近的活动列表

Github_issue插件 - 获得你个人或者组织下未处理的Pull requests列表

电影信息插件 - 列出最近上映和即将上映的电影信息

pycoders插件 - 获得订阅数据

pythonweekly插件 - 获得订阅数据

头条插件 - 获得今日头条新闻

travel插件 - 旅游推荐/景点介绍

v2ex feed插件 - 获得一些节点的最新feed

help插件 - 列出所有插件的帮助信息

最主要的是小黄鸡(simsim)

也可以直接发到个人的slackbot channel里:

配置slack

去你的slack组织的services页面. 比如我们是 https://pythoncn.slack.com/services/new. 找到Outgoing WebHooks:

点击Add+并确认到新建页面:

channel 可以选择any,也可以指定单独一个. Trigger Word(s) 可以不填, 那就是所有的记录都会走. 也可以找关键词/字, 用逗号隔开. url就是你控制的回调的地址. 本图中的是我们用的herokuapp地址: https://slack-bot2.herokuapp.com/slack_callback

你还可以自定义icon的图标和名字:

实现原理

我还是抄袭了小黄鸡的插件思想. 但是完全脱离和小黄鸡的代码关系. 只留下了几个原有的插件并更新. plugins目录下有一些插件. 系统会按照plugins/__init__.py里面的__all__的列表顺序, 挨个插件对比是否符合. 符合即停止. 都不符合最后会使用simsim模块

具体的插件编写可以参看项目的插件编写

一些trick

  1. 假如你没有设置Trigger Word. 你要保证你的回复可以被停止, 否者你的回复会被当成下一次的输入. 这样就死循环了. 比如我们使用一个函数, 只要文本用!开头就不会回调. 表示这个是一个正常的,不想被out-going的记录.
  2. 这里的天气图片是一个html5的data image. 还有豆瓣电影的海报图片做了防盗链, 不能被slack识别和正常显示. 我的用法是upload到slack. 获得返回的url.然后在生成附件.
  3. direct message其实是被限定了post的长度. 太长的话会报414. 需要对长数据切分. slack_bot已经实现了
  4. 现在simsim的借口被玩坏了, 有些低级,无聊,带有攻击性的回复. 酌情使用

前言

一次QQ群随意一说,得到大家的认同. 所有我想把这件事坚持下去, 做完.

具体的可以看 http://python-cn.org. 在社区没有上线前, 保持低调,欢迎你来参观.

社区项目

firefly. 欢迎star, 欢迎PR - 其实我要star没用, 但是我希望你能来给我提个Pull request.

我来做广告

豆瓣是国内python应用最广泛的公司. 没有其二. 我们条目组随着业务变更, 也需要更多地优秀工程师的加入. 条目是指什么? 豆瓣的电影/读书/音乐, 以及豆瓣东西都是我们在维护和迭代开发(对, 你没有听错, 你不是在一个产品线).

我先列一下作为一个厂公在技术层面, 我对豆瓣的感想(其他层面大家都懂):

  1. 豆瓣有很多知名的工程师, 包括但是不限于前后端以及IOS/Android, 你会他们协作, 吐槽他们的代码以及被他们吐槽
  2. 豆瓣多年积累了很多对工作有帮助的工具. 比如CODE, dae, 还有很多的用于运维支持, 数据分析和统计, 爬虫等项目.
  3. 豆瓣有很多开源项目, 被其他公司使用, 比如dpark, OzJS. 你有机会参与到这样的项目中

招聘内容

简单地需求都在这里: 产品开发工程师

我来解读下(以下方面能帮你提高面试通过的几率):

  1. 豆瓣希望你是一个倾向于全栈工程师. 比如你会python, 还能写前端, 或者有移动开发经验. 你可以想到, 我们大多是同时支持多个 产品线, 任务重的时候如果你独当一面很好, 要是可以再别的领域帮个忙就更好了
  2. 假如你python初学,但是很熟悉其他语言, 我们相信你很快会上手, 不要害怕. 我组之前就有一个原来是ruby的实习生.
  3. 希望你能有个github/stackoverflow账号, 或者个人博客. 尤其是能看到你的作品, 了解你的代码, 希望看到你对代码的热爱. 我相信你有主动的态度会走的更远
  4. 如果你曾经是某个/些开源项目的贡献者甚至是多次贡献者, 或者你曾经做过分享, 也请记得一并告诉我们.

联系我们

想想还是先联系我吧. 我会给大家转发到组内. 或者直接在线申请

我的邮箱是: ciici123#gmail.com. 请在标题中注明[豆瓣求职]

前言

我经常看到很多程序员, 运维在代码搜索上使用ack, 甚至ag(the_silver_searcher ), 而我工作中95%都是用grep,剩下的是ag. 我觉得很有必要聊一聊这个话题. 我以前也是一个运维, 我当时也希望找到最好的最快的工具用在工作的方方面面. 但是我很好奇为什么ag和ack没有作为linux发行版的内置部分. 内置的一直是grep. 我当初的理解是受各种开源协议的限制, 或者发行版的boss个人喜好. 后来我就做了实验, 研究了下他们到底谁快. 当时的做法也无非跑几个真实地线上log看看用时. 然后我也有了我的一个认识: 大部分时候用grep也无妨, 日志很大的时候用ag.

ack原来的域名是betterthangrep.com, 现在是beyondgrep.com. 好吧. 其实我理解使用ack的同学, 也理解ack产生的原因. 这里就有个故事.

最开始我做运维使用shell, 经常做一些分析日志的工作. 那时候经常写比较复杂的shell代码实现一些特定的需求. 后来来了一位会perl的同学. 原来我写shell做一个事情, 写了20多行shell代码, 跑一次大概5分钟, 这位同学来了用perl改写, 4行, 一分钟就能跑完. 亮瞎我们的眼, 从那时候开始, 我就觉得需要学perl,以至于后来的python.

perl是天生用来文本解析的语言, ack的效率确实很高. 我想着可能是大家认为ack要更快更合适的理由吧. 其实这件事要看场景. 我为什么还用比较’土’的grep呢? 看一下这篇文章, 希望给大家点启示

实验条件

PS: 严重声明, 本实验经个人实践, 我尽量做到合理. 大家看完觉得有异议可以试着其他的角度来做. 并和我讨论.

  • 我使用了公司的一台开发机(gentoo)

  • 我测试了纯英文和汉语2种, 汉语使用了结巴分词的字典, 英语使用了miscfiles中提供的词典

# 假如你是ubuntu: sudo apt-get install miscfiles
wget https://raw.githubusercontent.com/fxsjy/jieba/master/extra_dict/dict.txt.big

实验前的准备

我会分成英语和汉语2种文件, 文件大小为1MB, 10MB, 100MB, 500MB, 1GB, 5GB. 没有更多是我觉得在实际业务里面不会单个日志文件过大的. 也就没有必要测试了(就算有, 可以看下面结果的趋势)

cat make_words.py
# coding=utf-8

import os
import random
from cStringIO import StringIO

EN_WORD_FILE = '/usr/share/dict/words'
CN_WORD_FILE = 'dict.txt.big'
with open(EN_WORD_FILE) as f:
    EN_DATA = f.readlines()
with open(CN_WORD_FILE) as f:
    CN_DATA = f.readlines()
MB = pow(1024, 2)
SIZE_LIST = [1, 10, 100, 500, 1024, 1024 * 5]
EN_RESULT_FORMAT = 'text_{0}_en_MB.txt'
CN_RESULT_FORMAT = 'text_{0}_cn_MB.txt'


def write_data(f, size, data, cn=False):
    total_size = 0
    while 1:
        s = StringIO()
        for x in range(10000):
            cho = random.choice(data)
            cho = cho.split()[0] if cn else cho.strip()
            s.write(cho)
        s.seek(0, os.SEEK_END)
        total_size += s.tell()
        contents = s.getvalue()
        f.write(contents + '\n')
        if total_size > size:
            break
    f.close()


for index, size in enumerate([
        MB,
        MB * 10,
        MB * 100,
        MB * 500,
        MB * 1024,
        MB * 1024 * 5]):
    size_name = SIZE_LIST[index]
    en_f = open(EN_RESULT_FORMAT.format(size_name), 'a+')
    cn_f = open(CN_RESULT_FORMAT.format(size_name), 'a+')
    write_data(en_f, size, EN_DATA)
    write_data(cn_f, size, CN_DATA, True)

好吧, 效率比较低是吧? 我自己没有vps, 公司服务器我不能没事把全部内核的cpu都占满(不是运维好几年了). 假如你不介意htop的多核cpu飘红, 可以这样,耗时就是各文件生成的时间短板:

 # coding=utf-8

import os
import random
import multiprocessing
from cStringIO import StringIO

EN_WORD_FILE = '/usr/share/dict/words'
CN_WORD_FILE = 'dict.txt.big'
with open(EN_WORD_FILE) as f:
    EN_DATA = f.readlines()
with open(CN_WORD_FILE) as f:
    CN_DATA = f.readlines()
MB = pow(1024, 2)
SIZE_LIST = [1, 10, 100, 500, 1024, 1024 * 5]
EN_RESULT_FORMAT = 'text_{0}_en_MB.txt'
CN_RESULT_FORMAT = 'text_{0}_cn_MB.txt'

inputs = []

def map_func(args):
    def write_data(f, size, data, cn=False):
        f = open(f, 'a+')
        total_size = 0
        while 1:
            s = StringIO()
            for x in range(10000):
                cho = random.choice(data)
                cho = cho.split()[0] if cn else cho.strip()
                s.write(cho)
            s.seek(0, os.SEEK_END)
            total_size += s.tell()
            contents = s.getvalue()
            f.write(contents + '\n')
            if total_size > size:
                break
        f.close()

    _f, size, data, cn = args
    write_data(_f, size, data, cn)


for index, size in enumerate([
        MB,
        MB * 10,
        MB * 100,
        MB * 500,
        MB * 1024,
        MB * 1024 * 5]):
    size_name = SIZE_LIST[index]
    inputs.append((EN_RESULT_FORMAT.format(size_name), size, EN_DATA, False))
    inputs.append((CN_RESULT_FORMAT.format(size_name), size, CN_DATA, True))

pool = multiprocessing.Pool()
pool.map(map_func, inputs, chunksize=1)

等待一段时间后,目录下是这样的:

$ls -lh
total 14G
-rw-rw-r-- 1 vagrant vagrant 2.2K Mar 14 05:25 benchmarks.ipynb
-rw-rw-r-- 1 vagrant vagrant 8.2M Mar 12 15:43 dict.txt.big
-rw-rw-r-- 1 vagrant vagrant 1.2K Mar 12 15:46 make_words.py
-rw-rw-r-- 1 vagrant vagrant 101M Mar 12 15:47 text_100_cn_MB.txt
-rw-rw-r-- 1 vagrant vagrant 101M Mar 12 15:47 text_100_en_MB.txt
-rw-rw-r-- 1 vagrant vagrant 1.1G Mar 12 15:54 text_1024_cn_MB.txt
-rw-rw-r-- 1 vagrant vagrant 1.1G Mar 12 15:51 text_1024_en_MB.txt
-rw-rw-r-- 1 vagrant vagrant  11M Mar 12 15:47 text_10_cn_MB.txt
-rw-rw-r-- 1 vagrant vagrant  11M Mar 12 15:47 text_10_en_MB.txt
-rw-rw-r-- 1 vagrant vagrant 1.1M Mar 12 15:47 text_1_cn_MB.txt
-rw-rw-r-- 1 vagrant vagrant 1.1M Mar 12 15:47 text_1_en_MB.txt
-rw-rw-r-- 1 vagrant vagrant 501M Mar 12 15:49 text_500_cn_MB.txt
-rw-rw-r-- 1 vagrant vagrant 501M Mar 12 15:48 text_500_en_MB.txt
-rw-rw-r-- 1 vagrant vagrant 5.1G Mar 12 16:16 text_5120_cn_MB.txt
-rw-rw-r-- 1 vagrant vagrant 5.1G Mar 12 16:04 text_5120_en_MB.txt

确认版本

  test  ack --version # ack在ubuntu下叫`ack-grep`
ack 2.12
Running under Perl 5.16.3 at /usr/bin/perl

Copyright 2005-2013 Andy Lester.

This program is free software.  You may modify or distribute it
under the terms of the Artistic License v2.0.
  test  ag --version
ag version 0.21.0
  test  grep --version
grep (GNU grep) 2.14
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Mike Haertel and others, see <http://git.sv.gnu.org/cgit/grep.git/tree/AUTHORS>.

实验设计

为了不产生并行执行的相互响应, 我还是选择了效率很差的同步执行, 我使用了ipython提供的%timeit. 上代码

import re
import glob
import subprocess
import cPickle as pickle
from collections import defaultdict

IMAP = {
    'cn': ('豆瓣', '小明明'),
    'en': ('four', 'python')
}
OPTIONS = ('', '-i', '-v')
FILES = glob.glob('text_*_MB.txt')
EN_RES = defaultdict(dict)
CN_RES = defaultdict(dict)
RES = {
        'en': EN_RES,
        'cn': CN_RES
}
REGEX = re.compile(r'text_(\d+)_(\w+)_MB.txt')
CALL_STR = '{command} {option} {word} {filename} > /dev/null 2>&1'

for filename in FILES:
    size, xn = REGEX.search(filename).groups()
    for word in IMAP[xn]:
        _r = defaultdict(dict)
        for command in ['grep', 'ack', 'ag']:
            for option in OPTIONS:
                rs = %timeit -o -n10 subprocess.call(CALL_STR.format(command=command, option=option, word=word, filename=filename), shell=True)
                best = rs.best
                _r[command][option] = best
        RES[xn][word][size] = _r

# 存起来

data = pickle.dumps(RES)

with open('result.db', 'w') as f:
    f.write(data)

温馨提示, 这是一个灰常耗时的测试. 开始执行后 要喝很久的茶…

我来秦皇岛办事完毕(耗时超过1一天), 继续我们的实验.

我想要的效果

我想工作的时候一般都是用到不带参数/带-i(忽略大小写)/-v(查找不匹配项)这三种. 所以这里测试了:

  1. 英文搜索/中文搜索
  2. 选择了2个搜索词(效率太低, 否则可能选择多个)
  3. 分别测试’’/’-i’/’-v’三种参数的执行
  4. 使用%timeit, 每种条件执行10遍, 选择效率最好的一次的结果
  5. 每个图代码一个搜索词, 3搜索命令, 一个选项在搜索不同大小文件时的效率对比

多图预警, 我先说结论

  1. 在搜索的总数据量较小的情况下, 使用grep, ack甚至ag在感官上区别不大
  2. 搜索的总数据量较大时, grep效率下滑的很多, 完全不要选
  3. ack在某些场景下没有grep效果高(比如使用-v索索中文的时候)
  4. 在不使用ag没有实现的选项功能的前提下, ag完全可以替代ack/grep

渲染图片的gist可以看这里benchmarks.ipynb. 他的数据来自上面跑的结果在序列化之后存入的文件

附图(共12张)

chart chart-1 chart-2 chart-3 chart-4 chart-5 chart-6 chart-7 chart-8 chart-9 chart-10 chart-11

前言

我以前写过一些IPython高级用法, 还有在组内分享了一期IPython notebook的分享. 今天IPython3被release了. 它带来什么可以看一下release notes. 好吧, 我也没有意识到ipython3来的会这样快. 这多半年来. 我作为一个150个贡献者之一, 见证了IPython的发展. 这是个里程碑的版本. 他带来了非常多的变化和新的特性. 今天我来帮大家迁移和解读一些吧.

IPython是什么? 本质上它是一个增强版的python交互模式解释器, 所见即所得的执行代码, 查看结果, 也拥有历史记录. 我认为这是一个python开发者必备的工具. 我个人依赖ipython常用的功能有:

  • ipython notebook - 一个可以跑的在线可编辑可运行的笔记. 可以测试程序, 执行代码, 当做说明文档, 能帮助不擅长web开发的同学做出很多页面的效果, 支持markdown语法等
  • 自动补全 - 当我import xx的时候 我可以像用zsh一样使用Tab自动补全对应的模块/方法的名字
  • magic - 它提供很很多magic的函数命令, 比如你可以直接执行ls, pwd等. 还能使用其他shell命令, 调用编辑器等
  • 它能通过?或者??帮我查看代码的注释, 接口参数等等.
  • 它提供很多的配置选择, 可以使用内置/外部插件达到一些其他的功能, 比如autoreload - 你不需要退出ipython就能获得你已经import之后的代码修改后的效果.
  • 它在分布计算, 数据分析上又很好的支持, ipython非常大的使用群体是科学家和算法工程师

它在python界有什么地位? 我肯定会带有个人色彩. 来一些github的数据说一说(截止到2015-03-01之前):

项目 Issue数 Star数
django 4221 13088
flask 1359 12810
tornado 1352 8626
ipython 7898 5822

这是python最有名的几个项目. 可以看到ipython的star远落后于其他. 但是他的issue数却大大的高于其他, 一方面IPython覆盖的功能和逻辑更多更复杂. 一方面用户对IPython的依赖和兴趣要高很多, 还有一方面IPython也由于内容太多更容易有bug,且主要维护者都是科学家没有太多精力和兴趣做一些基础保障. 可见IPython的知名度不高, 但是对用户粘性却很高.

如何升级

假如你需要使用ipython notebook, 需要使用

pip install --upgrade "ipython[all]"

否则直接

pip install --upgrade ipython

使用不同的内核(kernel)

IPython的组件大多是核心开发者开发的, 中提到了kernel是这样几个:

Bash
Echo
Python2
Python3
R

Bash是这个项目https://github.com/takluyver/bash_kernel/, 你可以直接

sudo pip install bash_kernel

那么开始说kernel是什么, kernel是一个能执行各种语言的程序封装, 比如可以用notebook跑bash, 跑ruby, 能使用其他语言的语法. 上面的bash就是借用pexpect的replwrap实现的bash的封装.对比一下就知道了:

$/usr/local/bin/ipython
In [1]: echo
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-53f31a089339> in <module>()
----> 1 echo

NameError: name 'echo' is not defined
In [2]: bc
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-2-b79898bb7907> in <module>()
----> 1 bc

NameError: name 'bc' is not defined
$/usr/local/bin/ipython console --kernel bash # 使用bash内核就可以使用这些bash下命令了
IPython Console 3.0.0

In [1]: echo '2-1'
2-1

In [2]: echo '2-1'|bc
1

看一下我本地都能用什么kernel:

$ipython kernelspec list
Available kernels:
python2
python3
bash
echo

python2就是系统默认的, 原来用的那个. 看到echo和python3的原因在这里:

$pwd
/Users/dongweiming/.ipython/kernels
$tree
.
├── echo # 在~/.ipython/kernels有这个echo的目录里面包含了正确地kernel.json就会出现对应的kernel
│   └── kernel.json
├── echokernel.py
└── python3
    └── kernel.json

2 directories, 3 files

看一下代码:

$cat echo/kernel.json
{"argv":["python","-m","echokernel", "-f", "{connection_file}"],
 "display_name":"Echo"
 }

$cat python3/kernel.json
{
  "display_name": "IPython (Python 3)",
  "language": "python",
  "argv": [
    "python3",
    "-c", "from IPython.kernel.zmq.kernelapp import main; main()",
    "-f", "{connection_file}"
    ],
  "codemirror_mode": {
    "version": 2,
    "name": "ipython"
    }
}
$ipython console --kernel python3 # 可以在python2下跑python3的代码了
In [1]: print
Out[1]: <function print>

In [2]: print 'sd'
  File "<ipython-input-2-f747b7d9e029>", line 1
      print 'sd'
      ^
SyntaxError: invalid syntax

更多的自定义请看Wrapper kernels

当然这里默认都可以在notebook里使用

Widget

widget系统经过了很大的重构和更新, 全部信息在widget migration guide.

Widget是什么? 这是ipython notebook的插件系统, 大部分的插件都可以看这里: containers_widgets.ipynb, 看完就知道它是什么和它能做什么了. 在我分享notebook的项目divingintoipynb里你能看到我自定义的widget: selectize_widget.ipynb, 和对应的widget_selectize.py.

custom.js在使用的时候也有了很大的变动, 可以看我分享项目的custom.js

Notebook format

原来的Notebook的版本是3, 现在已经升级为4. 他们是不兼容的版本. 在启动新版IPython访问你的ipynb的时候会出现这样的弹出框:

This notebook has been converted from an older notebook format (v3) to the current notebook format (v4). The next time you save this notebook, the current notebook format will be used. Older versions of IPython may not be able to read the new format. To preserve the original version, close the notebook without saving it.

你记得保存一下会帮你自动转化为新版本, 下次再启动就可以. 假如由于bug或者其他原因想降级可以这样:

ipython nbconvert --to notebook --nbformat 3 <notebook>

使用jinja2自定义模板

通过NotebookApp.extra_template_paths可以指定外部模板目录, 可以代替默认模板. 或者:

ipython notebook '--extra_template_paths=["/Users/dongweiming/.ipython/templates/"]'

比如你不喜欢ipython notebook提供的现有的目录页(http://localhost:8000/tree这样的路径). 你可以自己写一个叫做tree.html.的模板放在 /Users/dongweiming/.ipython/templates/目录下. 但是建议还是继承原来的tree.html, 再自由发挥.

使用ipython notebook的terminal功能.

在notebook页面上其实是可以直接使用websocket连接到服务器上的. 但是你需要安装terminado. 这样在/tree下新建的时候就能选择terminal了.

其他

剩下就是一些bug修改, 方法重命名, 功能增强, 去掉一些不再被维护的内容等等. 对95%的用户影响几乎没有.

前言

今天发现一个项目: ptpython. 新一代的REPL神器. 玩了一下. 毅然的ipython换成了ptipython - ptpython自带的 iython的接口. 和大家介绍下

ptpython的优点

先说几个在用ipython过程中遇到的问题吧.

  1. ipython在Mac下缩进问题. 每次在交互模式里面输入一个缩进的内容, 比如下面
In [1]: def t():
   ...:     return 1
      ...:

In [2]: def t():
    return 1

看到了吧, 缩进不正确了. ptpython没有这个问题, 它的底层库prompt_toolkit实现了一个替代物

  1. jedi. 我在emacs里面, jedi是标配. 其一是因为有epc, 可以和python通信, 它的自动补全和跳到函数/类定义真的很方便. 但是python交互解释器 还没有一个用它的. 这里真的感觉效果很好.

  2. emacs/vim键位. 好吧我经常在用ipython的时候习惯Ctrl+x Ctrl+c退出. 能用emacs的键位做编辑真的很爽
  3. 提供一个终端的菜单, 有多个选项可以选择
  4. 模式粘贴. 大家知道python有个问题: 你粘贴过来的代码不一定能运行 - 粘贴后的缩进会很奇怪的. ipython虽然有%paste%cpaste. 但是有时候还是会有IndentationError问题. ptpython在这里独创了Paste mode. 使用F7切换. 还可以多行编辑.
  5. 可以开启多个Tab,甚至多个Tab的内容一齐显示出来. 类似vim的:split
  6. 能对你写的每行程序判断是否出现语法错误.如果你的代码有问题, 下面左侧会有错误提示 - 这其实能延伸做很多检查嘛
  7. ipython对查看对象的方法有一些问题, 比如这个:
$ipython
In [1]: '/tmp'.<tab> # 不会理你的

ptpython对这样的处理都很好

  1. 一个很贴心的特性:
$ptpython
In [1]: 'tmp<Tab> # 他会告诉你这是个目录, 还会自动完成列出目录下的文件. autocompletion
  1. 最后一点吧. 它非常容易的被嵌入你的程序,你的解释器
python
Python 2.7.3 (default, Apr 10 2013, 06:20:15)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
Welcome to Lazy Python.  Type "help LazyPython" for help.
>>> from ptpython.repl import embed
>>> embed(globals(), locals(), vi_mode=False, history_filename=None)
In [1]: # 看就这么2句

ptipython

ptipython = ptpython + ipython

在安装了ptipython之后, 就可以使用了. 完全可以替代ipython. 类似bipython = bpython + ipython

前言

本来准备下一次的Bpug(北京python用户组)的活动上准备做这个分享. 搁置了. 有兴趣组织纯技术活动的公司或者组织可以联系我. 有兴趣的同学可以前往(我也会把录像地址放到网站的). 提前给大家做一些预告. 其实ppt早就放到slideshare上了, 地址是http://www.slideshare.net/dongweiming/ipython-notebook-43211257. 也可以从github下载

对应的代码在divingintoipynb. Youtube在线看: https://www.youtube.com/watch?v=qMcKp8gFAYA

大纲

  1. 豆瓣东西双11临时后台 - 想看效果么? 看下面
  2. 把ipython notebook转换成html或者其他格式以及它的原理
  3. 我写的一个缩小版的nbviewer: Ipynb-viewer, 直接在ipython目录启动web服务
  4. nbconvert原理
  5. 用ipynb写blog(pelican/nikola) 效果可见divingintoipynb_pelicandivingintoipynb_nikola 还会讲到pelican转换ipynb到html插件,使用fabric: new_post, edit,import_ipynb. 我也给nikola贡献了import ipynb功能.
  6. ipython notebook用到得第三方库和组件
  7. Rich display system
  8. 现有的扩展, 演示. 我自己写的一个扩展. 演示, 代码分析
  9. 定制ipython notebook的键位. 使用emacs键位. 设计一个新的功能 - 弹出一个dialog列出所有emacs快捷键说明(想起来了么? C-h b)
  10. 定制一个基于selectize.js的widget. 前后端代码分析.
  11. ipython notebook 其他方面的一些用法, 整个过程中有ipython2也有ipython3

模拟后台效果:

UPDATE: 2015-02-02

上周五在组内分享了, 下面是视频下载地址: http://pan.baidu.com/s/1o6BjBXg