阅读视图

发现新文章,点击刷新页面。
☑️ ☆

from __future__ import annotations

最近做了一些豆瓣的产品业务代码的 Python 3 迁移相关的准备工作。首先当然是要去改那些基础 Model,除了代码符合 Python3 语法要求,这种基础的、核心的代码也要加上类型注解,结果一上来就遇到个问题,和大家分享下。 下面是一个模拟的简单版本的例子(豆瓣厂工和前厂工会理解这种写法😋)

from typing import Union
from dataclasses import dataclass

import pymysql.cursors

connection = pymysql.connect(host='localhost',
                             user='root',
                             password='',
                             db='test',
                             charset='utf8mb4')


@dataclass
class Subject:
    id: int
    cat_id: int
    title: str
    kind: int = 0

    @classmethod
    def get(cls, id: int) -> Union[Subject, None]:
        with connection.cursor() as cursor:
            cursor.execute(
                "select id, cat_id, title, kind from subject where id=%s", id)
            rs = cursor.fetchone()
        if not rs:
            return None
        return cls(*rs)

    def set_cover(self, cover: ImageCover):
        ...


@dataclass
class ImageCover:
    id: int
    identifier: str

我简单介绍下上面这段代码要做的事情:

  1. 用 dataclass 装饰器可以帮你生成 initrepr_ 和比较相关的各种魔术方法、同时添加字段验证等等,极高了提高了生产力。具体可以看我之前写的 attrs 和 Python3.7 的 dataclasses ,上例 Subject 包含了 id、title、kind、cat_id 四个字段
  2. Subject 包含一个 get 方法,通过 id 可以拿到对应的 Subject 实例,如果数据库找不到会返回 None,所以类型注解中用了 Union。

Python 是一种动态类型化的语言,不会强制使用类型提示,所以我们要借用外部的工具 mypy 做类型检查:

➜  pip3 install mypy --user
➜  mypy ~/workspace/movie/movie/models/base.py

mypy 的执行结果没有返回内容,说明我写的类型注解没有问题。但是运行不了

In [1]: from movie.models.base import Subject
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-b027a5ff7e8f> in <module>
----> 1 from a import Subject

~/workspace/movie/movie/models/base.py in <module>
     12
     13
---> 14 @dataclass
     15 class Subject:
     16     id: int

~/workspace/movie/movie/models/base.py in Subject()
     20
     21     @classmethod
---> 22     def get(cls, id: int) -> Union[Subject, None]:
     23         with connection.cursor() as cursor:
     24             cursor.execute(

NameError: name 'Subject' is not defined

即使把 Union[Subject, None] 这部分去掉,下面的 ImageCover 那部分也会抛错:

29         return cls(*rs)
     30
---> 31     def set_cover(self, cover: ImageCover):
     32         ...
     33

NameError: name 'ImageCover' is not defined

这个问题可以看 PEP 563 ,简单地说是因为类型注解部分求值太早了,那个时候类还没创建完!

怎么办呢?让类型注解「延迟求值」,使用 Python 3.7 新加入的方案:

from __future__ import annotations
from typing import Union
from dataclasses import dataclass

...

添加第一行,这样就可以了。

使用字符串替代类

第二次更新: 2018-12-30 19:55:57

文章发出后,@abc.zxy 和 @杨恺 Thomas Young 同学都提出了另外一个解决方案,就是使用对应字符串作为类型值:

@dataclass
class Subject:
    id: int
    cat_id: int
    title: str
    kind: int = 0

    @classmethod
    def get(cls, id: int) -> Union['Subject', None]:  # Subject是字符串
        with connection.cursor() as cursor:
            cursor.execute(
                "select id, cat_id, title, kind from subject where id=%s", id)
            rs = cursor.fetchone()
        if not rs:
            return None
        return cls(*rs)

    def set_cover(self, cover: 'ImageCover'):  # ImageCover是字符串
        ...

可以看到不使用 fromfutureimport annotations,这样的写法也是正常运行的。具体的可以看延伸阅读链接的 mypy 方案。而 PEP 484 也进行过讨论

这就引起了我的求知欲,既然 mypy 已经提供了解决方案,哪官方为什么要强烈的实现「延迟求值」这个特性呢?

看了下延伸阅读链接 2 里面「Typing Enhancements」部分做的解释,我汇总下观点:

Python 是一个动态语言,运行时不会做类型检查。本来在代码中添加类型注释是不应该响应性能的,但是很不幸,如果使用了 typing 模块就会影响到。

在之前的例子中,get 方法可能返回 None 或者一个 Subject 对象,就得用到 typing.Union 了。为什么会影响性能呢?

原来 typing 模块是标准库中最慢的模块了 !!所以在 Python3.7 大体做了 2 个角度的优化:

  1. 通过实现 PEP 560 让 typing 模块大大提速
  2. 通过实现 PEP 563 让类型注解延迟求值,因为类型提示没有执行

虽然前面用字符串替代类的方式可用。但是性能上可能会有影响,如果你已经在用 Python3.7 或者更高版本,理应选择 fromfutureimport annotations 这种方式。

google/pytype

三次更新: 2018-12-31 18:00:00

感谢 @laike9m 推荐的 https://github.com/google/pytype ,可以用它做静态检查和推断未注释 Python 代码的类型:

  cat foo.py
def make_greeting(user_id):
    return 'hello, user' + user_id

def print_greeting():
    print(make_greeting(0))
  pytype-single foo.py
File "foo.py", line 2, in make_greeting: unsupported operand type(s) for +: 'str' and 'int' [unsupported-operands]
  Function __add__ on str expects str
Called from (traceback):
  line 5, in print_greeting

For more details, see https://github.com/google/pytype/blob/master/docs/errors.md#unsupported-operands.

除了 mypy 也可以考虑下 Google 的 pytype ,毕竟是在生产环境下大厂成熟应用案例。

延伸阅读

  1. https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html#miscellaneous
  2. https://realpython.com/python37-new-features/
  3. https://www.python.org/dev/peps/pep-0563/

原文: from __future__ import annotations

☑️ ☆

消失的结石

先申明这不是一篇技术文章,只是用叙事的方式讲述我这 2 个礼拜由于结石住院的始末。结石是一种很常见的泌尿系统疾病,我认识的不少人都有过结石,我的感觉里面程序员或者说互联网从业者会更容易得这个病,那为什么写这篇文章呢?一则这个过程还是比较戏剧化的,对我来说真的是起伏跌宕,甚至现在对这个结果还有点懵逼,把这些记录下来并且回溯和反思权当回忆了,再则希望分享我的经历和经验让大家能了解和重视结石问题,最后衷心希望大家不要感受结石发作时的痛苦。

一次征兆

住院前的一个礼拜左右的某一天晚上,我被右后腰的一种酸痛疼醒了,这种疼有点像着凉引起的,而且当时我觉得可能由于睡觉姿势之类引起的,但是继续入睡又疼的睡不着,后来迷迷糊糊的不知道换了什么体位疼痛就消失,我又重新睡着了。一觉醒来一切无恙,之后几天再无这种情况,也就没当回事。

现在想想,这就是一个征兆,是身体给我的一个提醒,应该是结石的位置恰巧到了一个不舒服的位置把我疼醒的,后来翻来覆去睡不着过程中改变了它的位置又相安无事。

发作

8 月 13 日(发作前一天)夜间开窗户可能是着凉了,早上起来腹泻好几次,期间下腹有绞痛和后腰的酸疼,我赶紧喝了些热水,还依照过去的腹泻经验盖着被子把热水瓶放在肚子上,就这样很快绞痛的感觉渐渐消失了,然后我正常上班去了。

前一天的腹泻没怎么当回事,8 月 14 日夜间又开窗户了,结果早上起床又腹泻三次,同样还有下腹的绞痛的感觉,头冒冷汗,尝试了一会前一天的方法,症状没有减轻甚至有些加重的倾向。大概这样不舒服了半个小时,觉得情况不对,当时我的感觉是急性肠胃炎之类的,不能再等了,决定去医院!但结果这天恰巧下雨,不好打车,在小区门口等了可能 20 分钟的样子才打到出租车。

当时觉得一点小问题嘛,大医院又远排队又太麻烦,就找了最近的一个社区性质的医院,此时大概 8 点多,想着处理完还可以继续上班。结果到了医院,人家说不接我这腹泻类型的肠道急诊... 说最近的能接肠道急诊是华信医院,这个医院我之前去过,离公司也近。不墨迹赶紧打车,应该这个时候开始有憋尿的小腹下坠和拉扯的感觉了,结果还是由于下雨,而且我上班那条路本来也容易堵车,到华信大概耗时 40 分钟(平时打车也就 10 来分钟),在车上我的小腹下坠感觉越来越明显,同时下腹绞痛和后腰酸痛的感觉也越来越重。在出租车上这多半个小时非常煎熬,那种疼虽然没有和并有交流的一种刀割般那么疼,但疼是持续的,让人坐立不安,我很多次真的想下车自己跑去医院。

现在想想,13 日结石应该就在输尿管中段狭窄处了,13 和 14 日早上一开始是不疼的,几次腹泻之后才开始有痛感,也就是排尿很多或者上厕所的姿势等因素触发了结石痛。

肠道急诊

到了华信医院就赶紧去挂急诊了,本来就按着肠道问题来看的,然后挂号、抽血,当时医生是希望做便检确认问题的(非必须),但此时我已经小便不出,且恰由于早上腹泻多次,实在搜集不到。我只能在急诊处多喝水等待。疼痛的感觉有增无减,血结果大概半小时出来,医生说结果没什么特别明显的异常,医生比较年轻看起来经验不多,定位不了我的问题,就这样不明原因的过了差不多一个小时,期间找了几次医生描述我疼痛的情况,医生后来说那验个尿吧。这个时候实在疼痛难忍,先护士先给我打了一个止痛针,然鹅对于我的疼痛完全没有效果。我不断的喝水,小便不夸张的说是稀沥沥按滴数的,为了凑够验尿的量,我前后喝了 5 瓶矿泉水,去了不下 10 次厕所,总算勉强凑够了。接着就是等待尿检结果,结果一出来,红细胞很多,也就是尿中有血(肉眼分辨不出来),医生说「你这样像是结石」呀!然后开了 B 超和 CT 的单子,我离开了急诊,去门诊楼了。

离开急诊大概 11 点了。也就是说我在这里干疼了 2 小时,而且由于喝了很多水,让小腹下坠的疼痛感觉更厉害了。

确认结石

离开急诊后去门诊楼做检查,好像急诊来的可以优先,而且 11 点这个时间点人也比较少,倒是不需要排队,但是由于要憋尿,又是再喝水,CT 做的比较晚,中午 12 点半才做(其实已经下班,急诊的可以按铃),等了差不多 20 分钟拿到了 CT 结果。

2 个结果都证明有结石。我拿回急诊给医生看,医生告诉我去「泌尿外科」,并帮我挂了个号。

急诊做检查取结果要比门诊的更快,比如 CT 门诊要等一个小时,急诊的就半个小时,而且我这个不够半小时就拿到了。如果着急请挂急诊,门诊效率相比太低了。

门诊下午 1 点才开诊,但幸好这个医院泌尿外科看病人很少,我很快就看上了。医生告诉我,右侧输尿管有一个大约 5 毫米的结石,且有轻度肾盂和上段输尿管积水。听起来 5 毫米也不大嘛,但是要注意输尿管是很窄的,我翻了下资料,它直径平均 5-7 毫米,有三处狭窄的地方 1-2 毫米,可见我这个结石还是很大的,直接堵死了右侧输尿管,梗阻了。

确认结石时大概 13 点半,我这个时候还在疼,医生说这种疼普通的止疼针是没有效果的,立竿见影的办法就是住院打杜冷丁。虽然极度不想住院,也不想打这种听起来是绝症病人才用的药,但是这个疼一直持续真的太痛苦,此时我只想止疼。

住院

接着就是办理住院手续,在护士站登记后入病房,换了病号服。我的感觉是打针对疼痛有缓解但是还是疼,不一会我的主治医生找我,了解病情并和我沟通情况,提了 2 种治疗方案:

  1. 直接做手术(经皮肾镜微创取石)。虽然是微创的,但也要有 1cm 左右的伤口。
  2. 先体外碎石(即体外冲击波碎石术,ESWL)。看看能不能碎掉结石,如果能碎掉就没有必要做手术了。这里引出一句话, 碎石并不一定成功 ,而且医生一开始就和我说看 CT 的片子中结石发亮,感觉质地比较硬。

正是由于腹泻,暴露了结石的问题。让我在早期痛苦时就发现并就医了,护士长说通常结石病人住院时哭天喊地、要死要活、直不起腰的情况,我这算是轻的了。万幸吧!

做手术的话预计费用 2.5w,医保可报 70%,但是由于豆瓣有补充医疗保险,基本上我不花什么钱。但是我还是选择方案 2,主要是对手术的恐惧,希望身体是「完整」没有开过刀的,而且听说别人说好像都是自己排或者体外碎石出来的,我又这么年轻有什么必要直接做手术呢?

第一次碎石

沟通后不久,主治医生就带我去碎石室碎石。每次碎石冲击波共 2500 次,过程不到半小时,有时候打下来可能正中结石位置会比较疼,其余大部分疼痛能接受:据说有人在碎石过程中能睡着我也是服。

碎完就有一种能尿出来的感觉,感觉非常好,回病房不久就能排尿了,当然第一次是血尿,比较浑浊,医生要求每次都要接尿看是否有东西排出。碎石后会开始输液,在几个小时内会有感觉疼痛家加剧,但是输液非常利尿,随着我不断的排尿所有的疼痛感渐渐的就消失了,尿液颜色也逐渐正常了。

嗯,到输液完成大概凌晨 1 点。此时我已经完全不疼,像极了一个正常人。

这一天正好是我的生日,非常特别,第一次住院,基本一整天都是疼痛中度过。

第二次碎石前

接着几天模式都差不多:

  1. 上午 / 下午到点输液
  2. 多喝水,喝水喝到想吐,后来不得不来点脉动、力量帝之类的果味的饮料缓解下
  3. 多运动。就是蹦蹦跳跳,溜达,找同层病友聊个天什么的。

不过和预想的不一样,再也没有第一天那样疼过,我住院就是担心会往复的那种疼,有时候会有一些疼痛的感觉,但比较轻,而且很快疼痛会消失。

我这个人既来之则安之,都住院了,索性有什么不舒服的都提出来,然后就是做各种检查。这样总比我起个大早挂号再排队检查体验好得多。住院的做检查往往有很多优待,有专门的通道,专人送到住院部等等,另外穿着病号服去排队,往往能收获很多关爱的眼神😁

医院对住院病人管理不严,可以随意出入医院,我经常穿着病号服去门口小卖部买饮料:一眼看去真不想一个病人。

这个过程整体来说我还是很轻松的,一直不疼应该预示着石头已经排掉了,但我一直没有看到碎石后产生的碎渣之类的东西,还是有一丝忐忑。

第二次碎石

一周后的 21 日,又拍了一次 B 超和 CT,这次 B 超没找到结石,其中一个原因是腹部有气看不清楚,但是依然有肾积水,做 B 超的医生说应该结石还在,我不开心了😒,之后的 CT 也证实了这一点。但好消息是结石比之前小了一些,不到 3 毫米,而且已经到了输尿管和膀胱连接处,这里是最后一处输尿管狭窄的部分,另外主治医生说结石颜色变浅了一些,可能碎的比较薄了一些。

所以这里大家注意,并不是一次碎石就能成功,他可能还在,只是暂时不作恶罢了。住院部有泌尿外科的一些结石,其中提到一次体外碎石成功率是 90%,嗯,我就是那 10% 😢

主治医生找我沟通下一步,有 2 个选择。做手术或者再碎一次,同样明确了再碎依然不能保证碎掉。我犹豫了一下,还是决定再碎一次。过程就不说了,和第一次碎石大体一样,但是碎的过程的疼痛明显比上一次厉害,好在医生同意我一边碎一边刷手机转移了一点注意力。

第二次碎石后

整个住院的过程中,病友们换了一拨又一拨,第二次碎石时就已经有病友出院了。有的病友碎石当场就会排出碎渣,然后直接出院了。同样结石的病友年龄都很大,40-60 的居多,像我这种比较年轻的是很少的,他们竟然这么快出院了,尤其是有些病友都不怎么喝水和运动(当然也受限于年龄),这对我来说压力越来越大,因为我这种结石虽然不疼,但是长时间的肾积水是不行的,第二次碎石后,我在喝水和运动这些方面上更努力和积极了,真不想走到动手术哪一步啊。有一种工期临近但是离做完还很遥远的无力感,甚至是要做倒计时的感觉了。

这段时间我基本每天喝水 4k 毫升以上,各种跳跃,上下楼梯等等,我也会翻一些网络上输尿管排石经验,试着去做,但一日复一日,完全没有结石的感觉。

到周五(24 日),希望越来越渺茫了,系主任查房时也说这积水时间有点长了,这么多天都没排出来,内窥镜取了吧。我为什么觉得结石没出来呢?有几点

  1. 第二次碎石后的第一次血尿沉淀了一些浮沫,也能看到一些极小的碎屑,但是没有看到石头的碎渣,毕竟 3 毫米呢。主治医生看了后也觉得那些沉淀物不是结石
  2. 整个排尿我都接了,完全没看到排除小石块之类的碎渣。
  3. 排尿少。这也是我对石头还没出来的判断的重要依据。之前憋尿情况下,经常可以尿满一矿泉水瓶(500ml),这个时间点哪怕感觉要憋不住的情况也只能尿一半左右,仿佛有一边梗阻影响了排尿。其实我也会担心这种情况对肾积水的影响。

我当时选择做第二次碎石就是一种赌博,因为感觉我的结石形状不规则,并且有刺,我想很多病友都能排出来我的出不来,问医生说可能是有刺扎在某处下不来了,碎石可以让他能继续排出。但是到现在还是出不来,我觉得已经尽力了,那就做手术吧,然后找主治大夫约了下周一(27 日)的手术。

周五的心情

到周五时,已经开始送第二波病友,他们都是在我之后入院然后出院的。感觉医生,护士都对我这种情况有点无奈,我这个情况是什么呢?

  1. 结石数量少。有些病友都是 2 个甚至三个
  2. 结石尺寸小。第一次碎石时的 5 毫米还勉强入围一下,现在这不到 3 毫米的基本是人家最小那个的尺寸。看网上排石经验都没有我这么小的....
  3. 住院周期长。绝大多数的病人都会在一周内出院,有些甚至单靠输液不做体外碎石就能排出,我这明显往 2 周上冲。
  4. 我是最年轻的那一拨。病友动辄就 60 岁起步,和我同龄的不过 2-3 个,我住院期间只见过一个比我年龄小的男病人,且人家碎石后就排出来第二天出院了。

这个时候,心情是什么呢?有点尴尬?着急?好像没有语言能总结出来那种感受。唯一能够宽慰的是我并不是个例,还有一个妹子也是这样,虽然比我晚进来 2 天,但是没有排出来,心情应该和我一样。我俩在病房遇到第一句话总是『排出来了没?』,有点难兄难弟的感觉,捋了一遍,现在住院的这些除了几个病的中一些的,我俩是名副其实的「老人」。

最后的努力

周五时我已经不输液了,医生说我这种情况通过输液促进排尿和输尿管扩张的辅助作用已经意义不大了,和医生沟通了下反正住院也无事,周末我就回家,最后在努力一下,也是更好的休息一下,等待下周一手术。

其实周末对我来说,并没有抱多大希望,之前主治医生说碎石前三天排不出来之后恐怕也拍不出来了。不过我的性格就是,既然不能反抗就痛快的面对呗。但是该喝水该蹦跶一点也不会少。还买了跳绳。早中晚。初步算一下,不比医院的少,每天喝水 > 4k 毫升,跳绳次数 > 4k,这还不算散步或者高抬腿之类的运动。总之我会尽力到最后一天,即便结果看起来我的「赌」都是失败的,应该入院时就直接做手术省了很多罪。选择了就没什么可后悔的,对吧。

马上要手术了

本来预约周一比较晚了,我觉得可能得轮到下午。结果周一一大早医院打电话说我是上午第一个手术。匆忙间打车去医院,所幸并没有影响什么,刚到住院部就遇到主治医生,让我签了一堆东西,还给我开了 CT 让我再看看结石。我那时都已经放弃了,说直接做手术不用再看了吧?CT 也是一次辐射,最近做的次数有点多了。医生说肯定得看呀,万一结石没有了(我的内心是不信的),而且也得看看这一周结石在什么位置,方便手术确认,旁边一个医生做了一个比喻我忘记了,大意就是说 CT 的辐射量很小,我当时想做一个也好,万一结石马上出来了,医生说先不做了呢?

然后去下面等待拍 CT,太早了 CT 室还没上班多等了一会。等我排完回去正好遇到那个同样没排出来结石的女病友也来做 CT,她说:你的手术床都到了,等你了。嗯,有种待宰羔羊的感觉了。上楼后看到了那个手术床,仿佛完全没有回旋的余地了。这时护士找我做术准备了。

转折

护士备皮时,我已经在准备做手术时交代什么了:为啥这个小破结石就是不出来?是不是输尿管狭窄等问题,下次是不是再有结石我应该选择直接手术?你们要不要把那个结石拿去分析一下为啥 2 次都碎不掉?通过结石成分是不是说明我平时有什么有问题的饮食习惯... 等等。

我还在想问题时,听到我的主治医生在外面问其他护士,23 床进手术室没?然后朝我这个方向走来。那个时候我的心情其实已经是很平静的了。他推门进来,我还没来得及打招呼,就听它和护士说不用备皮了,手术不用做了。我当时的心情是:????

然后他微笑的和我说,看你的 CT 片子石头已经没有了,肾积水也没了,所以不用做手术了。护士应该也没见过这么一种场景,有点愣住了,继续处理了一些,然后就出去了....

我不知道该用什么描述这时的心情,如果早上我来的早一些应该术前准备都做完了,可能已经被推进手术室了:它离我真的很近。

我从处置室出来都一直有点懵逼,第一感觉是检查报告是不是搞混了?回病房简单冲了个澡,不一会,主治医生过来找我说「你可以出院了」,我唯一的问题就是「为什么我完全没有感觉它排出来」,医生说应该是碎的非常彻底,又叮嘱我一些事情。半个小时后,本来我应该刚做完手术在病床上不能动,结果已经神奇的到公司上班了,这完全是一个天上一个地下呀。

其实现在回想,有几次排尿过程中尿道有疼痛感,排后疼痛感很久就小时了,不过我确实没看到有结石排出,可能是没看仔细?

另外,妹子的检查结果也出来了,结石没有了,它的主治医生也通知她出院了。嗯,这个结果是不是灰常圆满?

后记

以上就是整个过程了。那么问题来了,我为什么会得这个结石,结合医院墙壁上的一些资料和与病友聊天,觉得有下面几个方面的问题:

  1. 缺乏运动。原来我是一个挺注重运动和锻炼的,但近 1-2 年,尤其是最近半年制作课程真的没有时间。之后还是要抽时间运动呀
  2. 肉食动物。尤其爱吃动物内脏,其实最近 1-3 年越来越胖,也越来越有中年大叔的倾向,我已经在饮食里面注意了,每次吃都会有愧疚感。之后要更加注意啦
  3. 饮食偏甜。其实刚工作我是很不喜欢这种饮食的,但是在外面吃饭是在不能避免,太淡的又真的让人没食欲,现在已经堕落到自己做菜都会放点糖了,之前在饮食里面已经注意减少了,之后继续加油
  4. 喜食草酸含量高的食物。这个大家可以自己搜索,我就爱吃豆腐、花生、红薯、橘子。
  5. 喝水少。我相对很多同事来说算是喝的挺多了。之后每天至少喝 2000 毫升水吧

在得结石后,除了去医院体外碎石,我更建议在它尚小的时候通过多喝水和多运动让他自己出来。最后说几点我对结石的认识:

  1. 结石病很常见。我对住院病人的不负责的统计,大概 1/3 的病人都是结石。
  2. 我并没有找到权威的高危人群和年轻的论文。以我对医院病友们的观察和交流,30-40 岁,55-65 岁这 2 个年龄段居多。相对来说女性得结石的要少很多,但是几率这种东西放在个人生身上就是 0% 和 100%,只不过年轻的关注小一些,随着年龄增大要越来越关注。
  3. 结石分很多种,大部分是肾结石和输尿管结石。
  4. 结石发作很痛。有过结石的同学可以留言说说感受,相对于一些病友刀割般疼、哭天喊地、直不起腰的感受,我的那种疼还没到痛不欲生的地步,只是当天下午等门诊时疼的跪在地上了。不过我还是建议大家不要体验这种感觉比较幸福。

就写到这里吧,感谢大家看完。

原文: 消失的结石

☑️ ☆

Python 3.7 中的 PEP 562

按照 Python3.7 的发布时间表,明天 Python 3.7.0 就发布了,最近各大开源项目都在做 3.7 相关的调整,之后我还会写文章更详细的介绍 Python 3.7 都带来了什么,敬请关注!Python 3.7 是一个比较中庸的版本,我比较关注的是 PEP 557 和 本文要提到的 PEP 562

PEP557 是 Data Classes,之前我已经在 attrs 和 Python3.7 的 dataclasses 里面专门介绍了。

PEP 562 主要做的是什么呢?

Customization of Access to Module Attributes

就是能在模块下定义getattrdir方法,实现定制访问模块属性了。有什么用呢?其实官网已经给出了答案:

  1. 弃用某些属性 / 函数
  2. 懒加载 (lazy loading)

getattr让模块属性的访问非常灵活,我分别举几个例子:

弃用某些属性 / 函数时

有时候会修改一些函数或者属性,会写新的版本,旧版本的在一段时间之后会弃用。在大型项目中调用者有很多,不了解业务挨处修改成本很高,通常会在旧版本的函数中加入 DeprecationWarning,有 3 个问题:

  1. 使用新的属性是没法提示 DeprecationWarning,只能在模块级别加 warn
  2. 如果找不到属性 / 函数直接抛错误了,不能做特殊处理
  3. 模块下弃用的函数多了,只能在每个函数内部加入 warn,再执行新函数逻辑

而 Python 3.7 就没这个问题了:

# lib.py

import warnings

warnings.filterwarnings('default')  # Python 3.2开始默认会隐藏DeprecationWarning


def new_function(arg, other):
    print('plz use me!')


_deprecated_map = {
    'old_function': new_function
}


def __getattr__(name):
    if name in _deprecated_map:
        switch_to = _deprecated_map[name]
        warnings.warn(f'{name} is deprecated. Switch to {__name__}.{switch_to.__name__}.',
             DeprecationWarning)
        return switch_to
    raise AttributeError(f"module {__name__} has no attribute {name}")

看一下效果吧:

>>> from lib import old_function
/Users/dongwm/test/lib.py:18: DeprecationWarning: old_function is deprecated. Switch to lib.new_function.
  DeprecationWarning)
