读改善python程序的91个建议总结

建议一 字符串格式化,让可读性高一些

print 'Hello %S' %('Chen',)
==> print 'Hello %(name)s!' %{'name': 'Chen'}


value = {'greet': 'Hello world', 'language': 'Python'}
print '%(greet)s from %(language)s.' % value
==>  print '{greet} from {language}'.format(greet = 'Hello world', language = Python'')

建议二 命名规则

1.避免只用大小写来区分不同的对象
2.避免使用容易引起混淆的名称比如(0o混淆,l1混淆)
3.不要害怕过长的变量名。

安装pep8

pip install -U pep8

检测代码

pep8 --first optparse.py

显示详细不符合pep8的代码

pep8 --show-source --show-pep8 test.py

建议六 编写函数的4个原则

1.函数设计要尽量短小,嵌套层次不宜过深,if elif while for 等循环判断的,最好能控制在3层以内。
2.函数声明应该做到合理、简单、易于使用,参数个数不宜过多,。
3.函数参数设计应该考虑向下兼容,比如一个函数,多加了一个参数,来记录功能的日志,要加一个默认参数,便于兼容。
4.一个函数只干一件事,尽量保证函数语句粒度的一致性,
python中函数设计的好习惯好包括:不要再函数中定义可变对象作为默认值,使用异常替换返回错误,保证通过单元测试等。

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

通过命名风格来提醒使用者该变量代表的意义为常量,如常量名所有字母大写,用下划线连接各个单词,如MAX_OVERFLOW. 然而这种方式并没有实现真正的常量,其对应的值仍然可以改变,这只是一种约定俗成的风格。

通过自定义的类实现常量功能。这要求符合”命名全部为大写”和”值一旦绑定便不可再修改”这两个条件。下面是一种较为常见的解决方法,他通过对常量对应的值进行修改时或者命名不符合规范时抛出异常来满足以上变量的两个条件。

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.ConstError, "const name '%s' is not all uppercase" % name
        self.__dict__[name] = value
import sys
sys.modules[__name__] = _const()

如果上面的代码对应的模块名为const,使用的时候只需要import const,便可以直接定义常量了, 如以下代码:

import const
const.COMPANY = "IBM"

利用assert语句来发现问题

断言在很多语言中都存在,在python中的语法是:

assert expression1 ["," expression2]

其中计算expression1的值会返回True或者False, 当值为False的时候会引发AssertionError,而expression2是可选的,常用来传递具体的异常信息。

python -O test.py 便可以禁用断言

1.不要滥用,这是使用断言最基本的原则。
2.如果python本身的异常能够处理就不要再使用断言。
3.不要使用断言来检查用户的输入
4.在函数调用后,当需要确认返回值是否合理时可以使用断言
5.当条件是业务逻辑继续下去的先决条件时可以使用断言

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

c = a
a = b
b = c

==> a,b = b,a

运行两者效率不一样:

In [1]: from timeit import Timer

In [2]: Timer('c = x;x=y;y=c','x = 2; y=3')
Out[2]: <timeit.Timer instance at 0x7f58a8273830>

In [3]: Timer('c = x;x=y;y=c','x = 2; y=3').timeit()
Out[3]: 0.03874492645263672

In [4]: Timer('x,y = y,x', 'x=2;y=3').timeit()
Out[4]: 0.03464508056640625

In [5]:

建议十 充分利用 Lazy evaluation的特性

1.避免不必要的计算,带来性能上的提升,比如 if x and y ,在x为false的情况下y表达式的值不再计算,二对于if x or y, 当x的值为true的时候将直接返回,不再计算y的值。所以当我们循环一个比较大的数据,先从可能性比较高的一方去循环他,从而节省了时间,提高了效率

  1. 节省空间,使得无限循环的数据结构成为可能。python中最典型的使用延迟计算的例子就是生成器表达式了,它仅在每次需要计算的时候通过yield产生所需要的元素。

    def fib():

    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b
    

    from itertools import islice
    print list(islice(fib(), 5))
    [0, 1, 1, 2, 3]

建议12 不推荐使用type()

基于内建类型扩展的用户自定义类型,type()函数并不能准确返回结果。
这时候用 isinstance(object, classinfo)

isinstance(2, int)

建议13 当涉及触发运算的时候尽量先将操作数转换为浮点类型再做运算

建议14 eval()不安全

__import__("os").system("dir")

你的当前目录下的所有文件都出来了

建议15 is 和 “==”

