异常捕获

基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
try:  
a = int(input("输入被除数:"))
b = int(input("输入除数:"))
c = a / b
print("您输入的两个数相除的结果是:", c)
except ValueError as e: # e 表示捕获的实例
print("输入内容不是合法滴数字呢")
except ZeroDivisionError as e:
print("除数不能为零呢")
except (ValueError,ZeroDivisionError) as e: #把两个异常结合在一起的写法
print("输入数字异常呢")
except Exception as e : # 捕获商量捕获不了的异常
print("出现未知异常呢")
print(e.args)
print(str(e))
print(repr(e))
else: # 没有出现异常之后,会执行 else 里面的代码
print("计算成功,没有出现异常")
finally: # 无论有没有异常都会执行的代码,即使异常没有被捕获
print("计算结束")

异常信息

我们捕获到了对应的异常实例之后就需要对异常获取信息处理:

  • e.args:返回异常的错误编号和描述字符串;
  • str(e):返回异常信息,但不包括异常信息的类型;
  • repr(e):返回较全的异常信息,包括异常信息的类型。

异常继承结构

338.jpg

如果要自己实现异常的时候,就一定要找对上层异常去继承。

异常

我们可以手动抛出异常 关键字 raise

可以在任何地方使用 raise,抛出一个异常的实例。

1
2
3
raise e # 在 except 语句里面抛出捕获的异常   
raise Exception # 新建异常 Exception
raise Exception("异常原因 XXX")

查看异常信息

导入 traceback 模块,调用方法:

  • traceback.print_exc() 控制台输出内容

  • traceback.print_exc(file=open('filename','a')) 文件 filename 中输出内容

就直接在 except 里面调用即可。

日志系统

Python 自带 logging 模块可以使用,

基本设置

感觉确实没有 Java 的日志系统使用起来更方便一点。导入 logging 模块之后,需要用 baseConfig() 进行最基本的设置,常用的参数如下:

1
2
3
4
5
6
7
8
filename: str | PathLike[str] | None = ...,  
filemode: str = ...,
format: str = ...,
datefmt: str | None = ...,
style: str = ...,
level: int | str | None = ...,
stream: SupportsWrite[str] | None = ...,
handlers: Iterable[Handler] | None = ...) -None

主要集中在 level (日志显示最低等级),format 输出日志格式,这两个上面。

1
2
import logging  
logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s')

日志级别

我们只要调用 logging 对应的日志输出函数,里面填充日志 message 内容

级别 对应的函数 描述
DEBUG logging.debug() 最低级别,用于小细节,通常只有在诊断问题时,才会关心这些消息。
INFO logging.info() 用于记录程序中一般事件的信息,或确认一切工作正常。
WARNING logging.warning() 用于表示可能的问题,它不会阻止程序的工作,但将来可能会。
ERROR logging.error() 用于记录错误,它导致程序做某事失败。
CRITICAL logging.critical() 最高级别,用于表示致命的错误,它导致或将要导致程序完全停止工作。

具体的日志系统如何使用,以后写项目再研究。

模块

Python 一个 py 文件就是一个模块,可以通过 import 导入进来,然后通过文件名作为此文件代码的命名空间,来访问里面的变量和函数。

import

import 有两种写法:

  • import module_name as alias 将模块 module_name.py 导入,并起别名 alias(可以不起别名),然后就可以别名作为命名空间访问里面的变量和模块,就像类一样。

  • from module_name import func/var 将变量或者函数单独导入,这样访问就不需要指定命名空间,用名字就可以了。

当然如果遇到开头是空格或者数字的模块名,那就这样导入:

__import__(modulename:str) 这是等价于 import 的内建函数。

__name__

在运行程序的时候,会首先看所有 import 的模块,将这些模块的代码逐一经过解释器执行。但是对于模块中一些不想被导入是执行的代码,可以通过判断命名空间来决定是否执行,内建模块自带 __name__ 变量。

在运行一个 Python 文件的时候,如果执行文件里面的代码,这个内建模块变量 __name__ 的值为 __main__,当执行到 import 导入模块的时候,会改变 __name__ 值为模块名字,然后再解释执行模块里面的内容。

所以当时直接运行模块文件的时候 __name__ 值为 __main__,我们可以运行一些代码执行,当时作为模块导入另外的文件的时候,值就会变成文件名,就可以不运行这些代码。

1
2
if __name__ == '__main__':  
main()

模块文档

只需要用 ''' 包裹在文件开始即可。然后再调用的地方可以用 模块名.__doc__ 获取这些字符串里面的内容。

模块查找

这里的模块定位查找:

  • 在当前目录,即当前执行的程序文件所在目录下查找;

  • 到 PYTHONPATH(环境变量)下的每个目录中查找;

  • 到 Python 默认的安装目录下查找。

这些目录都可以在 sys.path 中输出查看。


当 Python 程序找不到模块的时候:

  • 向 sys.path 中临时添加模块文件存储位置的完整路径;

  • 将模块放在 sys.path 变量中已包含的模块加载路径中;

  • 设置 path 系统环境变量。

模块封装

  1. 当用户通过 import 导入模块命名空间来访问模块成员的时候,如果想要限制一些方法的访问,可以使用 _ 或者 __ 作为开头限制访问。

  2. 当用户通过 from xxx import * 进行全部导入的时候,可以通过内建列表 __all__ 来设置允许被导入的成员名字。

1
__all__ = ['fun1','fun2','var1','var2']

导入本质

本质上就是创建了一个模块名的变量,类型是 <class 'module'>, 将模块里面的代码,交给了这个变量,通过这个变量来访问模块成员。

包 package

包是对模块的一种封装管理,是一个文件夹,文件夹的名字就是这个包的名字。这个文件夹里面可以包含另外一个包,也可以包含若干个模块。

基本结构

一般来说,一个 package 的文件夹下面都会有一个叫做 __init__.py 的模块,在单纯的导入包的时候,本质上就是导入了这个包的 __init__.py 模块。

比如我们可以新建一个文件夹叫 my_package,然后里面管理着两个模块 module1.pymodule2.py,那么这个完整的 package 如下:

1
2
3
4
my_package  
┠── __init__.py
┠── module1.py
┗━━ module2.py

导入 package

因为 package 相对于 module,多了一层概念,那么再导入的时候,也需要多一层 package 的名字:

有三种方式导入 package 中的 module:

  1. import 包名[.模块名 [as 别名]]
  2. from 包名 import 模块名 [as 别名]
  3. from 包名.模块名 import 成员名 [as 别名]
  • 对于第一种,如果只导入包名的话,其实是导入了 __init__.py 这个模块了。并不是同时导入 package 里面的所有 module。导入 包名.模块名 之后,除非起一个别名,否则还是用 包名.模块名 这样的命名空间来访问。

利用 __init__.py

实际上对与包的使用远远没有这么麻烦,比如在使用 requests 包的时候,可没对包里面的各种模块控制导入。这是因为第三方提供的包中,一般都会包的 __init__.py 模块中完成了整个包对外暴露的成员的控制,导入 __init__.py 就等于导入了整个包的功能。

因为导入包名其实就是导入了 __init__.py,所以只要在这个模块里面导入需要的成员,那么外界就可以借助着 __init__.py 也就是导入包名完成访问了。


__init__.py 编写导入模块代码的时候,写法和外部导入是一样的,不同的是包名使用 . 来代替,比如 requests 里面:

1
2
from . import utils # 在自己的包 request 中导入模块 utils  
from . import packages # 导入 packages

不过访问 __init__.py 里面导入的成员,肯定要加上包名这一层命名空间。