>>> old_function
<function new_function at 0x10ad30f28>
>>> old_function(1, 2)
plz use me!

懒加载

懒加载是指从一个数据对象通过方法获得里面的一个属性对象时,这个对应对象实际并没有随其父数据对象创建时一起保存在运行空间中,而是在其读取方法第一次被调用时才从其他数据源中加载到运行空间中,这样可以避免过早地导入过大的数据对象但并没有使用的空间占用浪费。

简单地说,按需才加载。这是一种设计模式。

Python3.7 之前想要 import 模块成功,就得在模块里面把相关属性 / 函数 / 类等都准备好,其实 import 模块时候是很重的,现在可以通过 PEP 562,能够极大的提升 import 的效率,尤其是导入了很重的逻辑。就如 PEP 中提的例子:

# lib/__init__.py

import importlib

__all__ = ['submod', ...]

def __getattr__(name):
    if name in __all__:
        return importlib.import_module("." + name, __name__)
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

# lib/submod.py

print("Submodule loaded")


class HeavyClass:
    ...

# main.py

import lib
lib.submodule.HeavyClass  # prints "Submodule loaded"

可以看到,import lib 的时候,HeavyClass 还没有没有加载的,当第一次使用 lib.submodule 的时候才会加载。

在标准库里面也有应用,比如 bpo-32596 中对 concurrent.futures 模块的修改:

def __dir__():
    return __all__ + ('__author__', '__doc__')


def __getattr__(name):
    global ProcessPoolExecutor, ThreadPoolExecutor

    if name == 'ProcessPoolExecutor':
        from .process import ProcessPoolExecutor as pe
        ProcessPoolExecutor = pe
        return pe

    if name == 'ThreadPoolExecutor':
        from .thread import ThreadPoolExecutor as te
        ThreadPoolExecutor = te
        return te

    raise AttributeError(f"module {__name__} has no attribute {name}")

这样还可以让 import asyncio 时可以快 15%。

原文: Python 3.7 中的 PEP 562

☑️ ☆

attrs 和 Python3.7 的 dataclasses

一直想写一篇介绍 attrs 的文章,但是最近几个月忙于做 爱湃森课程 实在抽不出空来做,最近感觉找到节奏了,还是稳步向前走了,这个周末就硬挤了一下午写写,要不感觉对不起订阅专栏的同学们。

在国内我没见过有同学说这 2 个东西,它们是什么,又有什么关联呢?别着急,先铺垫一下他俩出现的背景。

痛点

写多了 Python,尤其是开发和微信的项目比较大的时候,你可能和我一样感觉写 Python 的类很累。怎么累呢?举个例子,现在有个商品类,init是这么写的:

class Product(object):
    def __init__(self, id, author_id, category_id, brand_id, spu_id, 
                 title, item_id, n_comments, creation_time, update_time, 
                 source='', parent_id=0, ancestor_id=0): 
        self.id = id
        self.author_id = author_id
        self.category_id = category_id
        ...

问题 1:特点是初始化参数很多,每一个都需要 self.xx = xx 这样往实例上赋值。我印象见过一个类有 30 多个参数,这个init方法下光是赋值就占了一屏多...

再说问题 2,如果不定义repr方法,打印类对象的方式很不友好,大概是这样的:

