地道的 Python

Posted windmissing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了地道的 Python相关的知识,希望对你有一定的参考价值。

作者: Zhang Yang

逼格

水果公司的一句 “The bigger than bigger” 广告词,使得逼格进入了大众的视野。内置逼格神器的程序猿们的内心一定是向往着更高的逼格。试问自己心里是否憧憬着这样一个场景:

当你随手写下一行代码的时候,旁边的人不可思议的睁大眼睛,表示着原来代码可以这样写?

这是怎样一种装X之旅。Python 可以帮助你实现这样的愿望。

Python 对于外行们来说,它都非常容易上手。同时,对于我们这些使用C语言开蒙,甚至工作中主要使用C语言的程序员们来说,使用它却有着天然的障碍。这让苦逼的程序员们如何在外行面前保持尊严?别着急,请接着看。


惯用语

学习过英语的大家都知道,我们现在说的英语(English),实际上是 Chinglish,而印度人则说着一种叫 Hinglish 的语言,这也是为什么我们听好莱坞原声大片的时候,要比听中国人说英语要困难得多。每一种语言,每一群人,都会有自己的习惯用法 – 惯用语。程序语言也是同样的道理,特别是众多的高级语言,它们都是有着自己独特的那部分东西,这是它们被需要或者不需要的原因。就像蹩脚的程序员,用 C语言的习惯写 C++ 代码一样,编译执行都没有问题,但是你干嘛不直接写 C 文件?!

我们来看一看下面这片代码:

def count_person_c()
    f = open(‘data.csv‘)
    try:
        data = f.read()
    finally:
        f.close()

    groups = {}
    lines = data.splitlines()
    for i in xrange(len(lines)):
        name = lines[i].split(‘,‘)[0]
        age = lines[i].split(‘,‘)[1]
        sex = lines[i].split(‘,‘)[2]
        yow = lines[i].split(‘,‘)[3]
        salary = lines[i].split(‘,‘)[4]
        tax = lines[i].split(‘,‘)[5]
        bonus = lines[i].split(‘,‘)[6]

        if( sex == ‘Male‘ and int(age) > 60 ) or
          ( sex == ‘Female‘ and int(age) > 55 ):
            person = int(yow) * ( int(salary) - int(tax) + int(bonous) ) * 0.9
            if name not in groups:
                groups[name] = []
            groups[name].append(person)

    for key in groups:
        print key, ‘--->‘, groups[key]

这是使用 Python 的语法完成的,还可以使用 Python 解释器执行。但是,请再仔细看一遍,是否觉着似曾相识?这块代码除了使用了 Python 的原生字典类型,还有什么是 Python 的东西?这样的逼格得和马里亚纳海沟差不多的高度了。

要追求更高的逼格, Python 从来都是从 人 而不是 机器 的角度来思考问题的,后面我们会不断见到这样的例子。

蛋痛定律

如果有一件事让你蛋痛,那么一定已经有蛋痛人帮助你完成它。


赋值

例子中最亮瞎钛合金眼球的就是那 7 行整齐的赋值语句了。从机器的角度看,它们是一个又一个的赋值语句,非常完美。但是从 人 的角度看,这些东西看起来会让人蛋痛:明明是从同一块数据中抠出来的数值,为什么程序语言要求要用这么行来获取它?实际上,这样的代码,在 C 语言以及其他很多语言中,被认为是不可避免的,程序员们能做的就是让它们看起来比较整齐,别太疼而已。

蛋痛的 Python 人并不认为这应该是很多步的赋值,而应该是一个拆包(unpacking)的语句,所以应该一行就完全实现,如下:

name, age, sex, yow, salary, tax, bonus = lines[i].split(‘,‘)

这里说一句小编的个人感受,如果 Python 的代码里常常看到形如 name[i], name[1], name[key]的形式,说明楼主的 机器 之毒已经相当深厚了。一定要消灭它们,且一定有办法消灭它们,逼格实在够low。

回到正文,类似的例子,程序员们在学习计算机语言的的时候,一定都被老师要求写过一段程序,来交换两个变量的值;蛋痛的程序员们有时候还被要求不能使用第三个变量缓存。这样蛋痛的事情,依然还在很多学校里不断地发生。Python 里蛋痛的人们认为数值交换,就应该是一个简单的语句实现,超过一行以上的语句就是在侮辱自己的智商,所以在 Python 里我们可以这样:

