改善Python程序的建议

三元操作符:
我们都知道在C语言中,三元操作符为C ? X : Y,在人们的强烈要求下,python2.5之后三元操作符等价为X if C else Y

什么时候应该使用断言?
断言的基本语法如下:
assert expression1 ["," expression2]
计算expression1的值会返回True或者False,当值为False的时候会引发AssertError,而expression2是可选的,常用来查看具体的异常信息。
断言是有代价的,会对性能产生一定的影响,但python并没有严格定义调试与发布模式之间的差别,通常禁用断言的方法是在运行脚本时加上-O标志,这种方法带来的影响是它不优化字节码,而是忽略与断言有关的语句。
断言是用来捕获用户定义的约束的,不是用来捕获程序本身的错误的。

  • 断言应该被使用在正常逻辑不可到达的地方或正常情况下总是为真的场合。

  • 异常能处理就不要使用断言。

  • 不要用断言检查用户输入。

  • 当函数调用后,需要确定返回值是否合理时可以使用断言。

  • 当条件是业务逻辑继续的先决条件时可以使用断言。

我最近在一个爬虫代码里使用了几次断言,使用断言是为了保证程序自身所记录的页数与爬虫所运行到的页数相同,否则会在存储数据时发生错误。

数据交换值时不推荐使用中间变量
x,y = y,x 这种方法有更好的性能。

构建合理的包层次来管理module
本质上每一个python文件都是一个模块,但在大的项目中把所有的python文件放在一个目录下不是一个好的做法。我们应该用package包来管理模块。
__init__.py的一些作用:

Package/ __init__.py
  Module1.py
  Module2.py
  Subpackage/ __init__.py
        Module1.py
        Module2.py
  • 使包和普通目录区分

  • 如果我们在Package包下的__init__.py中添加:
    from Module1 import Test语句,则可以直接使用from Package import Test来导入类Test

  • __init__.py为空,意图使用from Package import *语句将包Package中所有的模块导入当前命名空间将会失败。它仅仅会执行__init__.py文件。因此我们需要修改__init__.py文件。
    如果我们在__init__.py文件中添加:

__all__ = ['Module1','Module2','Subpackage']
这样我们就能使用from Package import *语句将包Package中所有的模块导入当前命名空间。

使用with自动关闭资源
with语句可以在代码块执行完毕后还原进入该代码块时的现场。在文件管理时使用with总能保证文件被正常关闭。

None的特殊性
所有被赋值为None的变量相等,并且None与任何其他非None的对象比较结果都为False.

字符串的连接特别是大规模字符串的处理,应该优先使用join而不是+。
如果我们进行以下操作:S1+S2+S3+..,执行一次+操作便会在内存中申请一块新的内存,并将上一次操作的结果和本次操作的右操作数复制到该内存中。在N个字符串连接中,会产生N-1个中间结果,每产生一个中间结果都要申请和复制一次内存,总共要申请N-1次内存,严重影响效率。时间复杂度接近O(n^2)。
而当使用join()方法连接字符串时,会首先计算需要申请的总的内存空间,然后一次性申请所需内存并将字符序列中的每一个元素复制到内存中。时间复杂度为O(n)。

python中一切皆对象,每一个对象都有一个唯一的id。
函数传参既不是传值也不是传引用
在C/C++中,如果执行:

a = 5;
b = a;
b = 7;

在内存中申请一块内存并将a的值复制到该内存中,当执行b = 7时将b对应的值从5改到7。
而对于python,赋值并不是复制,b=a使得a和b引用同一个对象。b=7则是将b指向对象7。
python函数参数到底是传值还是传引用,实际都不是,而是传对象或者说传对象的引用。
对可变对象的修改在函数外部以及内部都可见,调用者和被调用者之间共享这个对象。
对于不可变对象,由于并不能真正修改,因为,修改往往是通过生成一个新对象然后赋值来实现的。

警惕默认参数潜在的问题

def在python中是一个可执行的语句,当解释器执行def时,默认参数也会被计算,并存在函数的.func_defaults属性中。  
由于Python中函数参数传递的是对象,可变对象在调用者和被调用者之间共享。
def appendtest(newitem,lista=[]):
    print(id(lista))
    lista.append(newitem)
    print(id(lista))
    return lista

>>> appendtest(1)
12345
12345
[1]
>>> appendtest('a')
12345
12345
[1,'a']

PS:我们可以用这个属性,统计某个方法被调用的次数。
如果不想让默认参数所指向的对象在所有的函数调用中共享,而是在函数调用的过程中动态生成,可在定义的时候使用None对象作为占位符。

def appendtest(newitem,lista=None):
    print(id(lista))
    lista.append(newitem)
    print(id(lista))
    return lista

str()和repr()的区别:目标不同:str()面向用户,repr()面向解释器和开发人员
字符串的一些技巧:

  • python遇到未闭合的小括号时会自动将多行代码拼接为一行和把相邻的两个字符串字面量拼接在一起。

s = ('SELECT * '
    'FROM atable '
    'WHERE afirld="value"')
print(s)
>>> SELECT * FROM atable WHERE afirld="value"
  • str的一些方法
    str方法很多,只说几个有意思的:

count()能查找子串在字符串中出现的次数,这个方法在调用replace方法时能使用,可以批量替换。
replace(old,new[,count])用以替换字符串的某些子串,如果指定count参数,就最多替换count次,不指定则替换全部。

参考资料:《编写高质量代码:改善Python程序的91个建议》

    原文作者:superPershing
    原文地址: https://segmentfault.com/a/1190000008655414
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