In : p
Out: <test.Product at 0x10ba6a320>

定义时参数太多,一般按需要挑几个露出来:

def __repr__(self):
    return '{}(id={}, author_id={}, category_id={}, brand_id={})'.format(
        self.__class__.__name__, self.id, self.author_id, self.category_id, 
        self.brand_id)

这样再看类对象就友好了:

In : p
Out: Product(id=1, author_id=100001, category_id=2003, brand_id=20)

但是每个类都需要手动的去写 repr

接着说问题 3,对象比较,有时候需要判断 2 个对象是否相等甚至大小(例如用于展示顺序):

def __eq__(self, other):
    if not isinstance(other, self.__class__):
        return NotImplemented
    return (self.id, self.author_id, self.category_id, self.brand_id) == (
        other.id, other.author_id, other.category_id, other.brand_id)
def __lt__(self, other):
    if not isinstance(other, self.__class__):
        return NotImplemented
    return (self.id, self.author_id, self.category_id, self.brand_id) < (
        other.id, other.author_id, other.category_id, other.brand_id)

如果对比的更全面不用所有 gt、gte、lte 都写出来,用 functools.total_ordering 这样就够了:

from functools import total_ordering


@total_ordering
class Product(object):
   ...

然后是问题 4。有些场景下希望对对象去重,可以添加hash:

def __hash__(self):
    return hash((self.id, self.author_id, self.category_id, self.brand_id))

这样就不需要对一大堆的对象挨个比较去重了,直接用集合就可以了:

In : p1 = Product(1, 100001, 2003, 20, 1002393002, '这是一个测试商品1', 2000001, 100, None, 1)

In : p2 = Product(1, 100001, 2003, 20, 1002393002, '这是一个测试商品2', 2000001, 100, None, 2)

In : {p1, p2}
Out: {Product(id=1, author_id=100001, category_id=2003, brand_id=20)}

可以看到集合只返回了一个对象,另外一个被去掉了。

最后是问题 5。我很喜欢给类写一个 to_dict、to_json 或者 as_dict 这样的方法,把类里面的属性打包成一个字典返回。基本都是每个类都要写一遍它:

def to_dict(self):
    return {
        'id': self.id,
        'author_id': self.author_id,
        'category_id': self.category_id,
        ...
    }

当然没有特殊的理由,可以直接使用 vars (self) 获得, 上面这种键值对指定的方式会更精准,只导出想导出的部分,举个特殊的理由吧:

def to_dict(self):
    self._a = 1
    return vars(self)

会把_a 也包含在返回的结果中,然而它并不应该被导出,所以不适合 vars 函数。

到这里,我们停下来想想,self.id、self.author_id、self.category_id 这些分别写了几次?

那有没有一种方法,可以在创建类的时候自动给类加上这些东西,把开发者解脱出来呢?这就是我们今天介绍的 attrs 和 Python 3.7 标准库里面将要加的 dataclasses 模块做的事情,而且它们能做的会更多。

attrs

attrs 是 Python 核心开发 Hynek Schlawack 设计并实现的一个项目,它就是解决上述痛点而生的,上述类,使用 attrs 这样写:

import attr


@attr.s(hash=True)
class Product(object):
    id = attr.ib()
    author_id = attr.ib()
    brand_id = attr.ib()
    spu_id = attr.ib()
    title = attr.ib(repr=False, cmp=False, hash=False)
    item_id = attr.ib(repr=False, cmp=False, hash=False)
    n_comments = attr.ib(repr=False, cmp=False, hash=False)
    creation_time = attr.ib(repr=False, cmp=False, hash=False)
    update_time = attr.ib(repr=False, cmp=False, hash=False)
    source = attr.ib(default='', repr=False, cmp=False, hash=False)
    parent_id = attr.ib(default=0, repr=False, cmp=False, hash=False)
    ancestor_id = attr.ib(default=0, repr=False, cmp=False, hash=False)

这就可以了,上面说的那些 dunder 方法 (双下划线开头和结尾的方法) 都不用写了:

In : p1 = Product(1, 100001, 2003, 20, 1002393002, '这是一个测试商品1', 2000001, 100, None, 1)

In : p2 = Product(1, 100001, 2003, 20, 1002393002, '这是一个测试商品2', 2000001, 100, None, 2)

In : p3 = Product(3, 100001, 2003, 20, 1002393002, '这是一个测试商品3', 2000001, 100, None, 3)

In : p1
Out: Product(id=1, author_id=100001, brand_id=2003, spu_id=20)

In : p1 == p2
Out: True

In : p1 > p3
Out: False

In : {p1, p2, p3}
Out:
{Product(id=1, author_id=100001, brand_id=2003, spu_id=20),
 Product(id=3, author_id=100001, brand_id=2003, spu_id=20)}

In : attr.asdict(p1)
Out:
{'ancestor_id': 0,
 'author_id': 100001,
 'brand_id': 2003,
 'creation_time': 100,
 'id': 1,
 'item_id': '这是一个测试商品1',
 'n_comments': 2000001,
 'parent_id': 0,
 'source': 1,
 'spu_id': 20,
 'title': 1002393002,
 'update_time': None}

In : attr.asdict(p1, filter=lambda a, v: a.name in ('id', 'title', 'author_id'))
Out: {'author_id': 100001, 'id': 1, 'title': 1002393002}

是不是清爽直观?

当然, 我这个例子中对属性的要求比较多,所以不同属性的参数比较长。看这个类的定义的方式是不是有点像 ORM?对象和属性的关系直观,不参与类中代码逻辑。