is的作用是用来检查对象的标识符是否一致的,也就是比较两个对象在内存中是否拥有同一块内存空间,它并不适合用来判断两个字符串是否相等;而”==”才是用来检验两个对象的值是否相等的,它实际调用内部eq()方法。

建议17 考虑兼容性,尽可能使用Unicode

Python内建的字符串有两种类型:str和Unicode,它们拥有共同的祖先basestring。其中Unicode是Python2.0中引入的一种新的数据类型,所有的Unicode字符串都是Unicode类型的实例。创建一个Unicode字符相对简单。

因此要解决乱码问题可以使用Unicode作为中间介质来完成转换。首先需要对读入的字符用UTF-8进行解码,然后再用GBK进行编码。修改后的结果如下:

filehandle = open("test.txt",'r')
print (filehandle.read().decode("utf-8")).encode("gbk")
filehandle.close()

关于BOM:
Unicode存储有字节序的问题,例如“汉”字的Unicode编码是0X6C49,如果将6C写在前面,则为big endian,将49写在前面则成为little endian。UTF-16以两个字节为编码单元,在字符的传送过程中,为了标明字节的顺序,Unicode规范中推荐使用BOM(Byte Order Mark):即在 UCS编码中用一个叫做ZERO WIDTH NO-BREAK SPACE的字符,它的编码是FEFF(该编码在UCS中不存在对应的字符),UCS规范建议在传输字节流前,先传输字符ZERO WIDTH NO-BREAK SPACE。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。 UTF-8使用字节来编码,一般不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符ZERO WIDTH NO-BREAK SPACE的UTF-8编码是EF BB BF。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

示例二分析:Python中默认的编码是ASCII编码(这点可以通过sys.getdefaultencoding()来验证),所以unicodetest.py文件是以ASCII形式保存的,s是包含中文字符的普通字符串。当调用print方法输出的时候会隐式地进行从ASCII到系统默认编码(Windows上为CP936)的转换,中文字符并不是ASCII字符,而此时源文件中又未指定其他编码方式,Python解释器并不知道如何正确处理这种情况,便会抛出异常:SyntaxError: Non-ASCII character ‘\xd6’ in file unicodetest.py on line 1。因此,要避免这种错误需要在源文件中进行编码声明,声明可用正则表达式
“coding[:=]\s*([-\w.]+)”表示。一般来说进行源文件编码声明有以下3种方式:

第一种声明方式:

# coding=<encoding name>

第二种声明方式:

#!/usr/bin/python
# -*- coding: <encoding name> -*-

第三种声明方式:

#!/usr/bin/python
# vim: set fileencoding=<encoding name> :

建议18:构建合理的包层次来管理module

什么是包呢?简单说包即是目录,但与普通目录不同,它除了包含常规的Python文件(也就是模块)以外,还包含一个init.py文件,同时它允许嵌套。包结构如下:
Package/ init.py
Module1.py
Module2.py
Subpackage/ init.py
Module1.py
Module2.py

以下是一个可供参考的Python项目结构:

ProjectName/
|---README
|----LICENSE
|----setup.py
|-----requirements.txt
|------sample/
|    |----__init__.py
|    |----core.py
|    |----helpers.py
|------docs/
|    |------conf.py
|    |------index.rst
|------bin/
|------package/
|    |-----__init__.py
|    |-----subpackage/
|    |------........
|------tests/
|    |------test_basic.py
|    |------test_advanced.py

建议19:有节制地使用from…import语句

在使用import的时候注意以下几点:
·一般情况下尽量优先使用import a形式,如访问B时需要使用a.B的形式。
·有节制地使用from a import B形式,可以直接访问B。
·尽量避免使用from a import *,因为这会污染命名空间,并且无法清晰地表示导入了哪些对象

为什么在使用import的时候要注意以上几点呢?在回答这个问题之前先来简单了解一下Python的import机制。Python在初始化运行环境的时候会预先加载一批内建模块到内存中,这些模块相关的信息被存放在sys.modules中。读者导入sys模块后在Python解释器中输入sys.modules.items()便可显示所有预加载模块的相关信息。当加载一个模块的时候,解释器实际上要完成以下动作:
1)在sys.modules中进行搜索看看该模块是否已经存在,如果存在,则将其导入到当前局部命名空间,加载结束。
2)如果在sys.modules中找不到对应模块的名称,则为需要导入的模块创建一个字典对象,并将该对象信息插入sys.modules中。
3)加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译。
4)执行动态加载,在当前模块的命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中。