a, b = b, a

循环

蛋痛的程序员们在学习 C 语言的时候,就被暗示: 循环是需要通过索引值得+1/-1完成数值递推的,所以i, j, k成为了程序中最最常见的变量名,大家几乎一看到它们就会条件反射般认为,它们应该是某个循环中的索引(Clean Code一书就明确这么说的)。但实际上,循环遍历一溜东东并不必然需要索引,它反而会带来一些混乱。几乎是写每个循环是都要额外考虑:索引到底是从0开始,还是从1开始;结束的地方是否要-1。看官们回想一下,在做线性代数家庭作业中向量计算的时候,你的脑子里使用过索引吗?是的,不需要,用眼睛看,手指头比划就行了。只是该死的 图灵机 需要索引,看到索引 人 就会蛋痛的。蛋痛的 Python 就这样循环:

 for d in [1, 2, 3, 4]:

变量 a 就是分别会成为1、2、3、4,这里索引是不需要的,会逼着 人 以 机器 的角度思考问题,增加不必要的难度。反对者们一定会说,索引可以让循环以反序遍历。请淡定,如下可以实现4、3、2、1的遍历。

D = [1, 2, 3, 4]
for d in D[::-1]:

尽管这样,蛋痛的事还是会发生,有些时候程序真的需要索引,特别是需要把索引值作为参数给循环体内调用的函数时,怎么办?蛋痛的 Python 还有方案:enumerate(colors),它能返回一对值: 索引, 变量,如下所示。

for i,d in enumerate([1, 2, 3, 4]):

Python 的 for 还有一个神奇的地方,被称为 for-else。程序中,常常有这样的场景:循环遍历某个链表,如果找到想要的成员则立刻返回它的位置,否则一定找完所有元素。

ages = [42, 21, 18, 33, 19]

are_all_adult = True
for age in ages:
    if age < 18:
        are_all_adult = False
        break
if are_all_adult:
    print ‘All are adults!‘

如果是 C 语言,这还是不错的。但是在 Python 看来,这也有让人蛋痛的地方,因为它可以是这样的:

ages = [42, 21, 18, 33, 19]
for age in ages:
    if age < 18:
        break
else
    print ‘All are adults!‘

当循环中的break没有被执行过,最后一次循环会进入到else里执行;蛋痛的人们这它也起了一个蛋痛的名字,更贴切的说法应该是un-break。
当然,Python 还有另一个更高逼格的方法实现这个功能,只需要两行代码,后文会提到。


字典

前面说的都是常见的列表循环,Python 自带的独特的数据类型:字典的循环,变量的值只是字典的键值而没有条目的内容:

for key in groups:
   print key, ‘--->‘, groups[key]

看到groups[key],蛋痛再次袭来。是的,Python 里字典的循环,不应该是这个样子的,它把自己设计成这样:

for key, value in groups.items():
   print key, ‘--->‘, value

是的,就是你看到的这个意思,键值和条目内容是成对返回的;这样还一个可能的额外好处:当字典变得非常庞大的时候,将items()改成interitems(),可以非常有效的改进 Python 的性能,还节约内存,这个叫迭代器,后文会提到。

说到字典,关注下面这片代码:

groups = {}
    ...
    if name not in groups:
        groups[name] = []
    groups[name].append(person)

这是一个 C 程序员写的典型 Python 代码,不但有蛋痛的groups[name],还有多层缩进,判断,赋值空列表,简直蛋痛加三级。根据蛋痛定律,一定有蛋痛的人帮助解决这样的问题:

groups = {}
...
   groups.setdefault(name, []).append(pension)

或者是

groups = defaultdict(list)
...
   groups[name].append(pension)

只有当name作为键值第一次出现时,字典groups会自动调用函数list建立一个空列表;如果已经存在了,则直接调用列表的append行为。小编更喜欢第二种写法,插入的时候代码更简洁明了。现在怎么看,怎么觉得 groups = {} 是在定义变量,蛋痛啊… …

以上是关于地道的 Python的主要内容,如果未能解决你的问题,请参考以下文章

地道的 Python

Python 工匠:编写地道循环的两个建议

怎么样编写地道的Kotlin代码

怎么样编写地道的Kotlin代码

怎么样编写地道的Kotlin代码

怎么样编写地道的Kotlin代码