有兴趣的可以看[Kenneth Reitz、Łukasz Langa、Glyph Lefkowitz 等人对项目的评价] ( http://www.attrs.org/en/stable/index.html#testimonials)。 。)

除此之外,attrs 还支持多种高级用法,如字段类型验证、自动类型转化、属性值不可变(Immutability)、类型注解等等 ,我列 3 个我觉得非常有用的吧

字段类型验证

业务代码中经验会对对象属性的类型和内容验证,attrs 也提供了验证支持。验证有 2 种方案:

  1. 装饰器
>>> @attr.s
... class C(object):
...     x = attr.ib()
...     @x.validator
...     def check(self, attribute, value):
...         if value > 42:
...             raise ValueError("x must be smaller or equal to 42")
>>> C(42)
C(x=42)
>>> C(43)
Traceback (most recent call last):
   ...
ValueError: x must be smaller or equal to 42
  1. 属性参数:
>>> def x_smaller_than_y(instance, attribute, value):
...     if value >= instance.y:
...         raise ValueError("'x' has to be smaller than 'y'!")
>>> @attr.s
... class C(object):
...     x = attr.ib(validator=[attr.validators.instance_of(int),
...                            x_smaller_than_y])
...     y = attr.ib()
>>> C(x=3, y=4)
C(x=3, y=4)
>>> C(x=4, y=3)
Traceback (most recent call last):
   ...
ValueError: 'x' has to be smaller than 'y'!

属性类型转化

Python 不会检查传入的值的类型,类型错误很容易发生,attrs 支持自动的类型转化:

>>> @attr.s
... class C(object):
...     x = attr.ib(converter=int)
>>> o = C("1")
>>> o.x
1

包含元数据

属性还可以包含元数据!这个真的非常有用,这个属性的值就不仅仅是一个值了,带上元数据的值非常灵活也更有意义,这样就不需要额外的把属性需要的元数据独立存储起来了。

>>> @attr.s
... class C(object):
...    x = attr.ib(metadata={'my_metadata': 1})
>>> attr.fields(C).x.metadata
mappingproxy({'my_metadata': 1})
>>> attr.fields(C).x.metadata['my_metadata']
1

通过上面支持的几个功能可以看出作者做项目就是基于实际工作痛点出发的。

dataclasses 模块

在 Python 3.7 里面会添加一个新的模块 dataclasses ,它基于 PEP 557 ,Python 3.6 可以通过 pip 下载安装使用:

pip install dataclasses

解决如上痛点,把 Product 类改成这样:

from datetime import datetime
from dataclasses import dataclass, field


@dataclass(hash=True, order=True)
class Product(object):
    id: int
    author_id: int
    brand_id: int
    spu_id: int
    title: str = field(hash=False, repr=False, compare=False)
    item_id = int = field(hash=False, repr=False, compare=False)
    n_comments = int = field(hash=False, repr=False, compare=False)
    creation_time: datetime = field(default=None, repr=False, compare=False,hash=False)
    update_time: datetime = field(default=None, repr=False, compare=False, hash=False)
    source: str = field(default='', repr=False, compare=False, hash=False)
    parent_id: int = field(default=0, repr=False, compare=False, hash=False)
    ancestor_id: int = field(default=0, repr=False, compare=False, hash=False)

先验证一下:

In : p1 = Product(1, 100001, 2003, 20, 1002393002, '这是一个测试商品1', 2000001, 100, None, 1)

In : p2 = Product(1, 100001, 2003, 20, 1002393002, '这是一个测试商品2', 2000001, 100, None, 2)

In : p3 = Product(3, 100001, 2003, 20, 1002393002, '这是一个测试商品3', 2000001, 100, None, 3)

In : p1
Out: Product(id=1, author_id=100001, brand_id=2003, spu_id=20)

In : p1 == p2
Out: True

In : p1 > p3
Out: False

In : {p1, p2, p3}
Out:
{Product(id=1, author_id=100001, brand_id=2003, spu_id=20),
 Product(id=3, author_id=100001, brand_id=2003, spu_id=20)}

In : from dataclasses import asdict

In : asdict(p1)
Out:
{'ancestor_id': 1,
 'author_id': 100001,
 'brand_id': 2003,
 'creation_time': '这是一个测试商品1',
 'id': 1,
 'parent_id': None,
 'source': 100,
 'spu_id': 20,
 'title': 1002393002,
 'update_time': 2000001}

dataclasses.asdict 不能过滤返回属性。但是总体满足需求。但是,你有没有发现什么不对?

attrs 和 dataclasses

虽然 2 种方案写的代码确实有些差别,但有木有觉得它俩很像?其实 attrs 的诞生远早于 dataclasses, dataclasses 更像是在借鉴。dataclasses 可以看做是一个强制类型注解,功能是 attrs 的子集。那么为什么不把 attrs 放入标准库,而是 Python 3.7 加入一个阉割版的 attrs 呢?

Glyph Lefkowitz 犀利的写了标题为 why not just attrs? 的 issue,我打开这个 issue 没往下看的时候,猜测是「由于 attrs 兼容 Python3.6,包含 Python2.7 的版本,进入标准库必然是一次卸掉包袱的重构,attrs 作者不同意往这个方向发展?」,翻了下讨论发现不是这样的。

这个 issue 很有意思,多个 Python 开发都参与进来了,最后 Gvanrossum 结束了讨论,明确表达不同意 attrs 进入标准库,Donald Stufft 也直接问了为啥?Gvanrossum 虽然解释了下,但是我还是觉得这算是「仁慈的独裁者」中的「独裁」部分的体现吧,Python 社区的态度一直是不太开放。包含在 PEP 557解释为什么不用 attrs ,也完全说服不了我。

我站 attrs,向大家推荐! 不仅是由于 attrs 兼容之前的 Python 版本,而是 attrs 是真的站在开发者的角度上添加功能支持,最后相信 attrs 会走的更远。

参考文章

  1. http://www.attrs.org/en/stable/index.html
  2. https://glyph.twistedmatrix.com/2016/08/attrs.html
  3. https://www.python.org/dev/peps/pep-0557/

原文: attrs 和 Python3.7 的 dataclasses

☑️ ☆

推荐 + 赠书 《Python 3学习笔记(上卷)》

前言

「如何学习编程」每个人都有自己的答案,在我初学 Python 的时候,我就非常关注大神们的学习方式和成长之路。工作这么些年过来,我发现大家入门和学习的共同点非常统一:读书、看源码、高频率的实践和动手,对于现在的同学还可以选择看视频。

在我的印象里面,大神 TJ Holowaychuk 的学习方法让我记忆深刻,大概 4-5 年前我看过一个介绍,但是找不到印象里的那篇了,只找到了这篇 TJ Holowaychuk 是怎样学习编程的? 。TJ 的学习方法很特别:

也不读书,从不去听课,我就是去阅读别人的代码,并搞清楚那些代码是如何工作的。

而《Python 3 学习笔记(上卷)》作者雨痕在我印象里面就是这样通过阅读 CPython 源代码来学习 Python 的。

qyuhen/book

雨痕前辈从 1996 年开始从事计算机软件开发工作,从 2006 年接触 Python,他的 qyuhen/book 在 2013 年的时候就已经非常知名了。这个项目下是除了 Python 笔记,还有 Go,C 方面的学习笔记。

第一次阅读《Python 学习笔记》就被它的内容吸引,虽然只是作者的学习笔记,但是依然不影响对于学习 Python 的开发者的意义,我觉得这个笔记有 2 个显著的特点:

  1. 从解释器和 CPython 源码实现的角度剖析语言语法
  2. 通过在交互环境中的实验去证明和验证细节,获得结论

可以说这本书对 13 年的我来说,有很大的帮助。当然这个笔记里面还有一些有意思的点,我在 14 年的 Python 高级编程 分享中 PPT 一上来就引用了这个笔记中提到的怎么让 Python 支持 end:

__builtins__.end = None

def test(x):
    if x > 0:
        print "a"
    else:
        print "b"
    end
end


def main():
    test(1)
    print('I can use end!')
end

嗯,这不是 ruby。其实能这么写的根本原因就是 end 被解释成了 None,写成什么都可以:

__builtins__.endif = None

def test(x):
    if x > 0:
        return True
    endif

test(1)

之后雨痕老师的 《Go 语言学习笔记》 出版了,但是《Python 学习笔记》却没有动静,我既有失望也满怀期待,想必没有考虑出版它是由于这个笔记是针对 Python2.7 的。

但是不要紧,《Python 3 学习笔记(上卷)》来了。

荐书

关注我的同学应该都知道我是不愿意参与荐书的,我得对订阅者负责。我没看过的、觉得不好的书我是不可能推荐给别人的。

目前我正式的荐书只有 《流畅的 Python》 和平时在群里说到的 《python 编程 从入门到实践》 ,《Python 3 学习笔记(上卷)》是我推荐的第三本书。节前就收到了样书,刚刚读完,给大家分享一下读后感。

本书的特点

  1. 书中内容基于目前最新的 Python 3.6 版本,书中提到的一些最新的 Python 内容还很少有中文书或者博客来介绍。在 2018 年这个节点,如果一本新书还在讲 Python 2,反正我是不会买了。
  2. 同样是从解释器和 CPython 源码实现的角度剖析语言语法。市面上大部分的教程都是在告诉你应该怎么用,但是背后隐藏的细节和原理却很少提及,这本书对这些并不避讳,而更像是想弄清楚解释器执行的流程和细节。尤其是在「解释器」章节里面除了从源码上分析 GIL,还有内存分配、垃圾回收和 Python 执行过程的方面的内容。
  3. 配图丰富、简单易懂。同作为图书作者,我很了解做配图是一件很耗时辛苦的事情,既要对配图的内容理解非常深刻,也要让它直观好懂是很难的,这本书这点做的就不错。
  4. 通过在交互环境中的实验去证明和验证细节,获得结论。这是我非常喜欢的方式,作为一个读者用这样的方式学习知识是很轻松愉快的
  5. 概念定义深刻准确。雨痕老师的编程经验非常丰富,对 Python 也很熟悉,对一些知识点的定义和理解非常好。我举 2 个例子: 1.「包和模块」。有多少人不能清晰区分他俩?我看到很多同学用这 2 个词的时候很随意,这本书里面是这么说的: 模块 (module) 是顶层代码组织单元,其提供大粒度的封装和复用... 如果说模块用于组织代码,那么包就是用来组织模块的...

    我的感觉是总结非常到位。

    借助生成器切换执行功能,改善程序的结构设计。我举书中列出的 2 个例子,第一个是生产消息模型:

def consumer():
    while True:
        v = yield
        print(f'consume: {v}')


def producer(c):
    for i in range(10, 13):
        c.send(i)



c = consumer()
c.send(None)

producer(c)
c.close()
当然作者也提到如果有多个消费者或者数据处理时间较长,还是建议使用专业的并发方案。第二个是消除回调。我们先看看异步回调的模式:
import time
import threading


def target(request, callback):
    s = time.time()
    request()
    time.sleep(2)
    callback(f'done: {time.time() -s }')


def request():
    print('start')

def callback(x):
    print(x)


def service(request, callback):
    threading.Thread(target=target, args=(request, callback)).start()
我一直不喜欢回调,这种接口设计的方式会让代码和逻辑分散开,维护性很差。如果使用生成器怎么做呢?
def request():
    print('start')
    x = yield
    print(x)


def target(fn):
    try:
        s = time.time()
        g = fn()
        g.send(None)

        time.sleep(2)
        g.send(f'done: {time.time() -s }')
    except StopIteration:
        pass


def service(fn):
    threading.Thread(target=target, args=(fn,)).start()

service(request)
这样就不需要callback性质的额外参数了,通过yield让程序逻辑看起来是串行的。

我要强调一下:这本书并不是适合入门,它假定读者已经有一定的编程和 Python 基础。所以更适合已经熟悉 Python 语言语法,使用 Python 写过程序的开发者,如果你正准备迁移 Python 2 的代码到 Python 3.6 这本书就更值得看一看了。

在上个月发布的 《爱湃森 2017 年度 Python 榜单》 中,我也把这本书放到了 2018 年最值得期待的国内出版的 Python 书籍 的第二位(那会我还没拿到样书),现在看来这本书也物超所值,对我这种 Python 老手来说,看书获取新知识的几率已经不高,但是从这本书里面我还是收获了很多。我举几个印象深刻的例子:

  1. 池化。之前没了解过,就是相同名字可能会重复出现在不同的名字空间里,就有必要共享实例,这样节约内存,也省去了创建新实例的开销,所以 Python 实现了字符串池。
  2. SimpleNamespace。之前我想快速的构建一个结构化的实例会用 namedtuple,但是缺点是它构建出来的是一个类型:
In [5]: Point = namedtuple('Point', ['x', 'y'])  # 构建出来的是一个类,而且传递参数的效果也不直观,field_names需要是一个列表或者空格分割的字符串,不方便
In [6]: p = Point(1, 2)  # 我还得实例化一下Point才能用
In [7]: p.x, p.y
Out[7]: (1, 2)

这样不方面,SimpleNamespace 就可以直接创建实例:

In [8]: from types import SimpleNamespace
In [9]: p = SimpleNamespace(x=1, y=2)
In [10]: p.x, p.y
Out[10]: (1, 2)

这样用起来就方便多了。

  1. 自定义异常类的名字。我以前自定义异常的名字比较随意,XyzException 或者 XyzError 通常有点看心情或者仿之前名字格式,这本书这样说的「内置异常多以 Error 结尾,但建议以 Exception、Error 后缀区分可修复异常和不可修复异常」,我觉得说得对...

上述这几点算是我的读书笔记,详细的还得看书中原文哦。

希望国内能出现越来越多的好书!有能力有想法的同学,可以私信我帮你联系出版社哟

赠书

我向电子工业出版社谋了 5 本《Python-3 学习笔记(上卷)》福利给大家。这次在移动端的抽奖用「抽奖助手」小程序,关注本公众号之后,长按下图,在弹出框中「选择识别图中的小程序码」进入抽奖页面即可。

原文: 推荐 + 赠书 《Python 3学习笔记(上卷)》

🔲 ☆

2017年度Python榜单

一起见证全世界 Python 的这一年

非常抱歉这个榜单到现在才发出来,主要有 2 个原因:1. 本来准备起用 http://ipython.io 这个域名;2. 域名备案时间长,造成后续事情都等;3. 对榜单运营的数据需要的精力和时间预估不够。好在赶在 1 月的尾巴上线了。

话不多说,之后我们详聊。地址是 https://annual2017.pycourses.com/

榜单内容如下:

  1. 开始页
  2. 2017 年 Star 最多的 Python 项目
  3. 2017 年最受关注的 Python 项目
  4. 2017 年最受关注的中国开发者 Python 项目
  5. 项目 / 文章介绍 系统设计入门
  6. 2017 年最受欢迎的 Web 框架
  7. 2017 年最受欢迎的爬虫框架
  8. 项目 / 文章介绍 不建议使用的 Python Web 框架
  9. 2017 年最受欢迎的 pycon 视频 10.2017 年必备的 Python 工具
  10. 2017 年要熟悉的 Python 项目
  11. 项目 / 文章介绍 知乎响应这么慢是因为使用了 Python 语言吗?
  12. 2017 年最受欢迎的 Python 开发者
  13. 2017 年最知名的中国 Python 开发者
  14. 项目 / 文章介绍 Code Like a Pythonista: Idiomatic Python
  15. 2017 年最受欢迎的 Python 英文文章
  16. 2017 年最受欢迎的 Python 中文文章
  17. 项目 / 文章介绍 Reddit Python 节点
  18. 2017 年最受欢迎的 Django 英文文章
  19. 2017 年最受欢迎的 Django 中文文章
  20. 项目 / 文章介绍 [英] 给 Django 贡献代码比你想得简单
  21. 2017 年最受关注的 podcast
  22. 2017 年最受关注的英文技术博客
  23. 2017 年最受关注的中文技术博客
  24. 项目 / 文章介绍 10 books Python developers should be reading (podcast)
  25. 2017 年最受关注的 Python 英文书籍
  26. 2017 年最受关注的 Python 中文书籍
  27. 2017 年最受关注的 Python 开源书籍
  28. 项目 / 文章介绍 10 分钟速成 Python 3
  29. 2017 年最值得关注的知乎 Python 回答
  30. 项目 / 文章介绍 关于 Python 的面试题
  31. StackOverflow 上最受关注的 10 个 Python 问题
  32. StackOverflow 上最受关注的 10 个 Python 回答
  33. 2017 年 StackOverflow 上最受关注的 10 个 Python 回答
  34. 项目 / 文章介绍 Python 2 和 Python 3 有哪些主要区别?
  35. 2017 年最受关注的 Python 新闻渠道
  36. 2017 年 Reddit 热议话题
  37. 项目 / 文章介绍 [视频] Python 高级编程
  38. 2017 年 1 月最受关注的内容(文章,项目等)
  39. 2017 年 2 月最受关注的内容(文章,项目等)
  40. 项目 / 文章介绍 python-koans
  41. 2017 年 3 月最受关注的内容(文章,项目等)
  42. 2017 年 4 月最受关注的内容(文章,项目等)
  43. 项目 / 文章介绍 Python 最佳实践 (BOBP)
  44. 2017 年 5 月最受关注的内容(文章,项目等)
  45. 2017 年 6 月最受关注的内容(文章,项目等)
  46. 项目 / 文章介绍 使用 Python 来挑战算法和数据结构
  47. 2017 年 7 月最受关注的内容(文章,项目等)
  48. 2017 年 8 月最受关注的内容(文章,项目等)
  49. 项目 / 文章介绍 Sanic(asyncio)暂时不适合生产环境
  50. 2017 年 9 月最受关注的内容(文章,项目等)
  51. 2017 年 10 月最受关注的内容(文章,项目等)
  52. 项目 / 文章介绍 FreeCodeCamp
  53. 2017 年 11 月最受关注的内容(文章,项目等)
  54. 2017 年 12 月最受关注的内容(文章,项目等)
  55. 项目 / 文章介绍 选择 python 2/3 ?
  56. 2017 年发生的重大事件时间线
  57. 感谢那些 Python 幻灯片
  58. 感谢那些老书
  59. 感谢那些不再更新的博客和它的作者们
  60. 缅怀去世 web.py 作者
  61. 2018 年最值得期待的 Python 书籍
  62. 结束页

话不多说,之后我们详聊。地址是 https://annual2017.pycourses.com/

需要注意几点:

  1. Web 端默认自动开启背景音乐(嗯,曲子都是抖音里面的),主要不要被吓到
  2. 移动设备由于有适配问题可能效果不佳,非常推荐在 PC 端浏览
  3. 这个榜单非常长(一共 62 页),有很多图片,预计需要花费 20M,非 wifi 下请谨慎打开,另外由于内容太多,第一次打开可能会慢
  4. Web 端可以玩弹幕
  5. 同样是榜单太长的缘故,可以点击右上角的「目录」了解目前的进度,以及直接跳到对应的榜单
  6. 同学如果看榜单过程中发现样式问题,欢迎截图戳我

明天我会介绍为什么要做这个榜单,以及榜单数据是怎么定的

原文: 2017年度Python榜单

🔲 ☆

Python 3新特性汇总

这篇文章灵感来源于一个新项目 A short guide on features of Python 3 for data scientists ,这个项目列出来了作者使用 Python 3 用到的一些特性。正巧我最近也想写一篇介绍 Python 3 (特指 Python 3.6+) 特色用法的文章。开始吧!

pathlib 模块

pathlib 模块是 Python 3 新增的模块,让你更方便的处理路径相关的工作。

In : from pathlib import Path
In : Path.home()
Out: PosixPath('/Users/dongweiming')  # 用户目录
In : path = Path('/user')
In : path / 'local'  # 非常直观
Out: PosixPath('/user/local')
In : str(path / 'local' / 'bin')
Out: '/user/local/bin'

In : f = Path('example.txt')
In : f.write_bytes('This is the content'.encode('utf-8'))
Out[16]: 19

In : with f.open('r', encoding='utf-8') as handle:  # open现在是方法了
....:         print('read from open(): {!r}'.format(handle.read()))
....:
read from open(): 'This is the content'

In : p = Path('touched')
In : p.exists()  # 集成了多个常用方法
Out: False
In : p.touch()
In : p.exists()
Out: True

In : p.with_suffix('.jpg')
Out: PosixPath('touched.jpg')
In : p.is_dir()
Out: False
In : p.joinpath('a', 'b')
Out: PosixPath('touched/a/b')

可迭代对象的解包

In : a, b, *rest = range(10)  # 学过lisp就很好懂了,相当于一个「everything else」
In : a
Out: 0
In : b
Out: 1
In : rest
Out: [2, 3, 4, 5, 6, 7, 8, 9]

In : *prev, next_to_last, last = range(10)
In : prev, next_to_last, last
Out: ([0, 1, 2, 3, 4, 5, 6, 7], 8, 9)

强制关键字参数

使用强制关键字参数会比使用位置参数表意更加清晰,程序也更加具有可读性,那么可以让这些参数强制使用关键字参数传递,可以将强制关键字参数放到某个参数或者单个后面就能达到这种效果:

In : def recv(maxsize, *, block):
....:
....:     pass
....:

In : recv(1024, True)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-49-8e61db2ef94b> in <module>()
----> 1 recv(1024, True)

TypeError: recv() takes 1 positional argument but 2 were given

In : recv(1024, block=True)

通配符**

我们都知道在 Python 2 时不能直接通配递归的目录,需要这样:

found_images = \
    glob.glob('/path/*.jpg') \
  + glob.glob('/path/*/*.jpg') \
  + glob.glob('/path/*/*/*.jpg') \
  + glob.glob('/path/*/*/*/*.jpg') \
  + glob.glob('/path/*/*/*/*/*.jpg')

Python3 的写法要清爽的多:

found_images = glob.glob('/path/**/*.jpg', recursive=True)

事实上更好的用法是使用 pathlib:

found_images = pathlib.Path('/path/').glob('**/*.jpg')

print

Python 3 之后 print 成为了函数,有了更多的扩展能力:

In : print(*[1, 2, 3], sep='\t')
1   2   3
In : [x if x % 3 else print('', x) for x in range(10)]
 0
 3
 6
 9
Out: [None, 1, 2, None, 4, 5, None, 7, 8, None]

格式化字符串变量

In : name = 'Fred'
In : f'My name is {name}'
Out: 'My name is Fred'

In : from datetime import *
In : date = datetime.now().date()
In : f'{date} was on a {date:%A}'
Out: '2018-01-17 was on a Wednesday'

In : def foo():
....:     return 20
....:
In : f'result={foo()}'
Out: 'result=20'

更严格的对比规范

下面这几种类型的用法在 Python 3 都是非法的:

3 < '3'
2 < None
(3, 4) < (3, None)
(4, 5) < [4, 5]
sorted([2, '1', 3])

统一 unicode 的使用

这是很多人黑 Python 2 的一点,举个例子。在 Python 2 里面下面的结果很奇怪:

In : s = '您好'

In : print(len(s))
6

In : print(s[:2])
?

Python 3 就方便了:

In : s = '您好'

In : print(len(s))
2

In : print(s[:2])
您好

合并字典

In : x = dict(a=1, b=2)
In : y = dict(b=3, d=4)
In : z = {**x, **y}
In : z
Out: {'a': 1, 'b': 3, 'd': 4}

字典可排序

Python 3 不再需要直接使用 OrderedDict:

In : {str(i):i for i in range(5)}
Out: {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}

原文: Python 3新特性汇总

🔲 ☆

使用pipenv管理你的项目

前言

刚才使用 pipenv 发现了一个 bug, 顺手提了个的 PR。无聊之下翻了下贡献者列表,貌似没有一个我国的开发者!我的普及工作任重而道远啊,我写篇文章给大家介绍下这个终极大杀器。

Python 开发者应该听过 pip、easy_install 和 virtualenv,如果看过我的书应该还知道 virtualenvwrapper、virtualenv-burrito 和 autoenv,再加上 pyvenv、venv(Python 3 标准库)、pyenv...

额,是不是有种发懵的感觉?

那么现在有个好消息,你可以只使用终极方案: pipenv + autoenv(可选)。

「终极方案」,听起来好噱头呀。给出我的理由之前我们先了解一下 Python 虚拟环境和包管理的历史吧。

历史

在 Python 发展史上出现了很多创建和发布包的工具。 当你想要把你的项目分享出去,放到 PYPI 或者其他托管服务上的时候,就需要借助这样的工具来构建和分发项目。早在 1998 年 Python 标准库内置了模块 distutils,但是只提供了有限的支持,之后社区选择通过 setuptools 这个包实现构建和发布,它自带 easy_install,能帮助你找到、下载、安装以及更新需要使用的包。不过依然功能很有限,比如不能删除包。

当你做一个专职的 Python 开发,独立的虚拟环境也是一个开发中迫切需要的功能,在这里请大家记住一个 Ian Bicking(下称 ianb) 的开发者,08 年,他开发了 virtualenv。

社区一些 Python 核心开发者和知名项目(如 Django)核心开发者也在支持和推动这件事,后来成立了 pypa (Python Packaging Authority),pypa 早期做的就是 pip - 现在最主流的安装包的工具。再提一下,ianb 也是 pip 的早期核心开发者。

不过非常遗憾,由于和社区产生了一些矛盾,ianb 很早之前就不再写 Python 项目(可见这矛盾多大呀 😮),virtualenv 也转给了其他开发者,这是 Python 社区一个极大的损失。

现在 pip 和 virtualenv 已经被大家所熟知,甚至可以说是 Python 官方的包管理和虚拟环境选择。不过其实还是有问题,我举几个例子:

  1. 必须手动安装或删除某些特定版本的包,并记得定期更新 requirements.txt 文件,以保持项目环境的一致
  2. 有时项目中需要有多个 requirements.txt 文件,比如开发时应该用 dev-requirements.txt,现有的模式不能满足这些复杂的需要
  3. 卸载包的时候只是卸载包自己,不能处理相关依赖,时间久了项目环境就混乱了

pipenv 是什么?

Pipenv 是 Python 项目的依赖管理器。其实它不是什么先进的理念和技术,如果你熟悉 Node.js 的 npm/yarn 或 Ruby 的 bundler,那么就非常好理解了,它在思路上与这些工具类似。尽管 pip 可以安装 Python 包,但仍推荐使用 Pipenv,因为它是一种更高级的工具,可简化依赖关系管理的常见使用情况。

主要特性包含:

  1. 根据 Pipfile 自动寻找项目根目录。
  2. 如果不存在,可以自动生成 Pipfile 和 Pipfile.lock。
  3. 自动在项目目录的 .venv 目录创建虚拟环境。(当然这个目录地址通过设置 WORKON_HOME 改变)
  4. 自动管理 Pipfile 新安装和删除的包。
  5. 自动更新 pip。

对于新手和老手来说都是更好的选择。

pipenv 都包含什么?

pipenv 是 Pipfile 主要倡导者、requests 作者 Kenneth Reitz 写的一个命令行工具,主要包含了 Pipfile、pip、click、requests 和 virtualenv。Pipfile 和 pipenv 本来都是 Kenneth Reitz 的个人项目,后来贡献给了 pypa 组织。Pipfile 是社区拟定的依赖管理文件,用于替代过于简陋的 requirements.txt 文件。

Pipfile 的基本理念是:

  1. Pipfile 文件是 TOML 格式而不是 requirements.txt 这样的纯文本。
  2. 一个项目对应一个 Pipfile,支持开发环境与正式环境区分。默认提供 default 和 development 区分。
  3. 提供版本锁支持,存为 Pipfile.lock。

click 是 Flask 作者 Armin Ronacher 写的命令行库,现在 Flask 已经集成了它。

接下来,我们看看怎么使用它吧

入门

pipenv 兼容 Python 2/3,我们这里以 Mac 下 Python 3 为例:

安装 pipenv

❯ brew install python3  # 如果已经安装了可以忽略
❯ python3 -m pip install --upgrade --force-reinstall pip
❯ pip3 install pipenv --user  # 推荐安装在个人目录下export PATH="/Users/dongweiming/Library/Python/3.6/bin:$PATH"  # 把用户目录下bin放在最前面,这样可以直接使用pipenv了

使用 pipenv

用一个空目录体验一下:

❯ mkdir test_pipenv
❯ cd test_pipenv
❯ pipenv install  # 创建一个虚拟环境
Creating a virtualenv for this project…
...
Installing setuptools, pip, wheel...done.

Virtualenv location: /Users/dongweiming/.virtualenvs/test_pipenv-GP_s2TW5
Creating a Pipfile for this project…
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (c23e27)!
Installing dependencies from Pipfile.lock (c23e27)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00
To activate this project's virtualenv, run the following:
 $ pipenv shell

❯ which python3
/usr/local/Cellar/python3/3.6.1/bin/python3  # 还是mac自带的Python

❯ pipenv shell  # 激活虚拟环境
Spawning environment shell (/bin/zsh). Use 'exit' to leave.
source /Users/dongweiming/.virtualenvs/test_pipenv-GP_s2TW5/bin/activate

❯ which python3  # 已经在虚拟环境里了
/Users/dongweiming/.virtualenvs/test_pipenv-GP_s2TW5/bin/python3

❯ exit  # 退出虚拟环境

❯ which python3
/usr/local/Cellar/python3/3.6.1/bin/python3

以上就是原来 virtualenv 的基本用法了。我们看一下当前目录现在是什么样子的:

❯ ls
Pipfile  Pipfile.lock

这个环境下目前什么都没有。我们安装 2 个包:

❯ pipenv install elasticsearch-dsl requests
Installing elasticsearch-dsl…
...
Adding elasticsearch-dsl to Pipfile's [packages]…
Installing requests…
...
Adding requests to Pipfile's [packages]…
  PS: You have excellent taste! ✨ 🍰 ✨
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (8d307d)!

现在 Pipfile.lock 已经更新了,包含了 elasticsearch-dsl、requests 和相关依赖的包信息。

另外如果你添加 --two 或 --three 标志到上面的最后一个命令,它分别使用 Python 2 或 3 来初始化你的项目。 否则将使用默认版本的 Python。

可以看一下依赖关系:

❯ pipenv graph
elasticsearch-dsl==6.1.0
  - elasticsearch [required: <7.0.0,>=6.0.0, installed: 6.1.1]
    - urllib3 [required: <1.23,>=1.21.1, installed: 1.22]
  - ipaddress [required: Any, installed: 1.0.19]
  - python-dateutil [required: Any, installed: 2.6.1]
    - six [required: >=1.5, installed: 1.10.0]
  - six [required: Any, installed: 1.10.0]
requests==2.18.4
  - certifi [required: >=2017.4.17, installed: 2017.11.5]
  - chardet [required: <3.1.0,>=3.0.2, installed: 3.0.4]
  - idna [required: <2.7,>=2.5, installed: 2.6]
  - urllib3 [required: <1.23,>=1.21.1, installed: 1.22]

可以看到,他俩都依赖了 urllib3。虽然现在 pipenv 不能直接卸载包及其依赖,但是由于它提供了良好的接口,我们还是可以实现:

❯ pipenv uninstall `pipenv graph --json |python3 depends.py requests`
Un-installing certifi…
Uninstalling certifi-2017.11.5:
  Successfully uninstalled certifi-2017.11.5

No package certifi to remove from Pipfile.
Un-installing requests…
Uninstalling requests-2.18.4:
  Successfully uninstalled requests-2.18.4

Removing requests from Pipfile…
Un-installing idna…
Uninstalling idna-2.6:
  Successfully uninstalled idna-2.6

No package idna to remove from Pipfile.
Un-installing chardet…
Uninstalling chardet-3.0.4:
  Successfully uninstalled chardet-3.0.4

No package chardet to remove from Pipfile.
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (c05ac4)!

其中 depends.py 脚本会解析依赖关系,排除其他包依赖的项目然后删除:

 cat depends.py
import sys
import json

package = sys.argv[1]
other_dependencies = set()
removing_dependencies = set([package])

for i in json.load(sys.stdin):
    for p in i['dependencies']:
        key = p['key']
        if i['package']['key'] == package:
            removing_dependencies.add(key)
        else:
            other_dependencies.add(key)

print(' '.join(removing_dependencies - other_dependencies))

再看一下现在环境中的包依赖关系:

❯ pipenv graph
elasticsearch-dsl==6.1.0
  - elasticsearch [required: >=6.0.0,<7.0.0, installed: 6.1.1]
    - urllib3 [required: >=1.21.1,<1.23, installed: 1.22]
  - ipaddress [required: Any, installed: 1.0.19]
  - python-dateutil [required: Any, installed: 2.6.1]
    - six [required: >=1.5, installed: 1.10.0]
  - six [required: Any, installed: 1.10.0]

是不是很干净呢?

其他功能

除了上述基本功能以外,pipenv 还有很多附加的功能,我举几个日常比较常用的例子:

❯ pipenv run which python # 「pipenv run」可以激活虚拟环境,并使用shell命令
/Users/dongweiming/.virtualenvs/test_pipenv-GP_s2TW5/bin/python
❯ pipenv check  # 检查装的包的安全性
Checking PEP 508 requirements…
Passed!
Checking installed package safety…
All good!
❯ pipenv --man  # 传统的看文档的方法
❯ pipenv check --style depends.py  # 代码Flake8检查,在这里我修改了depends.py让它故意有问题
/Users/dongweiming/test_pipenv/depends.py:1:1: F401 'os' imported but unused

另外由于 autoenv 也是 Kenneth Reitz 写的,所以 pipenv 默认也包含了对.env 文件的支持。

是不是方便很多呢?

原文: 使用pipenv管理你的项目

🔲 ☆

详解Python元类

什么是元类?

理解元类(metaclass)之前,我们先了解下 Python 中的 OOP 和类(Class)。

面向对象全称 Object Oriented Programming 简称 OOP,这种编程思想被大家所熟知。它是把对象作为一个程序的基本单元,把数据和功能封装在里面,能够实现很好的复用性,灵活性和扩展性。OOP 中有 2 个基本概念:类和对象:

  1. 类是描述如何创建一个对象的代码段,用来描述具有相同的属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法
  2. 对象是类的实例(Instance)。

我们举个例子:

In : class ObjectCreator(object):
...:     pass
...:

In : my_object = ObjectCreator()

In : my_object
Out: <__main__.ObjectCreator at 0x1082bbef0>

而 Python 中的类并不是仅限于此:

In : print(ObjectCreator)
<class '__main__.ObjectCreator'>

ObjectCreator 竟然可以被 print,所以它的类也是对象!既然类是对象,你就能动态地创建它们,就像创建任何对象那样。我在日常工作里面就会有这种动态创建类的需求,比如在 mock 数据的时候,现在有个函数 func 接收一个参数:

In : def func(instance):
...:     print(instance.a, instance.b)
...:     print(instance.method_a(10))
...:

正常使用起来传入的 instance 是符合需求的(有 a、b 属性和 method_a 方法),但是当我想单独调试 func 的时候,需要「造」一个,假如不用元类,应该是这样写:

In : def generate_cls(a, b):
...:     class Fake(object):
...:         def method_a(self, n):
...:             return n
...:     Fake.a = a
...:     Fake.b = b
...:     return Fake
...:

In : ins = generate_cls(1, 2)()

In : ins.a, ins.b, ins.method_a(10)
Out: (1, 2, 10)

你会发现这不算算是「动态创建」的:

  1. 类名(Fake)不方便改变
  2. 要创建的类需要的属性和方法越多,就要对应的加码,不灵活。

我平时怎么做呢:

In : def method_a(self, n):
...:     return n
...: 
In : ins = type('Fake', (), {'a': 1, 'b': 2, 'method_a': method_a})()

In : ins.a, ins.b, ins.method_a(10)
Out: (1, 2, 10)

到了这里,引出了 type 函数。本来它用来能让你了解一个对象的类型:

In : type(1)
Out: int

In : type('1')
Out: str

In : type(ObjectCreator)
Out: type

In : type(ObjectCreator())
Out: __main__.ObjectCreator

另外,type 如上所说还可以动态地创建类:type 可以把对于类的描述作为参数,并返回一个类。

用来创建类的东东就是「元类」,放张图吧:

MyClass = type('MyClass', (), {})

这种用法就是由于 type 实际上是一个元类,作为元类的 type 在 Python 中被用于在后台创建所有的类。在 Python 语言上有个说法「Everything is an object」。包整数、字符串、函数和类... 所有这些都是对象。所有这些都是由一个类创建的:

In : age = 35
In : age.__class__
Out: int

In : name = 'bob'
In : name.__class__
Out: str
...

现在,任何class中的特定class是什么?

In : age.__class__.__class__
Out: type

In : name.__class__.__class__
Out: type
...

如果你愿意,你可以把 type 称为「类工厂」。type 是 Python 中内建元类,当然,你也可以创建你自己的元类。

创建自己的元类

Python2 创建类的时候,可以添加一个metaclass属性:

class Foo(object):
    __metaclass__ = something...
    [...]

如果你这样做,Python 会使用元类来创建 Foo 这个类。Python 会在类定义中寻找metaclass。如果找到它,Python 会用它来创建对象类 Foo。如果没有找到它,Python 将使用 type 来创建这个类。

在 Python3 中语法改变了一下:

class Simple1(object, metaclass=something...):
    [...]

本质上是一样的。拿一个 4 年前写分享的元类例子(就是为了推荐你来阅读 😉 我的 PPT:《Python 高级编程》 )吧:

class HelloMeta(type):
    def __new__(cls, name, bases, attrs):
        def __init__(self, func):
            self.func = func
        def hello(self):
            print 'hello world'
        t = type.__new__(cls, name, bases, attrs)
        t.__init__ = __init__
        t.hello = hello
        return t

class New_Hello(object):
    __metaclass__ = HelloMeta

New_Hello 初始化需要添加一个参数,并包含一个叫做 hello 的方法:

In : h = New_Hello(lambda x: x)

In : h.func(10), h.hello()
hello world
Out: (10, None)

PS: 这个例子只能运行于 Python2。

在 Python 里new方法创建实例,init负责初始化一个实例。对于 type 也是一样的效果,只不过针对的是「类」,在上面的 HelloMeta 中只使用了new创建类,我们再感受一个使用init的元类:

In : class HelloMeta2(type):
...:     def __init__(cls, name, bases, attrs):
...:         super(HelloMeta2, cls).__init__(name, bases, attrs)
...:         attrs_ = {}
...:         for k, v in attrs.items():
...:             if not k.startswith('__'):
...:                 attrs_[k] = v
...:         setattr(cls, '_new_dict', attrs_)
...:
...:

别往下看。思考下这样创建出来的类有什么特殊的地方?

我揭晓一下(这次使用 Python 3 语法):

In : class New_Hello2(metaclass=HelloMeta2):
...:     a = 1
...:     b = True

In : New_Hello2._new_dict
Out: {'a': 1, 'b': True}

In : h2 = New_Hello2()

In : h2._new_dict
Out: {'a': 1, 'b': True}

有点明白么?其实就是在创建类的时候把类的属性循环了一遍把不是__开头的属性最后存在了_new_dict 上。

什么时候需要用元类?

日常的业务逻辑开发是不太需要使用到元类的,因为元类是用来拦截和修改类的创建的,用到的场景很少。我能想到最典型的场景就是 ORM。ORM 就是「对象 关系 映射」的意思,简单的理解就是把关系数据库的一张表映射成一个类,一行记录映射为一个对象。ORM 框架中的 Model 只能动态定义,因为这个模式下这些关系只能是由使用者来定义,元类再配合描述符就可以实现 ORM 了,现在做个预告,未来我会分享「如何写一个 ORM」这个主题。

参考资料

  1. What is a metaclass in Python?
  2. Understanding Python metaclasses

原文: 详解Python元类

🔲 ☆

Python北京开发者活动第一期PPT出炉啦

Python 北京开发者活动第一期结束了,虽然我没有参加,不过仍然第一时间拿到了主题的幻灯片分享给大家。和高大上的 Pycon 相比,这种技术技术活动更是 Python 工程师需要也是想要了解到的内容,本文我站着说话不腰疼地也对这些主题闲扯几句吧。

三个主题的 Slide 地址是: https://github.com/Python-Meetup-Peking/PMP_slide ,你也可以通过文末的「阅读原文」到达。我下面的内容会提到主讲人幻灯片中的一点内容,建议大家先完整看过 PPT 再来看本文效果会更好。

asyncio 和它的朋友们

asyncio 是 Python 3 官方的解决方案,也是 Guido van Rossum(Python 创建者,简称 GvR)主推的,我在之前的博文 我为什么不喜欢 Gevent 提到过我是比较认同 asyncio,不认可 gevent 的,我也说过 asyncio 的生态还远远没有建立,现在基本都只能算是玩一玩。asyncio 是非常有革命性意义的,直到它大家当做基本常识一样的被认可需要一个过程,这个过程看起来还比较长,我认为的原因有下面这么几点:

  1. 迁移成本太高。首先是 Python 2 到 Python 3 的迁移,并不是安装一个库就完事了,而且现在大部分公司在 Python 2 下的产品和服务运行良好,迁移得有值得的收益,所以现阶段不能说服 boss 干这件事,也让大家没了动力。
  2. 使用后的效果不突出。前年使用 asyncio 发现确实比之前的方案都要快,可以翻之前的内容,不过这个快并不是一个质的飞跃,而只是一个效率的提升,当效率没有成倍的提高的时候,你很难综合的决定为了那百分之 XX 的提升来做这么大的改变,毕竟现有的效率也没有出现瓶颈。
  3. asyncio 改变了编程习惯。asyncio 的作者们已经很努力的让开发者感受不到这种变化,但是 asyncio 用起来需要一整套的开发习惯的支持,简单地说:并不是你用了 aiohttp 你的 web 请求就都是异步非阻塞的了。请求到回应过程中某个地方没有注意阻塞了直接让效果大打折扣,但是如果你对业务以及 Python 和相关的库不熟很多时候这种问题是很难发现和杜绝的。
  4. 没有大公司出来背书,也鲜有重要项目宣称支持 asyncio 版本的驱动并及时维护。举个例子,aioredis 接口和 py-redis 参数不一致,一些常用的开源项目的更新迭代需要对应驱动也跟上,要不然开发者就会很尴尬。主题中主讲人提到了 https://github.com/aio-libs 这个组织,组织下面有些 aio 的驱动,不过这是一个民间组织,大部分开发者都是俄罗斯人,其实没有 Python 核心开发者参与的,自娱自乐成份要更多一些,质量上我是不放心的。如果哪天豆瓣上了 asyncio,我们肯定会选择自己维护一份相关的驱动,而不用社区性质的。其实在 Github 看到的大部分 asyncio 项目都不是官方的。大部分 Python 核心开发者在自己的公司也没有推这件事,只有少量的核心开发做了 asyncio 支持,比如 MongoDB 就有官方的 motor。

说了这么多消极的,那么我们能给 asyncio 这个生态做点什么?

  1. 多多用 asyncio 做项目,多写一些 asyncio 相关的文章,让更多人了解它接受它。
  2. 能力范围内造造常用工具的驱动,或者参与进来给现有项目提交 PR,你们可能不知道 Flask 刚流行那会插件系统的繁荣场景,其实做这个事情并没有那么难,整的理解 asyncio 之后,驱动就是一些套路罢了。

Python 性能调优

性能调优是开发者一直很关注的话题,在我的《Python Web 开发实战》中也介绍了很多的我常用的工具和使用方法,这个主题的内容基本覆盖了,唯一在书中没有提到的是 pyflame,不过之前在转载 Glow 的一篇《 Python web 应用性能调优 》时也提到了。

现在大家也能知道大家平时用的工具都差不多,倒没有秘籍。我其实更想说的是性能调优要求工程师平时要很关注到服务器资源的指标,及时发现问题,并且愿意花时间去分析和定位问题。

Python 在一家创业公司中的应用

清风老师早早的使用上了 Python3,羡慕。当然这也是由于没有历史包袱的原因。在老师介绍「Python3 带来了哪些好处」的时候提到:

  1. 带来了类型检查
  2. 不再被字符串编码问题折磨

第二点是很多人的痛就不提了,我来讲下对类型检查的看法。无独有偶,今年 Pycon 上海唯一能看的洪教授所在的爱因互动的主题演讲「Building Chatbot Service」中也提到了 Type Hint(当然也提到了 asyncio)。我之前提到过我不喜欢 TypeScript(当然原因复杂得多),所以答案是我对类型检查持保留意见(甚至反对)。当然这部分在一开始也在 Python 社区产生过分歧,我不喜欢的原因有几点:

  1. 我觉得 Python 就应该走简洁的路线,加上类型检查(哪怕是非强制的)让代码看起来臃肿不舒服,如果哪天 Python 强制要这么干,我就不再❤️它了。
  2. 类型错误造成的错误主要是由于工程师不了解业务需要和对自己写的代码没有充分理解才会出现,当然这还有异常处理的考虑的经验问题。靠类型检查来查出来,本质上是对工程质量的不信任。

当然类型检查这种方式可以极大的减少了代码出错的几率,让问题更早的暴露出来,这在实际生产环境中确实是有意义的,比如核心的如支付系统,为了确保万无一失,用上类型检查我还可以认同。

第二个点是老师创业选择的框架是 Django,理由是:

  1. 对于长期项目更加利于维护
  2. 第三方库众多
  3. BUG 比较少
  4. instagram 也在使用 - 之前我也发过一个翻译分享 Instagram 的工程师 Hui Ding 说到:『一直到用户 ID 已经超过了 32bit int 的限额(约为 20 亿),Django 本身仍然没有成为我们的瓶颈所在。』

这些我都是认可的,Django 确实是 Web 开发首选。大家知道我一直是推荐 Flask 的,它对于初学者友好,但是并不是让大家止步于此。我不用 Django 最重要的是我日常工作中没什么机会用 Django,而更常用 Flask 的首要的原因是我对它很熟悉,我可以随便自由地玩出花儿来,大家在实际开发的时候应该都知道都要基于业务场景对相关第三方库要进行一些定制,有些可能都不合适提交给上游而只能自己维护一个版本,所以对 Web 框架的熟悉程度是选择的重要标准。我之前记得什么地方有人说「企业开发都是用 Django,Flask 就是玩具」这样的论调,一看就是知识太狭隘,框架学的也不好,一个框架学好了,学其他的真的很轻松的。

想想为什么我没有什么机会用 Django 呢?

  1. 我个人不喜欢 Django 的大包大揽的不灵活,不用它但是我有能力「造」
  2. 没有历史遗留项目的包袱牵扯我的精力
  3. 我身边的工程师也不喜欢它,不会主动选择它
  4. 很早前阅读它的代码,它耦合度很高,和 Flask 写代码的方式差别是很明显的,这种方式倒不是不好,优点是代码利用率很高,缺点是刚了解它的人来说阅读和理解代码耗时耗力,Flask 则反之,代码调用层级明确且关系简单。

清风老师说工作中也使用了 Flask,在适当的场景下选择最适合的框架,这才是优秀工程师应该做的。

原文: Python北京开发者活动第一期PPT出炉啦

🔲 ⭐

IPython3时代到来

前言

我以前写过一些 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% 的用户影响几乎没有.

原文: IPython3时代到来

🔲 ⭐

ptpython- a better Python REPL

前言

今天发现一个项目: 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

原文: ptpython- a better Python REPL

☑️ ⭐

分享ipython notebook

前言

本来准备下一次的 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_pelican 和 divingintoipynb_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

原文: 分享ipython notebook

🔲 ☆

推荐pre-commit/pre-push

前言

使用 git 的同学想必都有这样的工作场景 - 保证生产环境的 ci 不挂。也就是检查 python 是否符合 pep8/csslint/jslint/pylint/pyflake8 等. 我在我的 emacs 配置中加入了这一项 py-autopep8 , 就是在保存缓存区的时候把当前缓存区的文本放到一个临时文件,然后执行 autopep8, 再检查 pep8/flake8

但是不能对 css/js/html 做规范检查。而且也不通用。周末看到了 Yelp 的 pre-commit . 感觉是个很有意思的东西,虽然之前也写过类似的 hook. 但是没有它灵活。看完他的源码后,我今天给大家介绍下这个东西

pre-commit

玩过 svn/git 的同学应该都知道他们有各种的 hook. 也就是准备 / 完成什么事件的时候做些额外的工作。一般是 shell 脚本,版本控制工具会判断脚本的退出码,如果不是 0, 就不会继续完成. pre-commit 顾名思义就是在 commit 之前做的准备,也就是每次执行

git commit -m 'xxx'

的时候去做一些检查。启用的插件都放到这个版本库目录的根目录下,名字叫做.pre-commit-config.yaml -> 详细文档请看: http://pre-commit.com/

这里有我的一个配置例子:

-   repo: https://github.com/pre-commit/pre-commit-hooks
    sha: b03733bc86d9e8b2564a5798ade40d64baae3055
    hooks:
    -   id: trailing-whitespace
    -   id: end-of-file-fixer
    -   id: autopep8-wrapper
    args: ['-i', '--ignore=E265,E309,E501']
    -   id: check-docstring-first
    -   id: check-json
    -   id: check-yaml
    -   id: debug-statements
    -   id: name-tests-test
    -   id: requirements-txt-fixer
    -   id: flake8
-   repo: https://github.com/pre-commit/pre-commit
    sha: 86c99c6b870a261d2aff0b4cdb36995764edce1b
    hooks:
    -   id: validate_config
    -   id: validate_manifest
-   repo: https://github.com/asottile/reorder_python_imports
    sha: ea9fa14a757bb210d849de5af8f8ba2c9744027a
    hooks:
    -   id: reorder-python-imports

安装使用

pip install pre-commit
pre-commit install
# PS: 第一次执行commit会比较慢,因为他会clone对应的源, 之后就会用这个缓存的源
# 其他的可选源和用法直接参照[https://github.com/pre-commit](https://github.com/pre-commit)里面的项目或者[http://pre-commit.com/hooks.html](http://pre-commit.com/hooks.html)

看一个失败的例子 (有颜色效果,不能展示出来)

$git commit -m 'test'

Trim Trailing Whitespace.................................................................................................................Passed
Fix End of Files.........................................................................................................................Passed
autopep8 wrapper.........................................................................................................................Passed
Check docstring is first.................................................................................................................Passed
Check JSON..........................................................................................................(no files to check) Skipped
Check Yaml..........................................................................................................(no files to check) Skipped
Debug Statements (Python)................................................................................................................Passed
Tests should end in _test.py........................................................................................(no files to check) Skipped
Fix requirements.txt................................................................................................(no files to check) Skipped
Flake8...................................................................................................................................Failed
hookid: flake8

pre_commit/__init__.py:2:1: F401 'os' imported but unused
pre_commit/__init__.py:3:1: F401 'sys' imported but unused

Validate Pre-Commit Config..........................................................................................(no files to check) Skipped
Validate Pre-Commit Manifest........................................................................................(no files to check) Skipped
Reorder python imports...................................................................................................................Passed
# 因为我的flake8有问题 所以commit失败了

pre-commit 的问题

我觉得对每次 commit 做一次审查,第一是需要时间,第二是没有必要,因为经常一个 pr 有多个 commit, 我只保证整体结果是正确的就好了 - 也就是说应该是在 push 的时候。整个过程我可能对 commit 做多次 rebase/--amend 等等。某一次的检查失败其实完全不 影响我做后的结果 - 我是手快党

so. 我基于它修改了一个版本 pre-push , 只是我对 push 做了拦截。并且我会经常和它保持同步

pre-commit install -t pre-commit # 默认安装pre-commit钩子, 每次commit触发
pre-commit install -t pre-push # 默认安装pre-push钩子, 每次push触发

其他用法完全一样.

假如 push 的时候想要不检查而强制 push, 可以加上 --no-verify 参数

Update from 2015-01-15

我的这个分支已经合并到 pre-commit . pull189

大家可以不要用我的分支了. PS: 这是我见到测试覆盖最高的项目.

原文: 推荐pre-commit/pre-push

🔲 ☆

ipython的一些高级用法(二)

今天我们学习下写 ipython 的 magic 命令。好,magic 是什么?它是 ipython 自带的一些扩展命令,类似 % history, % prun, % logstart..

想查看全部的 magic 可以使用 ismagic, 列出可用的全部 magics

%lsmagic

magic 分为 2 类:

  • line magic: 一些功能命令
  • cell magic: 主要是渲染 ipython notebook 页面效果以及执行某语言的代码
idb-pythondb.pyshellextension"> idb - python db.py shell extension

idb 是我最近写的一个 magic. 主要是给 ipython 提供 db.py 的接口,我们直接分析代码 (我只截取有代表性的一段):

import os.path
from functools import wraps
from operator import attrgetter
from urlparse import urlparse

from db import DB # db.py提供的接口
from IPython.core.magic import Magics, magics_class, line_magic # 这三个就是我们需要做magic插件的组件


def get_or_none(attr):
    return attr if attr else None


def check_db(func):
    @wraps(func)
    def deco(*args):
        if args[0]._db is None: # 每个magic都需要首页实例化过db,so 直接加装饰器来判断
            print '[ERROR]Please make connection: `con = %db_connect xx` or `%use_credentials xx` first!'  # noqa
            return
        return func(*args)
    return deco


@magics_class  # 每个magic都需要加这个magics_class装饰器
class SQLDB(Magics): # 要继承至Magics
    _db = None # 每次打开ipython都是一次实例化

    @line_magic('db_connect') # 这里用了line_magic 表示它是一个line magic.(其他2种一会再说) magic的名字是db_connect. 注意 函数名不重要
                              # 最后我们用 %db_connect而不是%conn
    def conn(self, parameter_s): # 每个这样的方法都接收一个参数 就是你在ipython里输入的内容
        """Conenct to database in ipython shell.
        Examples::
            %db_connect
            %db_connect postgresql://user:pass@localhost:port/database
        """
        uri = urlparse(parameter_s) # 剩下的都是解析parameter_s的逻辑

        if not uri.scheme:
            params = {
                'dbtype': 'sqlite',
                'filename': os.path.join(os.path.expanduser('~'), 'db.sqlite')
            }
        elif uri.scheme == 'sqlite':
            params = {
                'dbtype': 'sqlite',
                'filename': uri.path
            }
        else:
            params = {
                'username': get_or_none(uri.username),
                'password': get_or_none(uri.password),
                'hostname': get_or_none(uri.hostname),
                'port': get_or_none(uri.port),
                'dbname': get_or_none(uri.path[1:])
            }

        self._db = DB(**params) # 这里给_db赋值

        return self._db # return的结果就会被ipython接收,显示出来

    @line_magic('db') # 一个新的magic 叫做%db -- 谨防取名冲突
    def db(self, parameter_s):
        return self._db

    @line_magic('table')
    @check_db
    def table(self, parameter_s):
        p = parameter_s.split() # 可能传进来的是多个参数,但是对ipython来说,传进来的就是一堆字符串,所以需要按空格分隔下
        l = len(p)
        if l == 1:
            if not p[0]:
                return self._db.tables
            else:
                return attrgetter(p[0])(self._db.tables)
        else:
            data = self._db.tables
            for c in p:
                if c in ['head', 'sample', 'unique', 'count', 'all', 'query']:
                    data = attrgetter(c)(data)()
                else:
                    data = attrgetter(c)(data)
            return data

def load_ipython_extension(ipython): # 注册一下. 假如你直接去ipython里面加 就不需要这个了
    ipython.register_magics(SQLDB)

PS:

  1. 调试中可以使用 % reloa_ext idb 的方式重启 magic
  2. % install_ext 之后默认放在你的 ipython 自定义目录 /extensions 里。我这里是~/.ipython/extensions

好了,大家是不是觉得 ipython 的 magic 也不是很难嘛

来了解 ipython 都提供了什么?
  1. magic 装饰器的类型:
  • line_magic # 刚才我们见识了,就是 % xx, xx 就是 magic 的名字
  • cell_magic # 就是 %% xx
  • line_cell_magic # 可以是 % xx, 也可以是 %% xx

先说 cell_magic 来个例子,假如我想执行个 ruby, 本来应该是:

In [1]: !ruby -e 'p "hello"'
"hello"

In [2]: %%ruby # 也可以这样
   ...: p "hello"
      ...:
      "hello"

再说个notebook的:

In [3]: %%javascript
   ...: require.config({
   ...:     paths: {
   ...:         chartjs: '//code.highcharts.com/highcharts'
   ...:     }
   ...: });
   ...:
   <IPython.core.display.Javascript object>
});

