编写漂亮的,可读的Python代码的最佳实践

参考: Raymond Hettinger在PyCon US 2015上的演讲 Beyond PEP 8 – Best practices for beautiful intelligible code

通常的一些方式如list comprehension在此就不列举了,主要补充一些其他方式

将非pythonic的代码做一层Adapter变成pythonic的

先看一段的代码:

import jnettool.tools.elements.NetworkElement
import jnettool.tools.Routing
import jnettool.tools.RouteInspector

ne = jnettool.tools.elements.NetworkElement('x.x.x.x')

try:
    routing_table = ne.getRoutingTable()
except jnettoll.tools.elements.MissingVar:
    logging.exception('No routing table found')
    ne.cleanup('rollback')
else:
    num_routes = routing_table.getSize()
    for RToffset in range(num_routes):
        route = routing_table.getRouteByIndex(RToffset)
        name = route.getName()
        ipaddr = route.getIpAddr()
        print "%15s => %s" % (name, ipaddr)
finally:
    ne.cleanup('commit')
    ne.disconnect()

这段代码初看起来并没有什么问题,也的确没有什么问题,肯定能够正常运行,而且完全符合PEP 8标准。然而这看上去更像是一段Java的代码,带有浓烈的Java色彩,如getter, setter,getXXXByIndex,还有try…except…finally这一系列处理异常的方式(当然大多数语言也都是这样子),但这和Python的简约风格相去甚远。我们先来看一下理想的pythonic的版本长的样子:

from nettools import NetworkElement

#使用with去除了冗长的try...except...finally
with NetworkElement('x.x.x.x') as ne:   
    #使用python中sequence类型遍历的特性,代替原来的类似下标形式
    for route in routing_table:
        #直接使用变量名替代getter函数
        print "%15s => %s" % (route.name, route.ipaddr)    

就这样,4行代码搞定! 那如何才能将之前的冗长代码变身为这样呢?你需要做一层pythonic的封装(Adapter):

import jnettool.tools.elements.NetworkElement
import jnettool.tools.Routing

class NetworkElementError(Exception):
    pass

class NetworkElement(object):
    def __init__(self, ipaddr):
        self.ipaddr = ipaddr
        self.oldne = jnettool.tools.elements.NetworkElement(ipaddr)

    @property
    def routing_table(self):
        try:
            return RoutingTable(self.oldne.getRoutingTable())
        except jnettool.tools.elements.MissingVar:
            raise NetworkElementError('No routing table found')

    def __enter__(self):
        return self

    def __exit__(self, exctype, excinst, exctb):
        if exctype == NetworkElementError:
            logging.exception('No routing table found')
            self.oldne.cleanup('rollback')
        else:
            self.oldne.cleanup('commit')
        self.oldne.disconnect()

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.ipaddr)

class RoutingTable(object):
    def __init__(self, oldrt):
        self.oldrt = oldrt

    def __len__(self):
        return self.oldrt.getSize()

    def __getitem__(self, index):
        if index >= len(self):
            raise IndexError
        return Route(self.oldrt.getRouteByIndex(index))

class Route(object):
    def __init__(self, old_route):
        self.old_route = old_route

    @property
    def name(self):
        return self.old_route.getName()

    @property
    def ipaddr(self):
        return self.old_route.getIpAddr()

简要描述下这个Adapter都做了什么

  • 使用@property decorator来完成getter函数的功能,使用上将函数变成了变量。如果需要setter则需再加上如@name.setter,对赋值的参数进行验证等操作
  • 实现了__enter__, __exit__函数用于支持with,隐藏了try…except…finally
  • 实现了__len__, __getitem__使得对象具有了sequence类型的遍历特性

通过以上这一连串的修改,成功地将原本Java味如此重的代码pythonic化了

未完待续。。。

comments powered by Disqus