阿布云

你所需要的,不仅仅是一个好用的代理。

关于Python 程序改善的几个小建议(一)

阿布云 发表于

18.png

建议 1:理解 Pythonic 概念

Pythonic

Tim Peters 的 《The Zen of Python》相信学过 Python 的都耳熟能详,在交互式环境中输入import this可以查看,其实有意思的是这段 Python 之禅的源码:

d = {}

for c in (65, 97):

    for i in range(26):

        d[chr(i+c)] = chr((i+13) % 26 + c)

 

print "".join([d.get(c, c) for c in s])

哈哈哈,相信这是大佬在跟我们举反例吧。

书中还举了一个快排的例子:

def quicksort(array):

    less = []

    greater = []

    if len(array) <= 1:

        return array

    pivot =array.pop()

    for x in array:

        if x <= pivot:

            less.append(x)

        else:

            greater.append(x)

    return quicksort(less) + [pivot] + quicksort(greater)

代码风格

通过对语法、库和应用程序的理解来编写代码,充分体现 Python 自身的特色:

# 变量交换

a, b = b, a

# 上下文管理

with open(path, 'r') as f:

    do_sth_with(f)

# 不应当过分地追求奇技淫巧

a = [1, 2, 3, 4]

a[::-1] # 不推荐。好吧,自从学了切片我一直用的这个

list(reversed(a))   # 推荐

然后表扬了 Flask 框架,提到了 generator 之类的特性尤为 Pythonic,有个包和模块的约束:

包和模块的命名采用小写、单数形式,而且短小

包通常仅作为命名空间,如只含空的__init__.py文件

建议 2:编写 Pythonic 代码

命名的规范:

def find_num(searchList, num):

    for listValue in searchList:

        if num == listValue:

            return True

        else:

            pass

尝试去通读官方手册,掌握不断发展的新特性,这将使你编写代码的执行效率更高,推荐深入学习 Flask、gevent 和 requests。

建议 3:理解 Python 与 C 语言的不同之处

提到了三点:

  • Python 使用代码缩进的方式来分割代码块,不要混用 Tab 键和空格

  • Python 中单、双引号的使用

  • 三元操作符:x if bool else y

建议 4:在代码中适当添加注释

这一点已经受教了,现在编写代码都会合理地加入块注释、行注释和文档注释,可以使用__doc__输出。

建议 5:通过适当添加空行使代码布局更为优雅、合理

建议 6:编写函数的 4 个原则

  1. 函数设计要尽量短小,嵌套层次不宜过深

  2. 函数申明应该做到合理、简单、易于使用

  3. 函数参数设计应该考虑向下兼容

  4. 一个函数只做一件事,尽量保证函数语句粒度的一致性

Python 中函数设计的好习惯还包括:不要在函数中定义可变对象作为默认值,使用异常替换返回错误,保证通过单元测试等。

# 关于函数设计的向下兼容

def readfile(filename):         # 第一版本

    pass

def readfile(filename, log):    # 第二版本

    pass

def readfile(filename, logger=logger.info):     # 合理的设计

    pass

最后还有个函数可读性良好的例子:

 

def GetContent(ServerAdr, PagePath):

    http = httplib.HTTP(ServerAdr)

    http.putrequest('GET', PagePath)

    http.putheader('Accept', 'text/html')

    http.putheader('Accept', 'text/plain')

    http.endheaders()

    httpcode, httpmsg, headers = http.getreply()

    if httpcode != 200:

        raise "Could not get document: Check URL and Path."

    doc = http.getfile()

    data = doc.read()       # 此处是不是应该使用 with ?

    doc.close

    return data

def ExtractData(inputstring, start_line, end_line):

    lstr = inputstring.splitlines()             # split

    j = 0

    for i in lstr:

        j += 1

        if i.strip() == start_line: slice_start = j

        elif i.strip() == end_line: slice_end = j

    return lstr[slice_start:slice_end]

def SendEmail(sender, receiver, smtpserver, username, password, content):

    subject = "Contented get from the web"

    msg = MIMEText(content, 'plain', 'utf-8')

    msg['Subject'] = Header(subject, 'utf-8')

    smtp = smtplib.SMTP()

    smtp.connect(smtpserver)

    smtp.login(username, password)

    smtp.sendmail(sender, receiver, msg.as_string())

    smtp.quit()

建议 7:将常量集中到一个文件

在 Python 中应当如何使用常量:

通过命名风格提醒使用者该变量代表常量,如常量名全部大写

通过自定义类实现常量功能:将存放常量的文件命名为constant.py,并在其中定义一系列常量

 

class _const:

    class ConstError(TypeError): pass

    class ConstCaseError(ConstError): pass

    

    def __setattr__(self, name, value):

        if self.__dict__.has_key(name):

            raise self.ConstError, "Can't change const.%s" % name

        if not name.isupper():

            raise self.ConstCaseError, \

                    'const name "%s" is not all uppercase' % name

        self.__dict__(name) = value

import sys

sys.modules[__name__] = _const()

import const

const.MY_CONSTANT = 1

const.MY_SECOND_CONSTANT = 2

const.MY_THIRD_CONSTANT = 'a'

const.MY_FORTH_CONSTANT = 'b'

其他模块中引用这些常量时,按照如下方式进行即可:

from constant import const

print(const.MY_CONSTANT)

