Python Tips

2021年1月31日 作者 Just

定义一个all不会把本模块的所有内容都暴露在外部,将允许外部访问的变量、函数和类的名字放进去

说明

在模块中定义了__all__之后,从外部from module import * 只会import __all__中定义的内容。

示例

sample_package.py

__all__ = ["sample_external_function"]

def sample_external_function():
    print("This is an external function..")

def sample_internal_function():
    print("This is an internal function..")

main.py

from sample_package import *

if __name__ == "__main__":
    sample_external_function()
    sample_internal_function()

NameError: name 'sample_internal_function' is not defined

在list成员个数可以预知的情况下,创建list时需预留空间正好容纳所有成员的空间

说明

与Java、C++等语言的list一样,Python语言的list在append()成员时,如果没有多余的空间容纳新的成员,就会分配一块更大的内存,并将原来内存里的成员拷贝到新的内存上,并将最新append()的成员也拷贝到此新内存空间中,然后释放老的内存空间。如果append()调用次数很大,则如上过程会频繁发生,因而会造成灾难性性能下
降,而不仅仅是一点下降。

错误示例:

members = []
for i in range(1, 1000000):
    members.append(i)
len(members)

正确示例:

members = [None] * 1000000
for i in range(1, 1000000):
    members[i] = i
len(members)

在成员个数及内容皆不变的场景下尽量使用tuple替代list

说明

list是动态array,而tuple是静态array(其成员个数以及内容皆不可变)。因此,list需要更多的内存来跟踪其成员的状态。
此外,对于成员个数小于等于20的tuple,Python会对其进行缓存,即当此tuple不再使用时,Python并不会立即
将其占用的内存返还给操作系统,而是保留以备后用。

错误示例:

myenum = [1, 2, 3, 4, 5]

正确示例:

myenum = (1, 2, 3, 4, 5) # 如果恰好被缓存过,则初始化速度会为错误示例中的5倍以上。

对于频繁使用的外界对象,尽量使用局部变量来引用

说明

在Python中对一个函数、变量、模块的调用,是以一种字典树的方式来查找的。Python首先会查找locals()数组,这里保存着所有的局部变量;如果找不到,则会继续查找globals()数组;如果在这里也找不到,则会到
buildtin(其实是一个模块)中的locals()数组中查找,或者到其它import进来的模块/类中查找。
错误示例:

错误示例:

import math
def afunc():
    for x in xrange(100000):
        return math.tan(x)

在这个例子中,Python会先到globals()中的名值对字典中,找到math模块;然后在math模块的locals()的字典中
查找tan()函数;然后在当前函数的locals()中查找。这里存在着3次查找。
当调用次数大时,每次调用多出来的1、2次查找,就会被放大。
错误示例:

from math import tan
def afunc():
    for x in xrange(100000):
        return tan(x)

在这个例子中,Python会先到globals()的字典中查找tan()函数(其已经被from math import tan语句加载到了
globals()中);然后在当前函数的locals()中查找x。这里存在着2次查找,比前一个例子少了一次查找,但是还不是最优解。
正确示例:

import math
def afunc(tan=math.tan):
    for x in xrange(100000):
        return tan(x)

在这个例子中,在函数定义时,有且只有一次查找math模块、然后查找tan函数的操作;之后在循环中对tan()函数的调用,都是在afunc()函数的locals()中进行查找。而对函数的locals()中的查找,Python是有特殊优化措施的,速度是非常快的;当然,还包括对本地变量x的查找(也是在当前函数的locals()中查找)。

使用format方法、”%”操作符和join方法代替”+”和”+=”操作符来完成字符串格式化

说明

即使参数都是字符串,也可以使用format方法或%运算符来格式化字符串。一般性能要求的场景可以使用+或+=运算符,但需要避免使用+和+=运算符在循环中累积字符串。由于字符串是不可变的,因此会产生不必要的临时对象并导致二次而非线性运行时间。
推荐做法:

x = '%s, %s!' % (imperative, expletive)
x = '{}, {}!'.format(imperative, expletive)
x = 'name: %s; score: %d' % (name, n)
x = 'name: {}; score: {}'.format(name, n)
items = ['<table>']
for last_name, first_name in employee_list:
    items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
    items.append('</table>')
employee_table = ''.join(items)

不推荐做法:

x = imperative + ', ' + expletive + '!'
x = 'name: ' + name + '; score: ' + str(n)
employee_table = '<table>'
for last_name, first_name in employee_list:
    employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
employee_table += '</table>'

函数参数中的可变参数不要使用默认值,在定义时使用None

说明

参数的默认值会在方法定义被执行时就已经设定了,这就意味着默认值只会被设定一次,当函数定义后,每次被调用时都会有”预计算”的过程。当参数的默认值是一个可变的对象时,就显得尤为重要,例如参数值是一个list或dict,如果方法体修改这个值(例如往list里追加数据),那么这个修改就会影响到下一次调用这个方法,这显然不
是一种好的方式。应对种情况的方式是将参数的默认值设定为None。

错误示例:

>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified
... bar.append("baz") # but this line could be problematic, as we'll see...
... return bar

在上面这段代码里,一旦重复调用foo()函数(没有指定一个bar参数),那么将一直返回'bar'。因为没有指定参
数,那么foo()每次被调用的时候,都会赋予[]。下面来看看,这样做的结果:

>>> foo()
["baz"]
>>> foo()
["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]

正确示例:

>>> def foo(bar=None):
... if bar is None: # or if not bar:
... bar = []
... bar.append("baz")
... return bar
...
>>> foo()
["baz"]
>>> foo()
["baz"]
>>> foo()
["baz"]

使用os.path库中的方法代替字符串拼接来完成文件系统路径的操作

说明

os.path库实现了一系列文件系统路径操作方法,这些方法相比单纯的路径字符串拼接来说更为安全,而且
为用户屏蔽了不同操作系统之间的差异。

错误示例:如下路径字符串的拼接在Windows操作系统无法使用

path = os.getcwd() + '/myDirectory'

正确示例:

path = os.path.join(os.getcwd(), 'myDirectory')