然后再说 line_cell_magic:

In [4]: %time 2**128
CPU times: user 2 µs, sys: 1 µs, total: 3 µs
Wall time: 5.01 µs
Out[4]: 340282366920938463463374607431768211456L

In [5]: %%time
   ...: 2**128
   ...:
   CPU times: user 4 µs, sys: 0 ns, total: 4 µs
   Wall time: 9.06 µs
   Out[5]: 340282366920938463463374607431768211456L

Ps: line_cell_magic 方法的参数是 2 个:

@line_cell_magic
def xx(self, line='', cell=None):
带参数的 magic(我直接拿 ipython 源码提供的 magic 来说明):

一共 2 种风格:

  • 使用 getopt: self.parse_options
  • 使用 argparse: magic_arguments
self.parse_options
@line_cell_magic
def prun(self, parameter_s='', cell=None):
    opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q',
                                       list_all=True, posix=False)
    ...

getopt 用法可以看这里 http://pymotw.com/2/getopt/index.html#module-getopt

我简单介绍下 'D:l:rs:T:q' 就是可以使用 -D, -l, -r, -s, -T, -q 这些选项.: 号是告诉你是否需要参数,split 下就是: D:,l:,r,s:,T:,q 也就是 - r 和 - q 不需要参数其他的都是参数 类似 % prun -D

