当前位置: 首页 > news >正文

Python之异常处理与程序调试(Exception Handling and Program Debugging in Python)

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

推荐:Linux运维老纪的首页,持续学习,不断总结,共同进步,活到老学到老
导航剑指大厂系列:全面总结 运维核心技术:系统基础、数据库、网路技术、系统安全、自动化运维、容器技术、监控工具、脚本编程、云服务等。
常用运维工具系列:常用的运维开发工具, zabbix、nagios、docker、k8s、puppet、ansible等
数据库系列:详细总结了常用数据库 mysql、Redis、MongoDB、oracle 技术点,以及工作中遇到的 mysql 问题等
懒人运维系列:总结好用的命令,解放双手不香吗?能用一个命令完成绝不用两个操作
数据结构与算法系列:总结数据结构和算法,不同类型针对性训练,提升编程思维,剑指大厂
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨

Python异常处理与程序调试

技能目标
- 掌握 Python 的异常处理
- 掌握测试和调试程序的方法
异常处理是程序中用于处理意外情况的代码段,而在代码编写的过程中,经常要进行代
码的调试和测试工作,本章将介绍 Python 语言中异常处理和程序调试的具体使用方法。

5.1 异常处理

5.1.1 异常

在人们的工作生活中,做某一件事情的时候,通常并不能很顺序的完成,在做事情的过
程中可能会有一些意外的情况发生。比如在开车上班的途中车胎被扎漏气,就需要先补好车
胎再去上班;再比如在写作业时笔坏了,就需要换一支新笔。所以当有意外情况发生时,就
需要有对应的解决方法,以便使事情能够继续做下去。对于程序来说,当要完成某一功能时,
有可能也会产生一些意外的情况,这种意外发生的情况在程序中称为异常。
示例 1:存在除数为 0 的程序代码
示例代码如下:
print('开始执行除法运算\n\n')
while True:
str1 = '输入 1 个整数作为第 1 个操作数\n'
str2 = '输入 1 个整数作为第 2 个操作数\n'
print ('开始执行除法运算\n')
op1 = int(input(str1))
op2 = int(input(str2))
result = op1 /op2
print ('%d / %d = %d' %(op1,op2,result))
仔细阅读这段代码,并没有发现问题。只是在 while 循环进行除法的计算功能。但是请
注意:当除数是 0 时,代码中的除法运算是没有意义的。所以,在这段程序运行过程中,
如果输入的第 2 个参数是 0,则会出现异常情况,输出结果如下:
开始执行除法运算
输入 1 个整数作为第 1 个操作数
3
输入 1 个整数作为第 2 个操作数
0
Traceback (most recent call last):
File "<stdin>", line 7, in <module>
ZeroDivisionError: integer division or modulo by zero
从程序输出结果中,可以发现:运行这段程序,键盘输入的第 2 个参数是 0 时,程序
会产生一个 ZeroDivisionError 异常。Python 编译器将会输出提示信息“integer division or
modulo by zero”,并终止程序的运行。就像是汽车的车胎被扎一样,需要停下车先补好车
胎才能继续开车。程序运行出现异常时,也需要做适当的处理,再继续完成所要实现的功能。
一个健壮的程序,不能因为发生异常就中断结束。
常见的异常现象有但不限于:读写文件时,文件不存在;访问数据库时,数据库管理系
统没有启动;网络连接中断;算术运算时,除数为 0;序列越界等。
异常(Exception)通常可看作是程序的错误(Error),是指程序是有缺陷(Bug)的。
错误分为语法错误和逻辑错误。
语法错误是指 Python 解释器无法解释代码,在程序执行前就可以进行纠正。逻辑错误
是因为不完整或不合法的输入导致程序执行得不到预期的结果。程序在运行时,如果 Python
解释器遇到一个错误,会停止程序的执行,并且提示一些错误信息,这就是异常。
程序开发时,很难将所有的特殊情况都处理的面面俱到,通过异常捕获可以针对突发事
件做集中的处理,从而保证程序的稳定性和健壮性。
示例 2:使用 try-except 语句捕获并处理除数为 0 的异常
示例代码如下:
print('开始执行除法运算\n\n')
while True:
str1 = '输入 1 个整数作为第 1 个操作数\n'
str2 = '输入 1 个整数作为第 2 个操作数\n'
print ('开始执行除法运算\n')
try:
#可能产生异常的代码块
op1 = int(input(str1))
op2 = int(input(str2))
result = op1 /op2
print ('%d / %d = %d' %(op1,op2,result))
except ZeroDivisionError:
#捕获除数为 0 的异常
print ('捕获除数为 0 的异常')
#结果
>>>
开始执行除法运算
输入 1 个整数作为第 1 个操作数
11
输入 1 个整数作为第 2 个操作数
0
捕获除数为 0 的异常
开始执行除法运算
输入 1 个整数作为第 1 个操作数
在示例 2 中,当输入第 2 个参数为 0 时,程序继续到下一次循环执行,也就是异常情
况得到了处理,程序并没有因为异常而终止。
这段代码对异常使用 try-except 的语法结构,对引发的异常进行捕获和处理,保证程序
能继续执行并获得正确的结果。由此得知,对异常进行处理要分为 2 个阶段,第一个阶段
是捕获可能引发的异常,第二个阶段是要对发生的异常进行及时的处理。当异常发生时,不
仅能检测到异常条件,还可以在异常发生时采取更可靠的补救措施,排除异常。
示例 1 和示例 2 中的 ZeroDivisionError 是除数为 0 的异常类,Python 中还有很多内置
的异常类,它们分别表示程序中可能发生的各种异常,如表 5-1 所示。
5-1 Python 内置的异常类
异常类说明举例
NameError尝试访问一个未声明的变量>>>foo
ZeroDivisionError除数为零>>>1/0
SyntaxError解释器语法错误>>>for
IndexError请求的索引超出序列范围
>>>iList=[]
>>>iList[0]
KeyError请求一个不存在的字典关键字
>>>idict={1:’A’,2:’b’}
>>>print idict[‘3’]
IOError输入/输出错误>>>fp = open(“myfile”)
AttributeError尝试访问未知的对象属性>>>class myClass():
pass
>>>my = myClass()
>>>my.id
 

