分享

Python中5个常见的错误

 NeighborMrSun 2023-02-23 发布于湖南

5个常见但难以发现的错误。

错误1. 没有使用if name == 'main':

结论

在脚本文件中,应该使用if __name__ == '__main__'。如:

# utils.py
def print_hello():
    print('hello!')

if __name__ == '__main__':
    print_hello()

例子

例如,我们创建了一个utils.py,定义了一个简单的功能:

def print_hello():
    print('hello!')

print_hello()

执行python utils.py,程序会执行print_hello(),输出语句hello!

当我们想在 main.pyimport utils,然后调用print_hello():

import utils
utils.print_hello()

执行python main.py,会发现程序输出了2行hello!
这是因为在import utils时,执行了utils.py中的语句,执行了1次print_hello()main.pyutils.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,这会捕获所有异常,包括SystemExitKeyboardInterrupt,导致无法使用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)

输出:

[1234]
[12345678

应该改为

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. 1. https://www./watch?v=fMRzuwlqfzs

  2. 2. 《Effecitve Python 第二版》的Item 24: Use None and Docstrings to Specify Dynamic Default Arguments

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多