magic_arguments
@magic_arguments.magic_arguments() # 最上面
@magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE',
    help="""
    Set break point at LINE in FILE.
    """
) # 这种argument可以有多个
@magic_arguments.argument('statement', nargs='*',
    help="""
    Code to run in debugger.
    You can omit this in cell magic mode.
    """
)
@line_cell_magic
def debug(self, line='', cell=None):
    args = magic_arguments.parse_argstring(self.debug, line) # 要保持第一个参数等于这个方法名字,这里就是self.debug
    ...

还有个 magic 方法集:用于并行计算的 magics: IPython/parallel/client/magics.py

原文: ipython的一些高级用法(二)

☑️ ☆

ipython的一些高级用法(一)

前言

以前在我的 PPT python 高级编程 也提到了一些关于 ipython 的用法。今天继续由浅入深的看看 ipython, 本文作为读者的你已经知道 ipython 并且用了一段时间了.

%run

这是一个 magic 命令,能把你的脚本里面的代码运行,并且把对应的运行结果存入 ipython 的环境变量中:

$cat t.py
# coding=utf-8
l = range(5)

$ipython
In [1]: %run t.py # `%`可加可不加

In [2]: l # 这个l本来是t.py里面的变量, 这里直接可以使用了
Out[2]: [0, 1, 2, 3, 4]
alias
In [3]: %alias largest ls -1sSh | grep %s
In [4]: largest to
total 42M
 20K tokenize.py
 16K tokenize.pyc
