怎样才能写出Pythonic 的代码?

社区编辑2018-05-18 11:25


近来,身边的一些Python 大牛们老是提到一个很时髦的词:Pythonic,但却很少人说得清楚它是个什么意思,搞得新童鞋一头雾水;

在我们周围有很多资深的工程师,用其他语言写过很多的代码,做过很多项目,却还是会被吐槽写的Python 代码不够Pythonic;

那究竟这个Pythonic 是什么意思?我们怎样才能写出Pythonic 的代码呢?

什么是Pythonic

 
在回答什么是Pythonic 之前,我们先来看一下Python 程序员每天津津乐道的the Zen of Python(Python 之禅)。

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

 

简单翻译几句就是:
优美胜于丑陋
明了胜于晦涩
简介胜于复杂
……
可读性很重要
……

 
What?!这个有点形而上的Python 之禅,换成任何一门其他编程语言似乎也是适用的啊?
 
其实,它给出的更多的还是一个对代码的评判标准。什么做法好,什么做法不好,我们把好的部分做到,把不好的部分避免掉,写出来的其实就是很Pythonic 的代码了。
 
当然,这对于解释什么是Pythonic 还是有点不够清楚。我们换一种方式。
 
从字面上去理解,英语中 -ic 这个后缀一般意为「属于…的,有…特征的」,「Pythonic 的代码」我们可以简单理解为 「有Python 特征的代码」。如果觉得译的有点生硬的话,结合上面的描述,优化一些给出的定义就为:Pythinic 就是以Python 的方式写出简洁优美的代码。
 

如何写出Pythonic 的代码

 
首先,无论使用什么语言,要做到Pythonic,基本前提还是写出的代码足够简洁优美。如果你目前还做不到,这里推荐两本教程《重构》和《代码整洁之道》,虽然这两本书使用的是java 语言,但并不影响作者要传递的思想和理念。
 

 
其次,在你已经能够写出简洁优美的代码的前提下,要写出Pythonic 的代码,还需要对Python 语言有比较好的了解。
 
上面我们提到,一些做过很多项目的资深工程师也经常会被吐槽写的Python 代码不够Pythonic,其实是因为大家对Python 的认识还有些偏差。一些工程师会认为Python 比较简单,所以没有经过系统性的学习,简单了解了一点Python 的语法后就开始写C风格的Python 代码了,所以写出的代码才不够Pythonic。
 
其实,Python 语言有着自己独特的语法和语言特性,对Python 有个更深入的了解将有助于大家更好地做到Pythonic。下面我举几个在Python 里与在C、C++ 和Java 里有显著差别的例子,来让大家更细致地了解Python 语言逻辑与语言特性。
 

1.交换两个数字

在其他语言里面
 

 t = a
 a = b
 b = t

 
在Python 语言里面
 

a, b = b, a

 

2.列表推导

 
列表推导是C、C++、Java 里面没有的语法,但Python 里面使用非常广泛,是特别推崇的用法。与列表推导对应的,还有集合推导和字典推导。我们来演示一下。
 
列表:30~40 所有偶数的平方

[ i*i for i in range(30, 41) if i% 2 == 0 ]

集合:1~20所有奇数的平方的集合

{ i*i for i in range(1, 21) if i % 2 != 0 }

字典:30~40 所有奇数的平方

{ i:i*i for i in range(30, 40) if i% 2 != 0 }

再举个实用的例子:
 
当前用户home 目录下所有的文件列表

[ item for item in os.listdir(os.path.expanduser('~')) if os.path.isfile(item) ]

当前用户home 目录下所有的目录列表
 

[ item for item in os.listdir(os.path.expanduser('~')) if os.path.isdir(item) ]

当前用户home 目录下所有目录的目录名到绝对路径之间的字典

{ item: os.path.realpath(item) for item in os.listdir(os.path.expanduser('~')) if os.path.isdir(item) }

 

3.上下文管理器

我们要打开文件进行处理,在处理文件的过程中可能会出错,但是,我们需要在处理文件出错的情况下,也顺利关闭文件。
 
Java 风格/C++ 风格的Python 代码:

myfile= open(r'C:\misc\data.txt')
try:
    for line in myfile:
        ...use line here...
finally:
    myfile.close()
 
Pythonic 的代码:
with open(r'C:\misc\data.txt') as myfile:
    for line in myfile:
        ...use line here...

这里要说的是,上下文管理器是Python 里面比较推荐的方式,如果用try…finally 而不用with,就会被认为不够Pythonic。此外,上下文管理器还可以应用于锁和其他很多类似必须要关闭的地方。

4.装饰器

装饰器并不是Python 特有,只是在Python 里面应用非常广泛,我们来看一个例子。
 
考虑这样一组函数,它们在被调用时需要对某些参数进行检查,在本例中,需要对用户名进行检查,以判断用户是否有相应的权限进行某些操作。

class Store(object):
    def get_food(self, username, food):
        if username != 'admin':
            raise Exception("This user is not allowed to get food")
        return self.storage.get(food)

    def put_food(self, username, food):
        if username != 'admin':
            raise Exception("This user is not allowed to put food")
        self.storage.put(food)

 
显然,代码有重复,作为一个有追求的工程师,我们严格遵守DRY(Don’t repeat yourself) 原则,于是,代码被改写成了这样:

def check_is_admin(username):
    if username != 'admin':
        raise Exception("This user is not allowed to get food")

class Store(object):
    def get_food(self, username, food):
        check_is_admin(username)
        return self.storage.get(food)

    def put_food(self, username, food):
        check_is_admin(username)
        return self.storage.put(food)

 
现在代码整洁一点了,但是,有装饰器能够做的更好:

def check_is_admin(f):
    def wrapper(*args, **kwargs):
        if kwargs.get('username') != 'admin':
            raise Exception("This user is not allowed to get food")
        return f(*arg, **kargs)
    return wrapper

class Storage(object):
    @check_is_admin
    def get_food(self, username, food):
        return self.storage.get(food)

    @check_is_admin
    def put_food(self, username, food):
        return storage.put(food)

 
在这里,我们使用装饰器,就可以把参数检查和业务逻辑完全分离开来,让代码显得更加清晰,这也是比较Pythonic 的代码。

5.动态类型语言

我们再来看一个例子,该例子充分演示了动态类型语言与静态类型语言编程之间的差异。
 
在某些情境中,我们会收到很多不同的请求,对于不同的请求,调用不同的请求处理函数。这是个比较常见的需求,那么相信大家对下面的代码不会陌生:

if (cmd == 'a')
    processA()
else if (cmd == 'b')
    processB()
else if (cmd == 'c')
    processC()
else if (cmd == 'd')
    processD()
……
else
    raise NotImplementException

 
在Python 里面,我们可以先判断一个类,有没有这个函数,如果有,则获取这个函数,然后再调用。所以,我们的代码可以写成这样:

class A:
    def fetch_func(self, action_name):
        func= getattr(self, action_name, None)
        return func

    def execute(self, action, msg):
        func= self.fetch_func(action)
        if func is None:
            return False, "Action not found"
        return func(action, msg)

 
结论:所谓的Pythonic,其实就是写出简洁优美的代码。这种思想在每种语言中都一样,如果你用其他编程语言写不出简洁优美的代码,那么你也很难用Python 写得出。当然,如果你能用其他语言写出很好的代码,那还是有必要了解下Python 这门语言特有的一些语法和语言特性,对其能做到灵活运用。如此,就能够写出Pythonic 的代码了。