Contents
1. 总结try/except/else/finally的用法及如何自定义异常
1.1. 异常以及其作用
所谓的异常,是指在程序执行过程中,修改程序的控制流的一些事件。在Python中,异常会在程序执行遇到错误的时候自动触发,同时可以通过特定的代码拦截异常,并对异常做相应的处理。
使用异常的目的通常情况如下:
- 错误处理:Python程序在运行时遇到错误的时候,会抛出异常,此时可以在代码中捕获这些异常信息,并对产生的错误进行相应的处理。如果不对异常进行处理,那么程序会在抛出异常的地方终止执行。通过
try...except...finally
语句结构可以捕获异常,并对其进行相应处理,此后程序就可以继续执行了。 - 事件通知:异常也可以用于通知一些有效的条件,而不是返回退出状态码。通过异常,可以明确的知道发生了什么,而退出状态码则很难直观认识到发生的问题。
- 特殊事件处理:有时一些异常可能极少会被触发,所以很难在代码中对其进行处理。此时可以在可能直接相关的业务代码中忽略这些异常,然后在更高一层级的代码中对抛出的异常进行处理。
- 结束操作:通过
try...finally
语句结构可以将一些收尾操作放在finally
语句块中,finally
语句块中的代码不管try
语句块中的代码是否抛出异常,都会被执行到。这部分的功能也可以通过上下文管理来完成,即with...as...
语句结构。
1.2. 异常处理常用的语句结构
异常处理中,常用的语句结构如下所示:
try:
# <statement block>
pass
except Exception as e: # except (exp1, exp2, exp3, ...) as e
# handle the exception in <statement block>
pass
else:
# execute if without Exception in <statement block>
pass
finally:
# always execute, no matter with or without Exception in <statement block>
pass
上述的伪代码,列出了异常捕获与处理的结构,关于上述伪代码的解释如下:
try
:将要执行的相关任务代码放在这个代码块中,如果代码块中有错误触发了异常,则会向外抛出。except
:捕获try
语句块中抛出的异常,并进行相应的处理,如果这里未捕获到try
语句块中抛出的异常,则Python解释器将会继续向上一层抛出异常,直到该异常被处理或者该程序因为异常而退出。else
:当try
语句块中没有异常出现的时候,才会执行这个子句下面的代码块。finally
:无论try
语句块中是否有异常,都会执行该子句下面的代码块。
其中,else
子句不能单独与try
子句一起使用,else
子句必须与except
组合使用,即出现else子句的地方,必须存在except
子句,否则报语法错误。
except
子句和finally
子句都可以单独与try
子句一起配合使用,也可以组合在一起使用。
这四个子句也可以组合在一起使用。
关于上述过程,见下图:
示例如下所示:
try...except...else...finally
语句块无异常的代码示例def fetcher(obj, index): return obj[index] try: x = 'spam' fetcher(x, 3) # 无异常 except Exception as e: print(e) else: print('Without Exception in try') finally: print('Always Executed')
上述代码的输出结果如下所示:
Without Exception in try Always Executed
从上述输出结果中可以看出,当try子句下面的代码块中没有异常的时候,就会执行else子句中的代码块,并且最后执行finally子句中代码块,因为无论try语句块中的代码是否有异常,最终都会执行finally子句中的代码块。
try...except...else...finally
语句块有异常的代码示例def fetcher(obj, index): return obj[index] try: x = 'spam' fetcher(x, 4) # 异常 except Exception as e: print(e) else: print('Without Exception in try') finally: print('Always Executed')
上述代码的执行结果如下所示:
string index out of range Always Executed
从上述输出结果中可以看出,当try子句中的代码有异常的时候,except子句捕获到之后,就会执行except子句下面的代码块,此时由于有异常,并不会执行else子句下面的代码块。但是finally子句中的代码块,则无论如何都是会被执行的。
如果except子句无法捕获到try子句的语句块中抛出的异常,那么该异常就会导致程序终止执行,具体如下所示:
def fetcher(obj, index): return obj[index] try: x = 'spam' fetcher(x, 4) # 异常 except TypeError as e: print(e) else: print('Without Exception in try') finally: print('Always Executed')
此时上述代码的执行结果如下所示:
Always Executed --------------------------------------------------------------------------- IndexError Traceback (most recent call last) ~\AppData\Local\Temp/ipykernel_466964/3854816074.py in <module> 4 try: 5 x = 'spam' ----> 6 fetcher(x, 4) # 异常 7 except TypeError as e: 8 print(e) ~\AppData\Local\Temp/ipykernel_466964/3854816074.py in fetcher(obj, index) 1 def fetcher(obj, index): ----> 2 return obj[index] 3 4 try: 5 x = 'spam' IndexError: string index out of range
上述执行结果中可以看出,finally子句中的语句块被执行了,然后程序最终因为异常未被捕获以及处理,所以导致程序崩溃,并且提示了异常产生的位置为fetcher(x, 4)这一行代码,同时给出了异常信息为IndexError: string index out of range。
除了上述四种子句之外,还有raise
以及raise from
和assert
语句用于处理异常。
-
raise Exception(info)
语句:抛出包含指定消息的异常。具体如下所示:示例代码如下:
try: print('in try statement block') raise Exception('raise exception') except Exception as e: print('in except statement block') print(e) else: print('without excpetion in try statement block')
上述代码的执行结果如下所示:
in try statement block in except statement block raise exception
如果不对异常信息进行捕获的时候,代码如下所示:
try: print('in try statement block') raise Exception('raise exception') except IndexError as e: print('in except statement block') print(e) else: print('without excpetion in try statement block')
上述代码的执行结果如下所示:
in try statement block --------------------------------------------------------------------------- Exception Traceback (most recent call last) ~\AppData\Local\Temp/ipykernel_466964/4125017337.py in <module> 3 try: 4 print('in try statement block') ----> 5 raise Exception('raise exception') 6 except IndexError as e: 7 print('in except statement block') Exception: raise exception
上述执行结果中给出了异常信息以及抛出异常的位置。
-
raise new_exception from other_exception
语句:是指将一个异常转换为另一种异常之后继续抛出。示例代码如下所示:
try: 1 / 0 except Exception as e: print(e) raise TypeError('Invalid Operation') from e
上述代码的try子句语句块中指定了一个除零运算,此操作会抛出异常ZeroDivisionError,在except子句中,将这个异常改为TypeError异常之后重新抛出。
上述代码的执行结果如下所示:
division by zero --------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) ~\AppData\Local\Temp/ipykernel_466964/2106895910.py in <module> 3 try: ----> 4 1 / 0 5 except Exception as e: ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: TypeError Traceback (most recent call last) ~\AppData\Local\Temp/ipykernel_466964/2106895910.py in <module> 5 except Exception as e: 6 print(e) ----> 7 raise TypeError('Invalid Operation') from e TypeError: Invalid Operation
上述结果中列出了原始的异常信息,以及异常改变之后的新的异常信息以及异常位置。
-
assert test, data
语句,相当于条件化的raise
语句,等效于如下代码:if __debug__: if not test: raise AssertionError(data)
在上述的等效代码中,当test为False的时候,就会抛出信息为data的
AssertionError
异常,如果test为True,则不抛出异常。上述的等效代码中,
__debug__
默认为True,除非在命令行中指定-O
选项,此时才会禁用掉assert
语句。具体示例代码如下所示:
def f(x): assert x < 0, 'x must be a negative number' return x ** 2 res1 = f(-2) print('res1 = {}'.format(res1)) res2 = f(3) print('res2 = {}'.format(res2))
上述代码主要是将负数返回为其平方数,如果传入的参数是正数,则抛出异常。具体如下所示:
res1 = 4 --------------------------------------------------------------------------- AssertionError Traceback (most recent call last) ~\AppData\Local\Temp/ipykernel_466964/44935144.py in <module> 8 res1 = f(-2) 9 print('res1 = {}'.format(res1)) ---> 10 res2 = f(3) 11 print('res2 = {}'.format(res2)) ~\AppData\Local\Temp/ipykernel_466964/44935144.py in f(x) 2 3 def f(x): ----> 4 assert x < 0, 'x must be a negative number' 5 return x ** 2 6 AssertionError: x must be a negative number
上述执行结果中给出了异常信息以及异常发生的位置。
1.3. 自定义异常
在builtins模块中,定义了一些内建异常,其中自定义异常最常用的异常基类就是Exception
这个异常类,内建异常中很多都是Exception
异常类的子类。该类的基类为BaseException
异常类。
1.3.1. 异常关系
在Python解释器启动的时候,已经默认加载了__builtins__
模块,其等效于builtins
这个模块。具体如下所示:
import builtins
type(__builtins__)
__builtins__ == builtins
要列出内建模块中的异常类,可以执行如下代码:
import string
[x for x in dir(__builtins__) if x[0] in string.ascii_uppercase]
通过如下代码可以查看Exception类的子类:
print(Exception.__subclasses__())
通过如下代码可以查看Exception异常类的基类:
print(Exception.__bases__) # (BaseException,)
通过如下代码可以查看Exception异常类的方法解析顺序:
Exception.mro() # [Exception, BaseException, object]
1.3.2. 实现自定义异常
如果要实现自定义异常,通常是以Exception
异常类作为基类,实现自定义异常类。
实现自定义异常类的代码如下所示:
class NoMoneyException(Exception): pass class OutOfBudgetException(Exception): pass balance = int(input('Enter your balance >>> ')) if balance < 1000: raise NoMoneyException('You have not enough money! Please earn some more money~~') else: raise OutOfBudgetException('You are so rich! Have a nice day~~')
上述代码的执行结果如下所示:
Enter your balance >>> 900 --------------------------------------------------------------------------- NoMoneyException Traceback (most recent call last) ~\AppData\Local\Temp/ipykernel_466964/2428185426.py in <module> 11 12 if balance < 1000: ---> 13 raise NoMoneyException('You have not enough money! Please earn some more money~~') 14 else: 15 raise OutOfBudgetException('You are so rich! Have a nice day~~') NoMoneyException: You have not enough money! Please earn some more money~~
上述结果中可以看出,当余额低于1000的时候,提示钱不够的异常。
再次执行,此时输入的数额大于1000,执行结果如下所示:
Enter your balance >>> 1500 --------------------------------------------------------------------------- OutOfBudgetException Traceback (most recent call last) ~\AppData\Local\Temp/ipykernel_466964/2428185426.py in <module> 13 raise NoMoneyException('You have not enough money! Please earn some more money~~') 14 else: ---> 15 raise OutOfBudgetException('You are so rich! Have a nice day~~') OutOfBudgetException: You are so rich! Have a nice day~~
当余额超过1000之后,提示超过预算的异常。
根据上述的不同异常抛出,可以实现对代码不同流程的控制。
在上述代码中结合
try...except
语句,如下所示:class NoMoneyException(Exception): pass class OutOfBudgetException(Exception): pass balance = int(input('Enter your balance >>> ')) try: assert not balance < 1000, 'You have not enough money! Please earn some more money~~' except NoMoneyException as e1: print(e1) else: raise OutOfBudgetException('You are so rich! Have a nice day~~')
上述代码的执行结果如下所示:
Enter your balance >>> 999 --------------------------------------------------------------------------- AssertionError Traceback (most recent call last) ~\AppData\Local\Temp/ipykernel_466964/1774503727.py in <module> 12 13 try: ---> 14 assert not balance < 1000, 'NoMoneyException: You have not enough money! Please earn some more money~~' 15 except NoMoneyException as e1: 16 print(e1) AssertionError: NoMoneyException: You have not enough money! Please earn some more money~~
上述代码中,在
assert test, data
子句中,当前面的测试结果为False的时候,抛出AssertionError异常,而当test结果为True的时候,即try子句中没有异常抛出的时候,则执行else子句部分,抛出OutOfBudgeException异常。再次执行上述代码,如下所示:
Enter your balance >>> 1500 --------------------------------------------------------------------------- OutOfBudgetException Traceback (most recent call last) ~\AppData\Local\Temp/ipykernel_466964/1774503727.py in <module> 16 print(e1) 17 else: ---> 18 raise OutOfBudgetException('You are so rich! Have a nice day~~') OutOfBudgetException: You are so rich! Have a nice day~~
此时test部分的测试结果为True,所以执行else子句,抛出了OutOfBudgeException异常。
将上述代码做一下修改,重载__init__
方法。
具体如下所示:
class NoMoneyException(Exception): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) print('In my custom Exception: {}'.format(self.__class__.__name__)) class OutOfBudgetException(Exception): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) print('In my custom Exception: {}'.format(self.__class__.__name__)) balance = int(input('Enter your balance >>> ')) try: if balance < 1000: raise NoMoneyException('You have not enough money! Please earn some more money~~') else: raise OutOfBudgetException('You are so rich! Have a nice day~~') except NoMoneyException as e1: print(e1) except OutOfBudgetException as e2: print(e2)
当输入的数字小于1000的时候,执行结果如下所示:
Enter your balance >>> 999 In my custom Exception: NoMoneyException You have not enough money! Please earn some more money~~
当输入的结果大于等于1000的时候,执行结果如下所示:
Enter your balance >>> 1500 In my custom Exception: OutOfBudgetException You are so rich! Have a nice day~~
上述就是自定义异常类的实现示例。
2. 实现一个插件化开发的demo
插件化开发的主要目的是通过插件扩充程序主体的功能,在程序主体中动态加载插件,从而实现对程序主体的功能丰富和扩展。
要实现插件化开发,主要核心点在于如何动态识别以及装载插件目录中的插件。为此,就需要使用到Python的importlib
库提供的import_module()
方法。
2.1. 示例代码文件组织
在下面的示例代码中,在工作目录下创建一个名为plugin-main.py的主程序文件;另外还需要一个插件目录,用于存放相关的插件,为此创建一个名为plugins的目录。
主程序plugin-main.py中定义了一个名为Document的类,在其中定义了初始化实例对象的__init__()
方法,以及两个用于动态装载插件的方法:dynamic_load_plugins()
以及run_plugin()
。前者用于提取插件的文件名,并将提取的文件名作为参数传递给run_plugin()
方法。后者实现插件的动态装载操作。
在plugins目录中,创建了两个文件:pdf-plugin.py以及word-plugin.py。在这两个文件中,分别打印pdf相关的信息以及word相关的信息。
具体目录结构如下图所示:
2.2. 代码内容以及执行结果
主程序文件以及插件文件的内容如下:
-
主文件plugin-main.py的文件内容:
代码内容如下所示:
# plugin-main import os.path as opth import importlib import glob class Document: def __init__(self, file_name, file_type, plugin_path): """ 初始化实例对象 :param file_name: :param file_type: :param plugin_path: # 用于装载插件 """ self.fname = file_name self.ftype = file_type self.ppth = plugin_path self.dynamic_load_plugins() def dynamic_load_plugins(self): """ 根据不同的文件类型的实例对象,调用不同的插件 """ plugpth = opth.abspath(opth.curdir) + '\\plugins\\' if self.ftype == 'pdf': plug_f = glob.glob(plugpth + '*pdf*.py') plug_name = plug_f[0].split('\\')[-1][:-3] self.run_plugin(plug_name) else: plug_f = glob.glob(plugpth + '*word*.py') plug_name = plug_f[0].split('\\')[-1][:-3] self.run_plugin(plug_name) def run_plugin(self, plugin_name): """ 动态加载插件 :param plugin_name: :return: """ plugin = self.ppth + plugin_name mod = importlib.import_module(plugin) mod.print_doc('.'.join([self.fname, self.ftype])) if __name__ == '__main__': plugin_path = 'plugins.' pdf1 = Document('my_first_pdf', 'pdf', plugin_path) print('-' * 10) word1 = Document('my_first_word', 'docx', plugin_path)
上述就是主文件的代码内容
-
插件目录下的pdf-plugin.py文件
plugins/pdf-plugin.py文件的内容如下所示:
# plugin1 for plugin-main def print_doc(pdf_obj): print('in pdf-plugin.py, pdf_print {}'.format(pdf_obj)) if __name__ == '__main__': pass
-
插件目录下的word-plugin.py文件
plugins/word-plugin.py文件的内容如下所示:
# plugin2 for plugin-main def print_doc(word_obj): print('in word-plugin.py, word_print {}'.format(word_obj)) if __name__ == '__main__': pass
主文件的执行结果如下所示:
in pdf-plugin.py, pdf_print my_first_pdf.pdf
----------
in word-plugin.py, word_print my_first_word.docx
Process finished with exit code 0
上述即为插件化开发的简单示例代码。
3. 列出git支持的常用命令及说明
git是一个开源的分布式版本控制系统,其提供了非常强大的命令行工具。git常用的命令行如下所示:
-
git config
:用于配置或者查询配置信息。该命令有几个不同的选项,用于指定配置信息影响的范围:
--system
:选项表示影响所有用户,全局配置--global
:选项表示影响当前用户的所有仓库,单个用户的配置--local
:选项表示影响当前用户的当前仓库,单个用户的单个仓库配置--worktree
:选项表示影响当前用户的当前仓库的每个不同的工作目录
其他一些常用的选项参数如下:
git config --list
命令可以列出默认的配置git config --file config_file
命令用于指定配置文件的位置git config --global color.ui auto
用于配置特定的信息,此处设置为命令行输出中自动显示颜色
-
git status
:查看当前工作路径或者仓库的文件状态 -
git diff
:显示当前工作路径或者仓库中已经被更改但是还未暂存的文件,发生了什么更改git diff --staged
用于显示已经发生更改并且被暂存的文件与上一次提交之间发生了哪些更改git diff first-branch second-branch
用于显示两个不同分支之间的内容差异
-
git add files
:将指定的文件纳入版本控制管理 -
git reset [file]
:用于将已经更改并且被暂存的文件从暂存空间中剔除,但是保留文件已经更改的内容 -
git commit -a -m "[description message]"
:将文件的提交记录到版本变更历史中 -
git branch
:列出当前仓库中的所有分支git branch branch_name
用于创建新的分支git branch -d branch-name
删除指定的分支
-
git checkout branch-name
:用于切换分支,并且更改当前工作目录的内容为新分支的内容git checkout -b branch-name
创建一个新分支branch-name并且切换到这个新分支,同时将工作目录的内容变更为新分支的内容
-
git rm file
:从工作目录以及暂存空间中删除指定的文件git rm --cached file
从版本控制系统中移除指定的文件,但是在当前目录中仍然保存该文件
-
git mv file-original file-renamed
:更改版本控制系统中的文件名,并准备提交更名后的文件 -
git ls-files
:列出当前版本控制系统中的文件如果存在
.gitignore
这个文件,并且其中记录了如下内容:*.log build/ temp-*
此时.gitignore文件将压制
git ls-files
命令的输出结果。如果要显示出这些被压制显示的内容,可以执行如下命令:
git ls-files --other --ignored --exclude-standard
-
git stash
:用于临时保存版本库中所有已经发生更改的文件git stash pop
恢复最近临时保存修改状态的文件git stash list
列出所有被临时保存修改状态的文件git stash drop
忽略最近一次被临时保存的修改状态
-
git log
:列出当前分支的版本历史git log --follow file
列出指定文件的版本历史,包括重命名操作
-
git show commit
:用于显示指定提交的元数据信息以及更改情况 -
git reset commit
:用于撤销commit之后的所有提交,保留本地的修改情况git reset hard commit
:将所有的历史和更改情况都恢复到指定的提交时的状态
-
git fetch bookmark
:从bookmark仓库中下载所有的版本历史记录 -
git merge bookmark/branch
:将bookmark仓库的branch分支合并到本地版本库的当前分支上 -
git push alias branch
:将本地版本库中的branch分支提交到alias指定的远程仓库中 -
git pull bookmark
:将bookmark仓库的版本历史记录下载到本地并且合并更改 -
git clone bookmark local-repo
:将bookmark仓库克隆为本地的local-repo仓库 -
git init
:用于初始化本地仓库
4. References
[1]. Try-Except
[2]. python插件式框架开发