建议 8:利用 assert 语句来发现问题

>>> y = 2

>>> assert x == y, "not equals"

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

AssertionError: not equals

>>> x = 1

>>> y = 2

# 以上代码相当于

>>> if __debug__ and not x == y:

...     raise AssertionError("not equals")

...

Traceback (most recent call last):

  File "<stdin>", line 2, in <module>

AssertionError: not equals

运行是加入-O参数可以禁用断言。

 

建议 9:数据交换的时候不推荐使用中间变量

>>> Timer('temp = x; x = y; y = temp;', 'x = 2; y = 3').timeit()

0.059251302998745814

>>> Timer('x, y = y, x', 'x = 2; y = 3').timeit()

0.05007316499904846

对于表达式x, y = y, x,在内存中执行的顺序如下:

  1. 先计算右边的表达式y, x,因此先在内存中创建元组(y, x),其标识符和值分别为y, x及其对应的值,其中y和x是在初始化已经存在于内存中的对象

  2. 计算表达式左边的值并进行赋值,元组被依次分配给左边的标识符,通过解压缩,元组第一标识符y分配给左边第一个元素x,元组第二标识符x分配给左边第一个元素y,从而达到交换的目的

下面是通过字节码的分析:

>>> import dis

>>> def swap1():

...     x = 2

...     y = 3

...     x, y = y, x

...

>>> def swap2():

...     x = 2

...     y = 3

...     temp = x

...     x = y

...     y = temp

...

>>> dis.dis(swap1)

  2           0 LOAD_CONST               1 (2)

              3 STORE_FAST               0 (x)

  3           6 LOAD_CONST               2 (3)

              9 STORE_FAST               1 (y)

  4          12 LOAD_FAST                1 (y)

             15 LOAD_FAST                0 (x)

             18 ROT_TWO                             # 交换两个栈的最顶层元素

             19 STORE_FAST               0 (x)

             22 STORE_FAST               1 (y)

             25 LOAD_CONST               0 (None)

             28 RETURN_VALUE

>>> dis.dis(swap2)                                                                                                                                    

  2           0 LOAD_CONST               1 (2)

              3 STORE_FAST               0 (x)

  3           6 LOAD_CONST               2 (3)

              9 STORE_FAST               1 (y)

  4          12 LOAD_FAST                0 (x)

             15 STORE_FAST               2 (temp)

  5          18 LOAD_FAST                1 (y)

             21 STORE_FAST               0 (x)

  6          24 LOAD_FAST                2 (temp)

             27 STORE_FAST               1 (y)

             30 LOAD_CONST               0 (None)

             33 RETURN_VALUE

建议 10:充分利用 Lazy evaluation 的特性

def fib():

    a, b = 0, 1

    while True:

        yield a

        a, b = b, a + b

哈哈哈,我猜到肯定是生成器实现菲波拉契序列的例子,不过对比我写的版本,唉。。。

建议 11:理解枚举替代实现的缺陷

利用 Python 的动态特征,可以实现枚举:

# 方式一

class Seasons:

    Spring, Summer, Autumn, Winter = range(4)

# 方式二

def enum(*posarg, **keysarg):

    return type("Enum", (object,), dict(zip(posarg, range(len(posarg))), **keysarg))

Seasons = enum("Spring", "Summer", "Autumn", Winter=1)

Seasons.Spring

# 方式三

>>> from collections import namedtuple

>>> Seasons = namedtuple('Seasons', 'Spring Summer Autumn Winter')._make(range(4))

>>> Seasons.Spring

0

# 但通过以上方式实现枚举都有不合理的地方

>>> Seasons._replace(Spring=2)                                             │

Seasons(Spring=2, Summer=1, Autumn=2, Winter=3)  

# Python3.4 中加入了枚举,仅在父类没有任何枚举成员的时候才允许继承

建议 12:不推荐使用 type 来进行类型检查

作为动态语言,Python 解释器会在运行时自动进行类型检查并根据需要进行隐式类型转换,当变量类型不同而两者之间又不能进行隐式类型转换时便抛出TypeError异常。

>>> def add(a, b):

...     return a + b

...

>>> add(1, 2j)

(1+2j)

>>> add('a', 'b')

'ab'

>>> add(1, 2)

3

>>> add(1.0, 2.3)

3.3

>>> add([1, 2], [3, 4])

[1, 2, 3, 4]

>>> add(1, 'a')

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

  File "<stdin>", line 2, in add

TypeError: unsupported operand type(s) for +: 'int' and 'str'

所以实际应用中,我们常常需要进行类型检查,但是不推荐使用type(),因为基于内建类型扩展的用户自定义类型,type()并不能准确返回结果:

 

class UserInt(int):

    def __init__(self, val=0):

        self._val = int(val)

    def __add__(self, val):

        if isinstance(val, UserInt):

            return UserInt(self._val + val._val)

        return self._val + val

    def __iadd__(self, val):

        raise NotImplementedError("not support operation")

    def __str__(self):

        return str(self._val)

    def __repr__(self):

        return "Integer %s" % self._val

>>> n = UserInt()

>>> n

Integer 0

>>> print(n)

0

>>> m = UserInt(2)

>>> print(m)

2

>>> type(n) is int

False                   # 显然不合理

>>> isinstance(n, int)

True

我们可以使用isinstance来检查:isinstance(object, classinfo)