下面是异常发生的三种情况,示例代码如下:

>>> foo
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
NameError: name 'foo' is not defined
#尝试访问一个未声明的变量
>>> for
File "<stdin>", line 1
for
^
SyntaxError: invalid syntax
#解释器语法错误
>>> iList=[]
>>> iList[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
#请求的索引超出序列范围
1 个异常类是 NameError,后面的“name 'foo' is not defined”的表示变量“foo”未定义。
2 个异常类是 SyntaxError,后面的“invalid syntax”是指存在语法错误。第 3 个异常类是
IndexError,后面的“list index out of range”是索引超出序列范围。可以看出,异常产生时,
都有一个对应的异常类,且后面有英文的提示信息。熟悉常见的异常类可以准确、快速的解
决问题,减少程序中的缺陷(Bug)。

5.1.2 异常处理

Python 中可以使用 try 语句检测异常,任何在 try 语句块里的代码都会被检测,检查
是否有异常发生。try 语句有两种主要形式 try-except try-finally

1. try-except

使用 try-except 定义异常监控,并且提供处理异常机制的语法结构如下。
语法:
try:
语句
# 被监控异常的代码块
except 异常类 [,对象]
语句
# 异常处理的代码
当执行 try 中的语句块时,如果出现异常,会立即中断 try 语句块的执行,转到 except
语句块,将产生的异常类型与 except 语句块中的异常进行匹配。如果匹配成功,执行相应
的异常处理。如果匹配不成功,将异常传递给更高一级的 try 语句。如果异常一直没有找到
处理程序,则停止执行,抛出异常信息。
通过示例 2 的代码分析异常的处理过程
示例代码如下:
print('开始执行除法运算\n\n')
while True:
str1 = '输入 1 个整数作为第 1 个操作数\n'
str2 = '输入 1 个整数作为第 2 个操作数\n'
print ('开始执行除法运算\n')
try:
#可能产生异常的语句块
op1 = int(input(str1))
op2 = int(input(str2))
result = op1 /op2
print ('%d / %d = %d' %(op1,op2,result))
except ZeroDivisionError as e:
#捕获除数为 0 异常
print ('捕获除数为 0 的异常')
print (e)
#结果
开始执行除法运算
输入 1 个整数作为第 1 个操作数
1
输入 1 个整数作为第 2 个操作数
0
捕获除数为 0 的异常
division by zero
开始执行除法运算
输入 1 个整数作为第 1 个操作数
示例 2 try 语句块中的代码有可能产生异常。当执行到“result = op1 /op2”时,如果除
数为 0 会触发异常,try 语句块就中断执行,直接转到 except 语句块进行异常类型的匹配。
except 语句块中,如果捕获的异常对象与“ZeroDivisionError”匹配成功,则执行与之相应
的代码块。“ZeroDivisionError as e”的作用是把异常类赋值给变量 e,用于输出异常信息。
try-except 结构还可以加入 else 语句,当没有异常产生时,执行完 try 语句块后,就要
执行 else 语句块中的内容。

示例 3:使用 try-except-else 语句捕获并处理除数为 0 的异常

示例代码如下:
print('开始执行除法运算\n\n')
while True:
str1 = '输入 1 个整数作为第 1 个操作数\n'
str2 = '输入 1 个整数作为第 2 个操作数\n'
print ('开始执行除法运算\n')
try:
op1 = int(input(str1))
op2 = int(input(str2))
result = op1 /op2
except ZeroDivisionError as e:
print ('捕获除数为 0 的异常')
print (e)
else:
#try 中没有异常产生时,执行 else
print ('%d / %d = %d' %(op1,op2,result))
#结果
开始执行除法运算
输入 1 个整数作为第 1 个操作数
3
输入 1 个整数作为第 2 个操作数
2
3 / 2 = 1
开始执行除法运算
输入 1 个整数作为第 1 个操作数
示例 3 中,输入的除数不为 0 不会产生异常,执行完 try 语句块后,程序转到 else
句块继续执行,输出结果的语句写到了 else 语句块中。输出结果的语句放在 try 语句块中或
者在 else 语句块中,对程序的执行结果并没有什么影响。
在同一个 try 语句块中有可能产生多种类型的异常,可以使用多个 except 语句进行处
理,语法结构如下。
语法:
try:
语句
# 被监控异常的代码块
except 异常类 1 [,对象]
语句
# 异常处理的代码
……
except 异常类 n [,对象]
语句
# 异常处理的代码
try 语句块发生异常时,将产生的异常类型与 except 后面的异常类逐一进行匹配,
按先后顺序确定相匹配的异常类型后,执行对应的语句块。
示例 4:使用多个 except 捕获多个异常类型
示例代码如下:
def safe_float(obj):
try:
retval = float(obj)
except ValueError as e1:
#字符串转浮点数异常
print (e1)
retval = "非数值类型数据不能转换为 float 数据"
except TypeError as e2:
#类型转换错误错误
print (e2)
retval = "数据类型不能转换为 float"
return retval
print (safe_float('xyz'))
print (safe_float(()))
print (safe_float(200))
print (safe_float(99.9))
#结果
could not convert string to float: 'xyz'
非数值类型数据不能转换为 float 数据
float() argument must be a string or a number, not 'tuple'
数据类型不能转换为 float
200.0
99.9
从示例 4 的运行结果可以看到,当函数 safe_float()的参数是“xyz”,会产生 ValueError
异常对象,在被“except ValueError as e1: ”捕获后,执行对应的语句块。当参数是元组”()”
时,会产生 TypeError 异常,被“except TypeError as e2:”捕获,执行对应的语句块。如果
是正确数据将不会产生异常,则执行最后的返回语句。

2. BaseException 类

Python 中,BaseException 类是所有异常的基类,也就是所有的其他异常类型都是直
接或间接继承自 BaseException 类。直接继承 BaseException 的异常类有 SystemExit
KeyboardExit Exception 等。SystemExit Python 解释器请求退出,KeyboardExit
用户中断执行,Exception 是常规错误。前面示例中的异常类都是 Python 内置的异常类型,
它们与用户自定义异常类一样,它们的基类都是 Exception
如果多个 except 语句块同时出现在一个 try 语句中,异常的子类应该出现在其父类之
前。因为发生异常时 except 是按顺序逐个匹配,而只执行第一个与异常类匹配的 except
语句,因此必须先子类后父类。如果父类放在了前面,当产生子类的异常时,父类对应的
except 语句会匹配成功,子类对应的 except 语句将不会有执行的机会。示例 5:捕获子类异常和父类异常
示例代码如下:
def safe_float(obj):
try:
retval = float(obj)
except Exception as e3:
retval = "有异常产生,类型不详"
except ValueError as e1:
print (e1)
retval = "非数值类型数据不能转换为 float 数据"
except TypeError as e2:
print (e2)
retval = "数据类型不能转换为 float"
return retval
print (safe_float('xyz'))
print (safe_float(()))
print (safe_float('595.99'))
print (safe_float(200))
print (safe_float(99.9))
#结果
>>>
有异常产生,类型不详
有异常产生,类型不详
595.99
200.0
99.9

示例 5 的代码首先将捕获的异常对象与 Exception 类相匹配,然后再与 ValueError 类

TypeError 类做匹配。由于 Exception 是所有异常类的基类,所以当发生异常时,捕获的
异常对象会首先与 Exception 类进行匹配,并执行 Exception 对应的语句块。如果对于类型
转换产生的异常,不需要针对不同的情况进行处理,那么只需要把后两个异常处理语句删除
即可。如果想针对不同的情况处理,那么就需要调整 Exception 语句的位置,把它放到所有
异常类型的后面,也就是在其前面的异常子类都不匹配时,才会与 Exception 进行匹配。
对于相同类型的异常,可以只使用一个 except 语句,把同类型的异常对象放到一个元
组中进行处理,语法结构如下。
语法:
try:
语句
# 被监控异常的代码块
except (异常类 1 [,异常类 2][,……异常类 n])[,对象]
语句
# 异常处理的代码
示例 6:使用元组保存并处理相同类型的异常对象
示例代码如下:
def safe_float(obj):
try:
retval = float(obj)
except (ValueError ,TypeError):
retval = "参数必须是一个数值或数值字符串"
return retval
print (safe_float('xyz'))
print (safe_float(()))
print (safe_float('595.99'))
print (safe_float(200))
print (safe_float(99.9))
#结果
>>>
参数必须是一个数值或数值字符串
参数必须是一个数值或数值字符串
595.99
200.0
99.9
示例 6 的代码将 ValueError TypeError 被放到了一个元组中,它们中的任何一个异
常发生,都会被捕获。使用这种方式的前提是,异常是同类型的;否则程序的处理是有问题
的。

3. try-except-finally

try 还有一个非常重要的处理语句 finally。一个 try 语句块只能有一个 finally 语句块,它
表示无论是否发生异常,都会执行的一段代码。加入finally后,
t
ry语句会有try-except-finally
try-except-else-finally try-finally 三种形式。finally 语句块通常用来释放占用的资源,例如
关闭文件、关闭数据库连接等。语法结构如下。
语法:
try:
语句
# 被监控异常的代码块
except 异常类 1 [,对象]
语句
# 异常处理的代码
[else:
语句] # try 语句块的代码全部成功时的操作
finally:
语句
# 无论如何都执行
当对文件进行操作后,关闭文件是必须要做的工作。不论程序运行是否正确都应该在结
束时关闭文件,因此,可以把关闭文件的代码写到 finally 中。

示例 7:使用 try-except-else-finally 进行文件的读写操作

示例代码如下:
fp = None
try:
fp = open('/usr/local/readme.txt','r+')
fp.write('12345')
except IOError:
print ('文件读写出错')
except Exception:
print ('文件操作异常')
else:fp.seek(1)
f = fp.readlines()
print (f)
finally:
fp.close()
print ('关闭文件')
#结果
['234556789']
关闭文件
顺利执行示例 7 代码,没有产生异常,最后执行到了 finally 语句块中,执行关闭文件
的语句。如果有异常产生,同样是要执行 finally 语句块,执行关闭文件的语句。因此,finally
语句块的作用是非常明显的,把释放资源的代码放在里面,可以保证这些代码一定会被执行。
5.1.3 抛出异常
在现实生活中,当完成一项工作时,碰到问题不知道怎样解决或没有权限做决断,就需
要向上一级领导反映问题。如果上一级领导也不知道怎样解决,依然要向上反映,直到某一
级别的领导可以解决。但如果最高领导还是无法解决,就需要暂停工作,思考解决办法。异
常也有类似的情况,前面的示例中产生的异常都是可以在当前程序块中解决的。但是一旦解
决不了,就需要向调用它的程序块抛出异常,寻找解决办法。比如 float()Python 自带的
转换为浮点数的函数,调用时只需要传递数据给它。当它无法把参数转换为浮点数时,就抛
出了异常,告诉调用者是什么原因无法转换。此时调用者就可以根据异常对象确定问题的所
在,并找到解决办法。通过抛出异常、接收并处理异常,可以实现程序的多分支处理。
程序中抛出异常使用 raise 语句,常用的语法格式如下。
语法:
raise 异常类
raise 异常类(参数或元组)
参数是指用户可以自定义的提示信息,使调用者能依此信息快速的判断并确认存在的问
题。
示例 8:要求输入的文件名不能是”_hello_”
示例代码如下:
filename = input("please input file name")
if filename == "_hello_":
raise NameError("input file name error")
#结果
please input file name_hello_
Traceback (most recent call last):
File "wyjx.py", line 3, in <module>
raise NameError("input file name error")
NameError: input file name error
当输入文件名是"_hello_"时,条件判断成立,执行抛出异常语句,和前面示例中的异常
形式相同,显示输出“NameError: input file name error“。此时的异常将交给上一级处理,也
就是 Python 解释器接收异常,因为程序代码没有对异常进行处理,所以最后的结果是程序
终止运行。
示例 9:捕获并处理抛出的异常
示例代码如下:
def filename():
filename = input("please input file name:")
if filename == "_hello_":
raise NameError("input file name error")
return filename
while True:
try:
#对异常进行捕获处理
filename = filename()
print ("filename is %s" %filename)
break
except NameError:
print ("please input file name again!")#结果
please input file name:_hello_
please input file name again!
please input file name:
示例 9 的函数 filename()中,如果输入的文件名是"_hello_",将会抛出 NameError
常。在调用 filename()时需要用 try 对它捕获进行处理,此时程序就不会终止运行,增加了
程序的健壮性。
5.2 调试和测试程序
Python 提供了内置的 pdb 模块进行程序调试,也提供了单元测试的模块 doctest。本节
将讲解这两个模块如何使用。
5.2.1 调试程序
Pdb 模块采用命令交互的方式,可以设置断点、单步执行、查看变量等,pdb 模块中的
调试函数分为两种:语句块调试函数和调试函数。
1.语句块调试函数
run()函数可以对语句块进行调试,只要把语句块作为参数执行即可进行调试,示例代
码如下:
import pdb
pdb.run('''
for i in range(1,3):
print (i)
''')
这是对 for 循环进行调试的一段代码,运行后会出现调试的命令提示,代码如下所示:
> <string>(2)<module>()
(Pdb)
然后,就可以输入命令进行调试,常用的命令如表 5-2 所示。
5-2 pdb 常用命令
命令/完整命令描述
h/help查看命令列表
j/jump跳转到指定行
n/next执行下一条语句,不进入函数
r/return运行到函数返回
s/step执行下一条语句,遇到函数进入
q/quit退出 pdb
b/break设置断点

2.调试函数

如果需要对函数进行调试,可以使用 runcall(),示例代码如下:
import pdb
def sum (a,b):
total = a+b
return total
pdb.runcall(sum,10,5)
pdb.runcall(sum,10,5)的含义是调试 sum()函数,后面是调用 sum()2 个实参。执行
后也是 pdb 的命令行模式,输入命令可以进行调试。
5.2.2 测试程序
doctest 模块提供了测试的函数,testmod()可以对 docstring 中测试用例进行测试,测
试用例使用“>>>”表示,示例 10 代码如下:
def sum(a,b):
"""
>>> sum(1,4)
5
>>> sum(100,11)
133
"""
return a+b
if __name__ == '__main__':
import doctest
doctest.testmod()
在每个”>>>”后面是调用函数的测试用例,紧跟在下面的内容是函数的返回结果。注意:
“>>>”后面要有一个空格。这段测试用例的执行结果如下:
>>>
**********************************************************************
File "wyjx.py", line 6, in __main__.sum
Failed example:
sum(100,11)
Expected:
133
Got:
111
**********************************************************************
1 items had failures:
1 of
2 in __main__.sum
***Test Failed*** 1 failures.
TestResults(failed=1, attempted=2)
因为对 sum(100,11)指定的结果是 133,但它的函数处理结果是 111,所以显示出相应
的错误信息。如果测试用例都能通过,将没有任何的错误信息。
可以像上面示例的代码一样,把测试代码和函数写在了一个 Python 文件中,也可以把
测试代码写到单独的文本文件中,再使用 testfile()函数进行测试,示例代码如下:
sum.py
#函数文件
def sum1(a,b):
return a+b
testsum.txt
#测试代码文件
>>> from sum import sum1
>>> sum1(1,4)
5
>>> sum1(100,11)
133
test.py
#测试文件
import doctest
doctest.testfile('testsum.txt')
执行 test.py 后,测试结果与使用 testmod()函数相同。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 银河麒麟v10-sp3 -x86系统创建新分区扩展lvm
  • java基础-IO(6)转换流InputStreamReader、OutputStreamWriter
  • 内存分区学习
  • Qt使用绿色pdf阅读器打开文件
  • java spring定时任务-动态任务
  • 认知杂谈59《实力为王:用硬本事赢得尊重,开启人生逆袭路》
  • Python3中函数的用法
  • linux-用户与权限管理-组管理
  • 防患于未然,智能监控新视角:EasyCVR视频平台在高校安全防控中的关键角色
  • 一维稳态与非稳态导热的详细分析
  • 通信工程学习:什么是IP-CAN(IP连接接入网)
  • ETL_场景练习
  • 建投数据通过ISO9001再认证
  • [Postman]接口自动化测试入门
  • 探索Promise:JavaScript异步编程的基石
  • “寒冬”下的金三银四跳槽季来了,帮你客观分析一下局面
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • 【附node操作实例】redis简明入门系列—字符串类型
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • ES6--对象的扩展
  • Java 内存分配及垃圾回收机制初探
  • js ES6 求数组的交集,并集,还有差集
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • 动态魔术使用DBMS_SQL
  • 手写一个CommonJS打包工具(一)
  • 白色的风信子
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • ​MySQL主从复制一致性检测
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • #define用法
  • #include<初见C语言之指针(5)>
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • (2024最新)CentOS 7上在线安装MySQL 5.7|喂饭级教程
  • (Bean工厂的后处理器入门)学习Spring的第七天
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (强烈推荐)移动端音视频从零到上手(上)
  • (十六)、把镜像推送到私有化 Docker 仓库
  • (四)js前端开发中设计模式之工厂方法模式
  • (转)es进行聚合操作时提示Fielddata is disabled on text fields by default
  • (转)用.Net的File控件上传文件的解决方案
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .Net插件开发开源框架
  • .Net多线程总结
  • [Apio2012]dispatching 左偏树
  • [C#]winform使用onnxruntime部署LYT-Net轻量级低光图像增强算法
  • [CareerCup] 2.1 Remove Duplicates from Unsorted List 移除无序链表中的重复项
  • [Grafana]ES数据源Alert告警发送
  • [iOS]Win8下iTunes无法连接iPhone版本的解决方法
  • [IT生活推荐]大家一起来玩游戏喽,来的都进!
  • [Java]SpringBoot快速入门
  • [JavaWeb]—Spring入门
  • [Latex] Riemann 问题中的激波,接触间断,膨胀波的 Tikz 绘图