8.0K story.html
4.0K autopep8
4.0K autopep8.bak
4.0K story_layout.html

PS 别名需要存储的,否则重启 ipython 就不存在了:

In [5]: %store largest
Alias stored: largest (ls -1sSh | grep %s)

下次进入的时候 % store -r

bookmark - 对目录做别名
In [2]: %pwd
Out[2]: u'/home/vagrant'

In [3]: %bookmark dongxi ~/shire/dongxi

In [4]: %cd dongxi
/home/vagrant/shire/dongxi_code

In [5]: %pwd
Out[5]: u'/home/vagrant/shire/dongxi_code'
ipcluster - 并行计算

其实 ipython 提供的方便的并行计算的功能。先回答 ipython 做并行计算的特点:

1.

$wget http://www.gutenberg.org/files/27287/27287-0.txt

第一个版本是直接的,大家习惯的用法.

In [1]: import re

In [2]: import io

In [3]: non_word = re.compile(r'[\W\d]+', re.UNICODE)

In [4]: common_words = {
   ...: 'the','of','and','in','to','a','is','it','that','which','as','on','by',
   ...: 'be','this','with','are','from','will','at','you','not','for','no','have',
   ...: 'i','or','if','his','its','they','but','their','one','all','he','when',
   ...: 'than','so','these','them','may','see','other','was','has','an','there',
   ...: 'more','we','footnote', 'who', 'had', 'been',  'she', 'do', 'what',
   ...: 'her', 'him', 'my', 'me', 'would', 'could', 'said', 'am', 'were', 'very',
   ...: 'your', 'did', 'not',
   ...: }

In [5]: def yield_words(filename):
   ...:     import io
   ...:     with io.open(filename, encoding='latin-1') as f:
   ...:         for line in f:
   ...:             for word in line.split():
   ...:                 word = non_word.sub('', word.lower())
   ...:                 if word and word not in common_words:
   ...:                     yield word
   ...:

In [6]: def word_count(filename):
   ...:     word_iterator = yield_words(filename)
   ...:     counts = {}
   ...:     counts = defaultdict(int)
   ...:     while True:
   ...:         try:
   ...:             word = next(word_iterator)
   ...:         except StopIteration:
   ...:             break
   ...:         else:
   ...:             counts[word] += 1
   ...:     return counts
   ...:

In [6]: from collections import defaultdict # 脑残了 忘记放进去了..
In [7]: %time counts = word_count(filename)
CPU times: user 88.5 ms, sys: 2.48 ms, total: 91 ms
Wall time: 89.3 ms

现在用 ipython 来跑一下:

ipcluster start -n 2 # 好吧, 我的Mac是双核的

先讲下 ipython 并行计算的用法:

In [1]: from IPython.parallel import Client # import之后才能用%px*的magic

In [2]: rc = Client()

In [3]: rc.ids # 因为我启动了2个进程
Out[3]: [0, 1]

In [4]: %autopx # 如果不自动 每句都需要: `%px xxx`
%autopx enabled

In [5]: import os # 这里没autopx的话 需要: `%px import os`

In [6]: print os.getpid() # 2个进程的pid
[stdout:0] 62638
[stdout:1] 62636

In [7]: %pxconfig --targets 1 # 在autopx下 这个magic不可用
[stderr:0] ERROR: Line magic function `%pxconfig` not found.
[stderr:1] ERROR: Line magic function `%pxconfig` not found.

In [8]: %autopx # 再执行一次就会关闭autopx
%autopx disabled

In [10]: %pxconfig --targets 1 # 指定目标对象, 这样下面执行的代码就会只在第2个进程下运行

In [11]: %%px --noblock # 其实就是执行一段非阻塞的代码
   ....: import time
   ....: time.sleep(1)
   ....: os.getpid()
   ....:
Out[11]: <AsyncResult: execute>

In [12]: %pxresult # 看 只返回了第二个进程的pid
Out[1:21]: 62636

In [13]: v = rc[:] # 使用全部的进程, ipython可以细粒度的控制那个engine执行的内容

In [14]: with v.sync_imports(): # 每个进程都导入time模块
   ....:     import time
   ....:
importing time on engine(s)

In [15]: def f(x):
   ....:     time.sleep(1)
   ....:     return x * x
   ....:

In [16]: v.map_sync(f, range(10)) # 同步的执行

Out[16]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

In [17]: r = v.map(f, range(10)) # 异步的执行

In [18]: r.ready(), r.elapsed # celery的用法
Out[18]: (True, 5.87735)

In [19]: r.get() # 获得执行的结果
Out[19]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

入正题:

In [20]: def split_text(filename):
....:    text = open(filename).read()
....:    lines = text.splitlines()
....:    nlines = len(lines)
....:    n = 10
....:    block = nlines//n
....:    for i in range(n):
....:        chunk = lines[i*block:(i+1)*(block)]
....:        with open('count_file%i.txt' % i, 'w') as f:
....:            f.write('\n'.join(chunk))
....:    cwd = os.path.abspath(os.getcwd())
....:    fnames = [ os.path.join(cwd, 'count_file%i.txt' % i) for i in range(n)] # 不用glob是为了精准
....:    return fnames

In [21]: from IPython import parallel

In [22]: rc = parallel.Client()

In [23]: view = rc.load_balanced_view()

In [24]: v = rc[:]

In [25]: v.push(dict(
   ....:     non_word=non_word,
   ....:     yield_words=yield_words,
   ....:     common_words=common_words
   ....: ))
Out[25]: <AsyncResult: _push>

In [26]: fnames = split_text(filename)

In [27]: def count_parallel():
   .....:     pcounts = view.map(word_count, fnames)
   .....:     counts = defaultdict(int)
   .....:     for pcount in pcounts.get():
   .....:         for k, v in pcount.iteritems():
   .....:             counts[k] += v
   .....:     return counts, pcounts
   .....:

In [28]: %time counts, pcounts = count_parallel() # 这个时间包含了我再聚合的时间
CPU times: user 47.6 ms, sys: 6.67 ms, total: 54.3 ms # 是不是比直接运行少了很多时间?
Wall time: 106 ms # 这个时间是

In [29]: pcounts.elapsed, pcounts.serial_time, pcounts.wall_time
Out[29]: (0.104384, 0.13980499999999998, 0.104384)

更多地关于并行计算请看这里: Parallel Computing with IPython

原文: ipython的一些高级用法(一)

🔲 ⭐

python几个特别的__开头的方法

前言

A Guide to Python's Magic Methods python 的绝大多数这样的特殊方法都 在这里面被提到了。今天我来说 3 个他没有提到的[dir, slots, weakref], 再强调下他提到的 2 个[missing, contains]

dir-> 看个小例子就知道了
In [1]: class T(object):
   ...:     pass
   ...:
In [2]: t = T()
In [3]: t.<Tab>
啥也没有...
In [4]: class T2(object):
   ...:     def __dir__(self):
   ...:         return ['a', 'b']
   ...:
In [5]: t = T2()
In [6]: t.
t.a  t.b
In [7]: dir(t)
Out[7]: ['a', 'b']

看出来了把,不解释,但是这个dir是相对于类的实例有效果的.

slots

这个在我初学 python 的时候就被模糊了,原来的理解是它的出现替代了dict,也就是说你只能给slots这个变量列表项的属性赋值。对外的接口减少了,也安全了。后来看了这篇 Saving 9 GB of RAM with Python’s slots . 好久不做运维了,在生产环境究竟怎么样我无法定论,也提到了,在对象实例很多的时候他能帮助减少内存,详见 https://www.safaribooksonline.com/library/view/python-cookbook-3rd/9781449357337/ch08s04.html . 这里来个小实验 (在 Hacker News 也被讨论过 https://news.ycombinator.com/item?id=6750187 )

代码例子 (我对细节做注释):

# coding=utf-8
import sys
from itertools import starmap, product


class SlotTest(object):
    # __slots__ = ['x', 'y', 'z'] 主要对比去掉这句和包含这句程序内存占用

    def __init__(self, x, y, z):
            self.x = x
                    self.y = y
                            self.z = z

    def __str__(self):
            return "{} {} {}".format(self.x, self.y, self.z)

p = product(range(10000), range(20), [4]) # 创建0-1000 & 0-20 & 4 的笛卡尔积
a = list(starmap(SlotTest, p)) # 相当于对每个SlotTest实例化,实例化的格式是p的长度

print a[0]
sys.stdin.read(1)

结果对比:

$pmap -x `ps -ef|grep test_slot.py|grep -v grep|awk '{print $2}'`|grep total # 未使用__slots__
  total kB          103496   76480   73728
$pmap -x `ps -ef|grep test_slot.py|grep -v grep|awk '{print $2}'`|grep total # 使用了__slots__
  total kB           49960   22888   20136

结果很明显,内存占用减少了很多...

weakref弱引用

首先先说下 weakref : 弱引用,与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收. 在 Python 中,当一个对象的引用数目为 0 的时候,才会被从内存中回收。但是被循环引用呢?

In [1]: import weakref

In [2]: import gc

In [3]: class Obj(object):
   ...:     def a(self):
   ...:         return 1
   ...:
In [4]: obj = Obj()

In [5]: s = obj

In [6]: gc.collect() # 不可达引用对象的数量
Out[6]: 3

In [7]: print s is obj
True

In [8]: obj = 1 # 最初的被引用的对象改变了.

In [9]: gc.collect()
Out[9]: 0

In [10]: s is None # s还是指向了Obj 引用计数为1
Out[10]: False

In [11]: s
Out[11]: <__main__.Obj at 0x2b36510>

----华丽的分割一下

In [12]: obj = Obj()

In [13]: r = weakref.ref(obj) # 让obj变成那个弱引用

In [14]: gc.collect()
Out[14]: 211

In [15]: r() is obj
True

In [16]: obj = 1

In [17]: gc.collect()
Out[17]: 0

In [18]: r() is None # 弱引用计数器没有增加,所以当obj不在引用Obj的时候,Obj对象就被释放了
Out[18]: True

好吧,我的总结是弱引用是个好东西,但是加了slots就不支持弱引用了。所以需要weakref

In [9]: class T3(object):
   ...:     __slots__ = []
      ...:

In [10]: class T4(object):
   ....:     __slots__ = '__weakref__'  # 这样就支持了weakref
      ....:

In [11]:  import weakref

In [12]: t3 = T3()

In [13]: t4 = T4()

