5个常见但难以发现的错误。
错误1. 没有使用if name == 'main':
结论
在脚本文件中,应该使用if __name__ == '__main__'如:
例子
例如,我们创建了一个utils.py
,定义了一个简单的功能:
def print_hello():
print("hello!")
print_hello()
执行python utils.py
,程序会执行print_hello(),输出语句hello!
。
当我们想在 main.py
中import utils
,然后调用print_hello():
import utils
utils.print_hello()
执行python main.py
,会发现程序输出了2行hello!
。
这是因为在import utils
时,执行了utils.py中的语句,执行了1次print_hello()
。main.py
中utils.print_hello()
又调用了一次print_hello()
。
在main.py
中 import utils
,我们只是想引入其中的函数,而不执行其中的调用语句。只需要将utils.py
中的调用语句放在if __name__ == '__main__':
条件下:
def print_hello():
print("hello!")
#可以在这里print(__name__) 验证__name__的值
if __name__ == '__main__':
print_hello()
__name__
是一个特殊变量,只有在执行该脚本时,__name__
才为__main__
;在被import
时,__name__
为脚本名utils
错误2. bare except
结论
不应该使用裸except,这会捕获所有异常,包括SystemExit
和KeyboardInterrupt
,导致无法使用Ctrl+C
中断程序,并且会隐藏潜在的问题。
尽量捕获具体的异常。
例子
import time
while True:
try:
print("hello!")
time.sleep(0.1)
except:
print("... 捕获异常 ...")
执行该程序,会一直打印hello!,即使用Ctrl+C
也无法中断。
修改方法很简单,将裸except
改为except Exception
,这样就不会捕获KeyboardInterrupt
,从而可以用Ctrl+C
中断程序。
import time
while True:
try:
print("hello!")
time.sleep(0.1)
except Exception:
print("... 捕获异常 ...")
错误3. 没有输出完整异常信息
结论
捕获异常时,应该用traceback
输出完整的异常信息,也就是异常溯源。只是简单的print异常,往往无法定位到真正出错的地方。
import traceback
try:
raise Exception('一个异常')
except Exception as e:
print("简单的print e:")
print(e)
# 使用traceback 打印异常溯源
print()
print("使用trackback:")
traceback.print_exc()
# 如果想输出异常信息到其它地方,可用traceback.format_exc()获得异常溯源的字符串
print()
print("使用traceback.format_exc()获得异常溯源的字符串:")
str = traceback.format_exc() # 可以将字符串输出到其它地方,如日志文件。
print(str)
错误4. 在应该用set/dict 的地方用了list
结论
在频繁查找某个元素是否在某个集合中时,应该用set/dict,而不该用list。
因为set/dict的查找时间是O(1),list的查找时间是O(N)。
例子
import time
numbers = [i for i in range(0,100_000,2)]
numbers_set = set(numbers)
def find_in_list():
ret = []
for i in range(10_000):
ret.append(i in numbers)
return ret
def find_in_set():
ret = []
for i in range(10_000):
ret.append(i in numbers_set)
return ret
print("find in list")
s = time.time()
find_in_list()
print(f"用时:{time.time() - s}")
print("find in set")
s = time.time()
find_in_set()
print(f"用时:{time.time() - s}")
运行结果:
find in list
用时:2.1997861862182617
find in set
用时:0.000942230224609375
错误5. 给可变类型参数提供默认值
结论
默认参数值仅在模块加载时的函数定义期间计算一次。这可能会导致动态/可变值(如 {}、[] 或 datetime.now())出现奇怪的行为。使用 None
作为任何具有动态值的关键字参数的默认值。
def get_numbers_list(numbers, lst=None):
if lst is None:
lst = []
for x in numbers:
lst.append(x)
return lst
例子
def get_numbers_list(numbers, lst=[]):
for x in numbers:
lst.append(x)
return lst
numbers = [1,2,3,4]
lst = get_numbers_list(numbers)
print(lst)
numbers2 = [5,6,7,8]
lst2 = get_numbers_list(numbers2)
print(lst2)
输出:
[1, 2, 3, 4]
[1, 2, 3, 4, 5, 6, 7, 8]
应该改为
def get_numbers_list(numbers, lst=None):
if lst is None:
lst = []
for x in numbers:
lst.append(x)
return lst
numbers = [1,2,3,4]
lst = get_numbers_list(numbers)
print(lst)
numbers2 = [5,6,7,8]
lst2 = get_numbers_list(numbers2)
print(lst2)
参考:1. https://www.youtube.com/watch?v=fMRzuwlqfzs
2. https://mp.weixin.qq.com/s/qNIOISpCoTBT0vBH4Eq-og
3.《Effecitve Python 第二版》的Item 24: Use None and Docstrings to Specify Dynamic Default Arguments