建议20:优先使用absolute import来导入模块

假设有如下文件结构,其中app/sub1/string.py中定义了一个lower()方法,那么当在mod1.py中import string之后再使用string.lower()方法时,到底引用的是sub1/string.py中的lower()方法,还是Python标准库中string里面的lower()方法呢?

app/
__init__.py
sub1/
__init__.py
mod1.py
string.py
sub2/
__init__.py
mod2.py

从程序的输出会发现,它引用的是app/sub1/string.py中的lower()方法。显然解释器默认先从当前目录下搜索对应的模块,当搜到string.py的时候便停止搜索进行动态加载。那么,如果要使用Python自带的string模块中的方法,该怎么实现呢?这就涉及absolute import和relative import相关的话题了。
在Python2.4以前默认为隐式的relative import,局部范围的模块将覆盖同名的全局范围的模块。如果要使用标注库中同名的模块,你不得不去深入考察sys.modules一番,显然这并不是一种非常友好的做法。Python2.5中后虽然默认的仍然是relative import,但它为absolute import提供了一种新的机制,在模块中使用from future import absolute_import 语句进行说明后再进行导入。同时它还通过点号提供了一种显式进行relative import的方法,“.”表示当前目录,“..”表示当前目录的上一层目录。例如想在mod1.py中导入string.py,可以使用from . import string,其中mod1所在的包层次结构为app.sub1.mod1,“.”表示app.sub1;如果想导入sub2/mo2.py可以使用from ..sub2 import mod2,“..”代表的是app。
但事情是不是就此结束了呢?远不止,使用显式relative import之后再运行程序一不小心你就有可能遇到这种错误“ValueError: Attempted relative import in non-package”。这是什么原因呢?这个问题产生的原因在于relative import使用模块的name属性来决定当前模块在包层次结构中的位置,如果当前的模块名称中不包含任何包的信息,那么它将默认为模块在包的顶层位置,而不管模块在文件系统中的实际位置。而在relative import的情形下,name会随着文件加载方式的不同而发生改变,上例中如在目录app/sub1/下运行Python mod1.py,会发现模块的namemain,但如果在目录app/sub1/下运行Python-m mod1.py,会发现name变为mod1。其中-m的作用是使得一个模块像脚本一样运行。而无论以何种方式加载,当在包的内部运行脚本的时候,包相关的结构信息都会丢失,默认当前脚本所在的位置为模块在包中的顶层位置,因此便会抛出异常。如果确实需要将模块当作脚本一样运行,解决方法之一是在包的顶层目录中加入参数-m运行该脚本,上例中如果要运行脚本mod1.py可以在app所在的目录的位置输入Python -m app.sub1.mod1。另一个解决这个问题的方法是利用Python2.6在模块中引入的package属性,设置package之后,解释器会根据packagename的值来确定包的层次结构。上面的例子中如果将mod1.py修改为以下形式便不会出现在包结构内运行模块对应的脚本时出错的情况了。

if __name__ == "__main__" and __package__ is None:
import sys
import os.path
sys.path[0] = os.path.abspath("./../../")
print sys.path[0]
import app.sub1
__package__ =  str('app.sub1')
from . import string

建议26: 深入理解None,正确判断对象是否为空

if xxx is not None:
    Do something
else:
    Not do something
    上边会走到else

只有None == None是True, 其他像0,[], (),’’等都是False

大多是情况下用

if(XXX):
    do or not do

建议27 连接字符串用join比”+”好

效率要高

建议28 .format

In [15]: 'adasd asd as asd  {0} id {1}'.format('ning','chen')
Out[15]: 'adasd asd as asd  ning id chen'

建议31 重点 记住函数传参既不是传值也不是传引用

python中的函数参数既不是传值也不是传引用,也不是可变对象传引用,不可变对象传值,正确的叫法应该是传对象(call by object)或者说传对象的引用(call-by-object-reference)。函数参数在传递的过程中将整个对象传入,对可变对象的修改在函数外部以及内部都可见,调用者和被调用者之间共享这个对象,二对于不可变对象,由于并不能真正被修改,因此,修改往往是通过生成一个新对象然后复制来实现。

建议36 掌握字符串的基本用法

basestring 包括str 和 unicode

字符串方法:

isalnum()
isalpha()
isdigit()
islower()
isupper()
isspace()
istitle()
startswith()
endwith()

建议14 eval()不安全


热评文章