In [14]: weakref.ref(t3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-bdb7ab7ac3bc> in <module>()
----> 1 weakref.ref(t3)

TypeError: cannot create weak reference to 'T3' object

In [15]: weakref.ref(t4)
Out[15]: <weakref at 0x2766f70; to 'T4' at 0x2586fd8>
contains判断某值 in/not in 实例
In [1]: class NewList(object):
   ...:     def __init(self, values):
   ...:         self.values = values
   ...:     def __contains__(self, value):
   ...:         return value in self.values
   ...:
In [2]: l = NewList([1, 2, 3, 4])

In [3]: 4 in l
Out[3]: True

In [4]: 10 in l
Out[4]: False
missing

最初看这个特殊方法是看 python 标准库的源码的时候 (collections#L421):

class Counter(dict):
    ...

    def __missing__(self, key):
        'The count of elements not in the Counter is zero.'
        # Needed so that self[missing_item] does not raise KeyError
        return 0

什么意思呢?

In [6]: c = collections.Counter({'a':1})

In [7]: c['b'] # 没有键的count设置默认值0
Out[7]: 0

很多人可能看过这个 (关于 defaultdict 的 ppt)[ http://discorporate.us/jek/talks/defaultdict/ ]. 内容就不说了,讲的非常好.

原文: python几个特别的__开头的方法

🔲 ☆

一个使用python的web程序员的emacs.d

前言

越来越多的人使用 emacs 作为开发工具。甚至 skype,gmail, 豆瓣 FM 都能通过 emacs. 作为一个产品开发,肯定使用很多插件,设置一些快捷键来提高开发效率。以前一直使用 prelude , 很久之后发现有以下问题:

  1. 比如开启 python 语言支持需要在 prelude-modules.el 里面把 python 这样的注释去掉
  2. 我不需要支持这么多的语言,也不需要那么多快捷键
  3. aotupair 实在太难用了
  4. scss/css 模式不好自定义缩进空格数,tab 和空格混用。不好定制
  5. 看过源码后发现,其实很来很简单粗暴的事情弄得有点复杂了

我造了个轮子 .emacs.d , 主要针对 python 和 web 开发

Update

2014-09-28, 经过这一个月的继续研究,已经有了很大的改变

项目目录结构

├── Cask ; 我使用[cask](https://github.com/cask/cask)做包管理工具
├── auto-insert ; 使用auto-insert设置新增elisp/python文件自动添加基于yasnippet的模板
│   ├── elisp-auto-insert
│   └── python-auto-insert
├── custom ; 自定义插件目录,你也可以把你写的程序放进来然后在init.el里面require
│   ├── flycheck.el ; 定制flycheck,让它在保存python程序时自动执行pep8和flake8,有问题的条目会打开新的buffer打印出来
│   └── py-autopep8.el ; 我自己实现了autopep8插件,保存时自动根据pep8标准处理文件
├── functions.el ; 用到的相关函数
├── helper.el ; 我自己写了个类似`C-h b`的介绍绑定的快捷键的预览表
├── hs-minor-mode-conf.el ; python函数/类折叠
├── init.el ; emacs启动的主程序
├── keys.el ; Key-chord配置,默认被注释了,因为它和我经常大片粘贴代码中代码重复造成很多麻烦
├── local-settings.el ; 本机的本地配置,比如用户名,单独的快捷键等
├── misc.el ; 对emacs本身的一些配置
├── mode-mappings.el ; 模式映射,比如Cask会自动用emacs-lisp-mode
├── modeline.el ; 我重新定制了modeline,使用了nyan-mode和powerline,一些加颜色的hack
├── osx.el ; Mac下的一些独立配置,为我的hhkb定制
├── smartparens-config.el ; 定制了smartparens配置
├── tmp
│   └── README.md
└── xiaoming-theme.el ; 我自己写了一个主题,好吧 我就是`小明`

使用的插件列表

  1. f - 处理文件相关的库
  2. s - 处理字符串相关的库
  3. ag - 据说比 ack 更快的文本搜索工具 the_silver_searcher 的 emacs 插件
  4. ht - 处理哈希相关的库
  5. anzu - 显示当前匹配文本,预览替换效果和总匹配数的插件
  6. dash - 常用函数集合
  7. helm - 方便查找各种文件内容,buffer 切换,emacs 命令执行等
  8. jedi - python 代码补全,快速需要函数 / 模块定义的插件
  9. smex - M-x 的命令行补全的功能
  10. direx - 展示目录树
  11. magit - git 插件
  12. slime - commonlisp 交互模式
  13. ac-js2 - js2-mode 支持 js 函数定义查找
  14. rinari - 依赖,需要安装
  15. diff-hl - 在行首用颜色表示 git 状态 - 只支持图形界面的 emacs
  16. dired-k - 用带不同颜色的高亮显示文件 / 目录,大小等信息
  17. bind-key - 本项目绑定快捷键的用法都根据这个包,没有用 global-set-key
  18. css-mode - css-mode
  19. js2-mode - js-mode 的升级版
  20. web-mode - 前端开发必备,html 缩进,支持根据 tag / 元素 / 属性 /block/dom 跳转,语法高亮,支持 mako,jinja2 等模板
  21. git-blame - git-blame, 单独版
  22. key-chord - 可以快速按键达到快捷键的作用
  23. nyan-mode - 一直可爱的小猫
  24. plim-mode - 我写的编辑 plim 的 major-mode
  25. powerline - 提供一个漂亮的状态栏
  26. sass-mode - 编辑 sass
  27. scss-mode - 编辑 scss
  28. sublimity - 在图形界面的 emacs 能缩小预览代码 - sublime-text 有类似的插件
  29. undo-tree - 让 undo 可视化
  30. yaml-mode - 编辑 yaml
  31. yasnippet - 一个神奇的模板系统,定义缩写并通过 tab 键自动帮你展开 (一些自动的 "填空题" 机制)
  32. drag-stuff - 可以将代码块整体拖动
  33. helm-swoop - 项目内关键词查找,并能自动跳到对应文件和对应行
  34. ibuffer-vc - 支持版本空的 ibuffer 模式
  35. projectile - 管理项目,可快速访问项目里任何文件,支持全项目关键词搜索
  36. coffee-mode - 编辑 coffee
  37. python-mode - 编辑 python
  38. smartparens - 自动括号匹配,可以按块删除,tag 跳转
  39. use-package - 本项目引用包的方式
  40. crontab-mode - 高亮编辑 crontab
  41. golden-ratio - 黄金分割展示当前 window
  42. helm-ipython - helm 的 ipython 插件
  43. rainbow-mode - 在代码中通过背景色标示颜色值
  44. ace-jump-mode - 快速让光标位置到你想去的地方
  45. expand-region - 按层次块区域选择
  46. helm-css-scss - helm 的 css/scss 插件
  47. markdown-mode - 编辑 markdown
  48. switch-window - 可视化切换窗口
  49. visual-regexp - 可视化正则匹配
  50. gitconfig-mode - 单独的 gitconfig-mode
  51. gitignore-mode - 单独的 gitignore-mode
  52. helm-descbinds - 让默认的 C-h b 高亮并且按组分开
  53. imenu-anywhere - 类似于 etag, 可直接跳到对应的标签
  54. multiple-cursors - 一次编辑多处 / 行文字
  55. discover-my-major - 告诉你当前 mode 的一些说明 / 快捷键设置
  56. virtualenvwrapper - virtualenvwrapper
  57. gitattributes-mode - 独立的 gitattributes-mode
  58. rainbow-delimiters - 对内嵌的括号等 pair 符号加不同颜色
  59. idle-highlight-mode - 在设置的一段设置时间未操作电脑会自动高亮当前关键词,并且全文高亮相同关键词
  60. exec-path-from-shell - 可以使用 $PATH 环境变量
  61. find-file-in-repository - 根据 git 属性在项目里查找文件
  62. emmet-mode - 类似于 zencoding,但是能编辑 css, 使用很少的代码就能构造一个复杂的 div/css
  63. browse-kill-ring - 查看最近操作的删除文本,以及恢复后的效果

安装使用

curl -fsSkL https://raw.github.com/cask/cask/master/go | python
git clone https://github.com/dongweiming/emacs.d .emacs.d
cd .emacs.d
cask
sudo pip install jedi pep8 autopep8 flake8

快捷键分布

请参看项目的 README.md

原文: 一个使用python的web程序员的emacs.d

🔲 ☆

python的魔法二

python 的魔法 (-) 之基础知识,我们再来说说 python 开发中的坑 "> 有了第一篇 python 的魔法 (-) 之基础知识,我们再来说说 python 开发中的坑

不要使用可变对象作为函数默认值

In [1]: def append_to_list(value, def_list=[]):
   ...:         def_list.append(value)
   ...:         return def_list
   ...: 

In [2]: my_list = append_to_list(1)

In [3]: my_list
Out[3]: [1]

In [4]: my_other_list = append_to_list(2)

In [5]: my_other_list
Out[5]: [1, 2] # 看到了吧,其实我们本来只想生成[2] 但是却把第一次运行的效果页带了进来

In [6]: import time

In [7]: def report_arg(my_default=time.time()):
   ...:         print(my_default)
   ...:     

In [8]: report_arg() # 第一次执行
1399562371.32

In [9]: time.sleep(2) # 隔了2秒

In [10]: report_arg()
1399562371.32 # 时间竟然没有变

这 2 个例子说明了什么?字典,集合,列表等等对象是不适合作为函数默认值的。因为这个默认值实在函数建立的时候就生成了,每次调用都是用了这个对象的 "缓存". 我在上段时间的分享 python 高级编程 也说到了这个问题,这个是实际开发遇到的问题,好好检查你学过的代码,也许只是问题没有暴露

可以这样改:

def append_to_list(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to

生成器不保留迭代过后的结果

In [12]: gen = (i for i in range(5))

In [13]: 2 in gen
Out[13]: True

In [14]: 3 in gen
Out[14]: True

In [15]: 1 in gen
Out[15]: False # 1为什么不在gen里面了? 因为调用1->2,这个时候1已经不在迭代器里面了,被按需生成过了

In [20]: gen = (i for i in range(5))

In [21]: a_list = list(gen) # 可以转化成列表,当然a_tuple = tuple(gen) 也可以

In [22]: 2 in a_list
Out[22]: True

In [23]: 3 in a_list
Out[23]: True

In [24]: 1 in a_list # 就算循环过,值还在
Out[24]: True

lambda 在闭包中会保存局部变量

In [29]: my_list = [lambda: i for i in range(5)]

In [30]: for l in my_list:
   ....:         print(l())
   ....:     
4
4
4
4
4

这个问题还是上面说的 python 高级编程 中说过具体原因。其实就是当我赋值给 my_list 的时候,lambda 表达式就执行了 i 会循环,直到 i =4,i 会保留

但是可以用生成器

In [31]: my_gen = (lambda: n for n in range(5))

In [32]: for l in my_gen:
   ....:         print(l())
   ....:     
0
1
2
3
4

也可以坚持用 list:

In [33]: my_list = [lambda x=i: x for i in range(5)] # 看我给每个lambda表达式赋了默认值

In [34]: for l in my_list:
   ....:         print(l())
   ....:     
0
1
2
3
4

有点不好懂是吧,在看看 python 的另外一个魔法:

In [35]: def groupby(items, size):
   ....:     return zip(*[iter(items)]*size)
   ....: 

In [36]: groupby(range(9), 3)
Out[36]: [(0, 1, 2), (3, 4, 5), (6, 7, 8)]

一个分组的函数,看起来很不好懂,对吧?我们来解析下这里

In [39]: [iter(items)]*3
Out[39]: 
[<listiterator at 0x10e155fd0>,
 <listiterator at 0x10e155fd0>,
 <listiterator at 0x10e155fd0>] # 看到了吧, 其实就是把items变成可迭代的, 重复三回(同一个对象哦), 但是别忘了,每次都.next(), 所以起到了分组的作用
 In [40]: [lambda x=i: x for i in range(5)]
Out[40]: 
[<function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>] # 看懂了吗?

在循环中修改列表项

In [44]: a = [1, 2, 3, 4, 5]

In [45]: for i in a:
   ....:     if not i % 2:
   ....:         a.remove(i)
   ....:         

In [46]: a
Out[46]: [1, 3, 5] # 没有问题

In [50]: b = [2, 4, 5, 6]

In [51]: for i in b:
   ....:      if not i % 2:
   ....:          b.remove(i)
   ....:         

In [52]: b
Out[52]: [4, 5] # 本来我想要的结果应该是去除偶数的列表

思考一下,为什么 -- 是因为你对列表的 remove, 影响了它的 index

In [53]: b = [2, 4, 5, 6]

In [54]: for index, item in enumerate(b):
   ....:     print(index, item)
   ....:     if not item % 2:
   ....:         b.remove(item)
   ....:         
(0, 2) # 这里没有问题 2被删除了
(1, 5) # 因为2被删除目前的列表是[4, 5, 6], 所以索引list[1]直接去找5, 忽略了4
(2, 6)

IndexError - 列表取值超出了他的索引数

In [55]: my_list = [1, 2, 3, 4, 5]

In [56]: my_list[5] # 根本没有这个元素
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-56-037d00de8360> in <module>()
----> 1 my_list[5]

IndexError: list index out of range # 抛异常了

In [57]: my_list[5:] # 但是可以这样, 一定要注意, 用好了是trick,用错了就是坑啊
Out[57]: []

重用全局变量

In [58]: def my_func():
   ....:         print(var) # 我可以先调用一个未定义的变量
   ....:     

In [59]: var = 'global' # 后赋值

In [60]: my_func() # 反正只要调用函数时候变量被定义了就可以了
global

In [61]: def my_func():
   ....:     var = 'locally changed'
   ....:     

In [62]: var = 'global'

In [63]: my_func()

In [64]: print(var)
global # 局部变量没有影响到全局变量

In [65]: def my_func():
   ....:         print(var) # 虽然你全局设置这个变量, 但是局部变量有同名的, python以为你忘了定义本地变量了
   ....:         var = 'locally changed'
   ....:         

In [66]: var = 'global'

In [67]: my_func()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-67-d82eda95de40> in <module>()
----> 1 my_func()

<ipython-input-65-0ad11d690936> in my_func()
      1 def my_func():
----> 2         print(var)
      3         var = 'locally changed'
      4 

UnboundLocalError: local variable 'var' referenced before assignment

In [68]: def my_func():
   ....:         global var # 这个时候得加全局了
   ....:         print(var) # 这样就能正常使用
   ....:         var = 'locally changed' 
   ....:     

In [69]: var = 'global'

In [70]: 

In [70]: my_func()
global

In [71]: print(var)
locally changed # 但是使用了global就改变了全局变量

拷贝可变对象

In [72]: my_list1 = [[1, 2, 3]] * 2

In [73]: my_list1
Out[73]: [[1, 2, 3], [1, 2, 3]]

In [74]: my_list1[1][0] = 'a' # 我只修改子列表中的一项

In [75]: my_list1
Out[75]: [['a', 2, 3], ['a', 2, 3]] # 但是都影响到了

In [76]: my_list2 = [[1, 2, 3] for i in range(2)] # 用这种循环生成不同对象的方法就不影响了

In [77]: my_list2[1][0] = 'a'

In [78]: my_list2
Out[78]: [[1, 2, 3], ['a', 2, 3]]

原文: python的魔法二

❌