模块与包
2019-08-21 13:38:03 0 举报
AI智能生成
python知识点:模块、包以及异常处理
作者其他创作
大纲/内容
模块、包与异常处理
模块*****
定义:
别人写好的,具有相同类别的一组功能具体样式可能是 文件夹/py文件/C语言编译好的一些编译文件
作用:
1.分类管理方法
2.节省内存
3.提供更多的功能
分类:
内置模块
跟着python解释器一起安装的那些方法
第三方模块/扩展模块
没在安装python解释器一起装上的那些功能
自定义模块
自己写的功能如果是个通用的功能,就可以当做一个模块
模块的导入:
import 文件名 #导入一个py文件的名字,但不加.py
import 一个模块,相当于执行了这个模块所在的py文件
一个模块不会被重复导入
所有的模块导入都应该尽量放在这个文件的开头
导入模块的过程发生了什么?
1.找到这个my_module模块
2.创建一个属于my_module的内存空间
3.从上到下执行my_module的代码
4.将这个模块所在的内存空间建立一个个my_module之间的引用关系
模块有自己的内存空间,与本文件(即自己写的代码,导入模块的文件)的内存空间是相互隔离的,只是可以通过模块名引用 ##
导入多个模块:
但python开发规范PEP8规定,导入多个模块的时候,不应在一行导入,应该写多行
import osimport my_module (√)
模块导入顺序
# 先导入内置模块 # 再导入第三方模块 # 最后导入自定义模块
使用方法:
模块名.方法名() #my_module.login()
模块名相当于一个变量来使用,因此,模块的名字必须遵循变量的命名规则
一般模块名,都是小写字母开头
模块的重命名:
import my_module as m
并非是把模块的名字改了,只是把引用模块的变量名改了
导入模块的指定部分
语法:from 模块名 import 需要导入的变量/方法名
任然相当于执行了一遍整个py文件
from my_module import login的时候发生了什么?
4.知道了要imprt的是login这个方法,那么就在本文件中创建一个变量login,指向模块命名空间中的login方法
注意:
# 导入了什么就能使用什么,没有导入的变量不能使用
# 没有导入不等于不存在,只是没有建立本文件到模块中其他名字的引用
# 当模块中导入的方法或变量与本文件中的重名时,那么这个名字只代表最后一次对他赋值的方法或者变量
# 被导入的模块不能反向引用本文件中的变量和方法
# 在文件中对全局变量的修改,完全不会影响对模块的引用要对模块中的变量进行修改,只有带着模块名的限定时(my_module.name = 'Zoey')才能修改,不过都不会这么做的
在本文件中对name重新赋值,只会断开name到模块中的name = 'Alex' 的引用,并将本文件的name赋值为'太亮',而模块中的name还是'alex'
重命名:
语法:from 模块名 import 需要导入的变量/方法名 as 新名字
导入多个变量/方法:
语法:from 模块名 import 变量名,方法名(每个用,隔开)
PEP8允许在一行导入多个变量
导入多个再重命名:
from 模块 import * :
模块中的所有东西都可以引用了,在本文件中都有一个与模块中的东西相同的变量名来引用
模块中写__all__可以控制*导入的内容
模块其他知识
1.把模块当成脚本运行
运行一个py文件的两种方式
①以模块的方式运行:import my_module
②以脚本的形式运行:即直接用 pycharm 运行或者用 cmd 运行
__name__内置变量
模块方式执行时__name__ = 'my_module'
直接运行时__name__ = '__main__'
模块代码中没有直接封装在函数或者类,不需要调用就能执行的代码都应该写在 if __name__ = 'main': 这句话下面(快捷写法main写完按TAB键)。这样,模块在作为脚本运行时就会正常运行,而在以模块的形式导入其他文件中时,导入后就不会自动输出一些有的没的
2.模块搜索路径
模块的搜索路径指的就是在导入模块时需要检索的文件夹
导入模块时查找模块的顺序是:①先从内存中已经导入的模块中寻找②内置的模块③环境变量sys.path中找
能否导入自定义模块,要看sys.path列表中是否有要调用的文件的绝对路径可以用sys.path.append('文件路径')来添加可以用del sys.path[-1]来删除新添加的路径
3.pyc编译文件
4.重新加载模块
模块只导入一次,不会重复导入因此,模块导入之后,再修改模块,即使再重新导入也与我无关了
要想重新加载只有用importlib.reload(模块名),就可以强制重新加载一遍模块此方法就只是用来玩一下,不可以用在开发中!!!
5.模块的循环引入
在模块导入中,坚决不要发生循环导入问题如果发生了循环导入,则会发现,明明写在模块的方法,却提示找不到# 多个文件之间有导入关系,画个图看看导入关系是否成环
6.整一个base_path作为项目的基本路径,使导模块更方便
print(__file__)会输出执行此语句的文件所在的绝对路径
7.永远不要给一个文件起一个与所有已知模块相同的文件名,不然会影响模块导入使用的 (注意有坑!!!)
包***
1.定义:
集合了一组py文件,提供了一组复杂功能的,有__init__的文件夹
2.作用:
提供的功能比较复杂,一个py文件写不下
3.包中都有什么:
至少拥有一个__init__.py
4.直接导入模块:
①import 包.包.模块 使用时:包.包.模块.变量
②from 包.包 import 模块(推荐使用)使用时:模块.变量
从包中导入模块,要注意这个包所在的目录是否早sys.path中
5.导入包(读框架源码的时候会用到)
导入包,相当于执行了这个包下面的__init__.py文件如果希望导入包之后,模块能正常使用,需要自己去完成__inti__文件的开发可以设计一下__init__.py来完成一些模块的导入:在init里面导入下一层包/模块
包中模块的绝对导入:
from glance import apifrom glance.api import policy绝对导入时,当前执行文件与包的相对位置不能变
包中模块的相对导入:
from . import apifrom . import policy# .表示当前目录位置(指正在编辑的__init__文件与需要导入的模块是否是在相同目录)# 使用了相对导入的模块,只能被当做模块执行,不能被当作脚本执行(即不能用pycharm直接run)
模块的总结
补充:项目开发规范
常用模块1(方法模块)
1.re模块*****
正则表达式
概念:一种匹配字符串的规则
作用:可以定制一个规则,①来确认某一字符串是否符合规则; ②从大段的字符串中找到符合规则的内容
在程序领域的应用:
①登录注册页的表单验证
②爬虫
③自动化开发:日志分析
正则表达式语法:
字符组:
[小-大]规定一个位置上能出现的内容(大和小是指ASCII码的值) [1-9A-Zabc] ==》匹配一个字符 [a-z][1-9][A-Z] ==》 匹配三个字符
元字符:
/d ==[0-9] 即/d也表示匹配数字 (digit)
/w == [0-9A-Za-z_] 即/w也表示匹配一个数字字母下划线 (word)
/s ==[/n /t] 即/s表示匹配所有的空白符,包括回车、空格、制表符tab (space) # /n表示匹配回车 # /t表示匹配制表符
/D 表示匹配非数字
/W 表示匹配非数字字母下划线
/S 表示匹配非空白符
^ 表示匹配字符串的开始 ****
$ 表示匹配字符串的结尾 ****
^hello$ ==》 只有一个hello可以匹配 (多个hallo就无法匹配)
. 匹配除换行符以外的任意字符(如果.要作为字符本身来匹配的话,需要加转意符/.) #在爬虫中用的多,在表单验证是很少用
a|b 表示匹配a或者b,任然是匹配一个字符
!!若两个字符有重叠部分的,要把长的放前面
() 分组
[...] 匹配字符组中的字符
[^…] 匹配字符组中的字符以外的
量词:
放在字符之后对字符进行约束,一个量词约束一个元字符或者一个字符组
量词匹配的一个原则:贪婪匹配 会给你匹配尽量多的次数(贪婪匹配内部使用的是回溯算法)
特殊用法和现象:
1.?的用法:
①紧跟在字符后面:字符重复一次或零次
2.字符组中一些特殊字符会现原形,不用转义
[ () +*/?$. ] 会现原形 而^以及所有带\\的都不会,只表示其本身含义[-]只有写在字符组首位的时候才表示普通负号,写在其他位置时都默认表示范围
3.要匹配正则表达式中的一些特殊字符本身,需要转义
在正则表达式中要匹配字符串/n ==> 正则表达式就应该写为//n ,在python代码中就直接两个都加r写成 r'/n' ==>r'//n'另外还如 /( 、//t、/^等等也是一样的
re模块:操作字符串的模块
1)匹配的方法
findall *****
返回值类型:列表
返回值个数:1
返回值内容:所有匹配上的项(没匹配上就是一个空列表)
font color=\"#f15a23\
search *****
返回值类型:匹配上了 --> 正则匹配结果对象 没匹配上 --> None
返回值内容:匹配上了 --> 对象 没匹配上 --> None
Python中分组遇见search:当search正则表达式中有分组时,可以通过给group(n)传参,拿到正则表达式中第n个分组所对应的匹配的内容不传n则默认是0,则拿到完整匹配结果
match **
与search语法、返回值都完全一样,不过match是从头匹配(即默认正则表达式最前面有一个^),因此匹配结果可能不同
对比:
2)替换的方法
sub ***
语法及参数:re.sub('正则表达式','替换成什么','需要操作的字符串',替换的次数) #如不指定替换的次数,默认全替换
返回值类型:字符串
返回值内容:替换后的字符串
subn ***
语法及参数:re.subn('正则表达式','替换成什么','需要操作的字符串')
返回值类型:元祖
返回值内容:(替换后的字符串,替换的次数)组成的元祖
3)切割的方法
split ***
返回值内容:所有切割后的字符串组成的列表
4)进阶方法(爬虫和自动化开发必备)
compile *****(对复杂正则表达式预编译)
正则表达式在python中运行过程:\\d*\\s?adsf*. --> 将正则表达式编译为python能理解的代码 --> 执行代码
语法及参数:re.compile('正则表达式').findall('需要操作的字符串')# 将增则表达式编译为代码储存起来,以便后面每次都可以直接用
用法:ret = re.compile('很长的正则表达式') ret.findall('需要操作的字符串') ret.search('需要操作的字符串')等等
作用:节省时间(时间效率)只有多次使用同一个正则表达式的时候,才会提高代码效率
finditer *****(对海量数据进行处理)
一个海量数据中,匹配正则表达式的字符串结果也是一个庞大的数据量,finditer就将这个庞大数据量的匹配结果转化成为一个可迭代对象,就可以使用for循环来进行下一步处理了
相关值的类型:ret 可迭代对象的内存地址 r 正则匹配结果对象
作用:节省内存(空间效率)
分组命名
语法:(?P<name>正则表达式) 表示给分组起名字 (?P=name) 表示使用这个分组
通过索引使用分组
\\1 表示使用第一组,匹配到的内容必须和第一组相同
2.random模块***** (随机)
应用:抽奖、彩票、发红包、验证码
1)获取随机小数的方法
①random获取0-1之间的随机小数
语法:random.random()
②uniform获取n到m之间的随机小数
2)获取随机整数的方法 *****
3)随机抽取
①choice随机抽取一个值元祖/列表/字符串/range()
②sample随机抽取n个值元祖/列表/字符串/range()
4)打乱顺序在原列表的基础上做乱序只能操作列表,dict都不行
3.time模块****(描述/获取时间)
1)三种时间的格式
①时间戳时间(格林威治时间/float数据类型时间)(是给机器用的)语法:time.time()获取的是伦敦时间1970年1月1日0:0:0(北京时间1970年1月1日8:0:0)起至当前所经历的秒数
②结构化时间:是一个时间对象,可以通过.属性名来获取对象的值,结构类似于一个元祖。(前两种格式的转换桥梁)语法:time.localtime() 通过结构化时间,可以将时间戳时间转换为格式化时间
③格式化时间(字符串时间/str数据类型时间)(给人看的)语法:time.strftime(%Y-%m-%d) #2019-08-14 time.strftime(%H:%M:%S) #21:33:50可以根据你需要的格式来显示时间
%y 两位数的年份表示(00-99)%Y 四位数的年份表示(000-9999)%m 月份(01-12)%d 月内中的一天(0-31)%H 24小时制小时数(0-23)%I 12小时制小时数(01-12)%M 分钟数(00=59)%S 秒(00-59)
2)三种格式之间的转换
4.sys模块****(与python解释器交互)
1)sys.path:当前python解释器寻找模块的路径
2)sys.modules:当前文件在内存中导入的所有路径
用的最多的地方:反射本模块的时候用,sys.modules[__name__],其他时候几乎不会用到
3)sys.exit():强制python解释器结束程序
基本不会用,都直接用内置函数exit()
4)sys.argv:
②返回值解读:第一个元素是执行这个文件时,写在python命令后面的第一个值。之后的元素,执行Python启动的时候,在python命令后面写其他内容,都会被添加到这个列表中
③应用:执行py文件的时候先做一个登录认证,认证成功才给你执行这个py文件,认证不成功就直接给退出(可以不用再执行python文件之后再input认证)
这样做的优势:一是符合运维人员的工作习惯;二是可以避免input阻塞,顺畅系统调度,提高系统执行效率
代码:name = sys.argv[1]pwd = sys.argv[2]if name == 'alex' and pwd == 'alex3714': print('执行以下代码')else: exit()
5.os模块*****(与操作系统交互)
1)os.系列
(1)和工作目录相关的
①os.getcwd():获取当前文件的工作路径(即在哪里执行的当前文件)
②os.chdir('新路径'):修改当前文件的工作路径(不管在哪执行,都把工作路径给强制改为新路径)相当于shell下cd
(2)创建/删除文件、文件夹相关的****
①os.mkdir('dirname'):创建单级目录(这个文件夹与当前文件同级)
应用:网盘
②os.makedirs('dir1/dir2/dir3'):创建多级目录
③os.rmdir('dir1/dir2/dir3'):删除单级目录
#只能删除一级文件夹(即dir3)
#只能删除空文件夹,非空的删不了
④os.removedirs('dir1/dir2/dir3'):删除多级目录
#递归向上删除文件夹,直至遇到非空文件夹为止
#只能删除空文件夹,非空的删不了。如果dir1空,dir2非空,dir3空,运行结果就只是删dir3
⑤os.remove('aaa.py'):删除一个文件
⑥os.rename(\"oldname\
⑦os.listdir('路径')*****
列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表格式返回
(3)和操作系统差异相关的
①os.stat('path/filename'):获取文件/目录信息
②os.sep:查看当前所在的操作系统的目录分割符
Windows:\\Linux:/
③os.linesep:输出当前平台使用的行终止符
win:\"\\\"Linux:\"\\"
要打印出确切的符号,应该写为:print([os.linesep]),不然只会看到一个空行
④os.pathsep:查看用于分割文件路径的字符串
win下为;Linux下为:
⑤os.name:查看字符串指示当前使用平台
win:'nt'Linux、Mac:'posix'
有什么用呢?主要应用于如果写的程序跨平台,在Windows上能使用,在Linux上也能使用,代码中要进行路径拼接等操作的时候,就需要进行判断所在平台和选择不同平台适用的分隔符等,用这些就便于python编程时更好的兼容不同平台
(4)使用python来和操作系统命令交互相关的
自动化开发/运维必须会这两个***
①os.system('命令'):运行shell命令
#直接按操作系统的编码方式显示,且不需要print
只执行,不返回结果,因此不能后续操作
应用场景:删除文件、copy文件等不关心返回值的操作
②os.popen('命令'):运行shell命令
返回一个对象:ret = os.popen('dir') ret是个对象,然后ret.read()就能读取到内存,就可以直接操作了,ret.read()类型是str
应用场景:查看当前路径、查看某些信息的时候
(5)查看环境变量
③os.environ:查看操作系统的环境变量
返回值类型:字典。环境变量:值
2)os.path.系列
①os.path.abspath('路径'):返回path规范化的绝对路径
如果给的是个相对路径,他会给你返回绝对路径
如果给的是一个不规范的路径(即左斜杠/),他会给你转成一个规范化的路径(即右斜杠\\),并且这个\\是已经转义好的\\
②os.path.split('路径'):将路径分割成目录和文件名的二元元组返回
返回值类型:('目录路径','文件名')
print(os.path.split('D:/sylar/python_workspace/day25/5.os模块.py'))#('D:/sylar/python_workspace/day25','5.os模块.py')
③os.path.dirname('路径'):目录路径,就是split的第一个元素
应用:去拿项目的base_path,用__file__拿到当前目录之后,直接网上翻几层就得到了os.path.dirname(os.path.dirname(__file__))
④os.path.basename('路径'):文件名,就是split的第二个元素
⑤os.path.exist('路径'):判断路径是否存在
返回值:True/False
⑥os.path.isabs('路径'):判断路径是否是绝对路径
⑥os.path.isfile('路径'):判断路径是否是一个存在的文件
⑥os.path.isdir('路径'):判断路径是否是一个存在的目录
#第一个绝对路径之前的参数将被忽略
⑧os.path.getatime('路径'):返回路径所指向的文件或者目录的最后访问时间
⑨os.path.getmtime('路径'):返回路径所指向的文件或者目录的最后修改时间
⑩os.path.getsize('路径'):返回路径指向的文件或者目录的大小*****
#文件的大小都能准确统计,但目录的大小统一都返回4096单位:字节
面试题:统计一个文件夹中所有文件的总的大小
6.序列化模块*****
1)定义
序列化:将原本的字典、列表、数字、对象等内容转换成一个字符串的过程
反序列化:将字符串装换成原本的列表、对象等数据类型的过程
2)为什么要序列化?
①要把内容写入文件的时候就要序列化,因为写文件只能写字符串
②网络传输数据时就要序列化,因为网络通过高低电压传输10101二进制,二进制由于bytes最接近,而bytes与字符串最接近
3)方法
(1)json模块
方法:
①json.dumps():序列化
#字符串是由“”括起来的
②json.loads():反序列化
返回值类型:原数据类型
④json.load(f):读。直接将文件中的内容反序列化
# with open('json_dump2') as f:# print(json.load(f))
json的限制情况:
json格式的限制2:经过json序列化之后的数据,字符串都是用“”来的,经过反序列化会回到‘’。因此如果要对一个数据进行反序列化,那么所有字符串类型的数据都必须是“”括起来,否则会报错
④能否多次dump数据到一个文件里:可以多次dump,但没法load(写可以写,但读不了,只能一次读一个变量)
用dumps可以实现:
⑤对中文格式的数据dump之后,会在文件里显示编码,需要将参数ensure_ascii = False
⑥set不能被dump/dumps
⑦json的其他参数:为了让程序员看得更方便,但写入文件的话,会占不必要的空间
2)pickle模块
pickle序列化之后的返回值是bytes数据类型
优缺点:
优点:可以处理任意数据类型,且可以将数据原封不动的反序列化而不作任何改变。
缺点:序列化之后无法直接看出原数据内容(但实际无所谓,因为序列化的目的是把数据存入内存,之后要再拿出来的,如果只是储存来供人看得话,根本不要序列化)。
①pickle.dumps()
对象都可以序列化
②pickle.loads()
③pickle.dump()
④pickle.load()
能多次dump和load数据:
3)shelve模块(削微模块^_^)(用的较少)
适用于:
如果写定了一个文件,且改动的比较少,读文件的操作比较多,并且你大部分的读取都需要基于某个key获得某个value,name就适合使用shelve
取值:# f = shelve.open('shelve_demo')# content = f['key']# f.close()# print(content)
7.collections模块***(数据类型的扩展模块)
双端队列deque:
什么是队列?先进先出的一列数据
一端放,另一端取,且看不到队列里的值和顺序
适用于网上购票等排队的事项
什么是双端队列?
两端都可以放,两端都可以取,先放的在中间,后方的在两端,取得时候先取两端的,且能看到队列里的值和顺序
collection实际是个包,因此需要from collection import deque
①默认从右边操作
②可以按内容删除
③可以在指定位置插入内容
双端队列的操作效果和list差不多,区别在哪呢?
①列表是一个连续内存空间,如果使用remove/insert比较多的话,每一次所有元素都需要挪一遍位置,列表的效率很低
②双端队列的底层使用的是C语言的一个叫链表的数据类型,链表的内存空间不连续,是一个个独立有用地址相连系的内存空间
总结:
在insert/remove的时候,deque的平均效率要高于列表
列表根据索引查看某个值的效率要高于deque
append和pop对于列表的效率是没有影响
常用模块2面向对象相关
1.hashlib模块*****(提供常用的摘要算法的模块)
能够把一个字符串数据类型的变量,转变成一个定长的、密文的字符串,且这个字符串的每一个字符都是一个16进制数。
1)摘要算法特点:
①只能将字符串转为密文,不能将密文再转回原字符串(转密不可逆)
②对于同一字符串(不管有多长),无论在任何环境下、多少次执行、在任何语言中,用相同的算法、相同的手段进行摘要,获得的值也总是相同的
③只要不是相同的字符串,得到的结果一定不同
2)常用的摘要算法:
md5与sha1算法比较:md5算法效率更快,算法相对稍微简单一点
(1)md5算法:
结果:32位的字符串,每个字符都是一个16进制数
应用:
①注册时用户名和密码的加密储存
代码:#s='alex3714'#md5_obj = hashlib.md5()#md5_obj.update(s.encoding('utf-8'))#res = md5_obj.hexdigist()#print(res)
注:可以对s进行分段update,最终结果不变
②文件的一致性校验
大文件的一致性校验:
md5不够安全:因为使用的人太多,存在撞库现象
怎样解决?
①加\"盐\"# md5_obj = hashlib.md5('任意的字符串作为盐'.encode('utf-8'))# md5_obj.update(s1.encode('utf-8'))# res = md5_obj.hexdigest()
②动态加盐将username作为盐
(2)sha1算法:
结果:40位的字符串,每个字符都是一个16进制数
语法:#s='alex3714'#sha1_obj = hashlib.sha1()#sha1_obj.update(s.encoding('utf-8'))#res = sha1_obj.hexdigist()#print(res)
也适合动态加盐
sha算法后面的数字越大,算法越复杂,结果越长,计算速度越慢,安全性更高(是因为用的人少所以更安全)
2.configparse模块*(处理配置文件的模块)
可以将按固定格式写的.ini配置文件直接解析,然后可以进行字符串的操作
.ini配置文件的格式:
[section1]键1 = 值1键2 = 值2[section2]键3 = 值3
3.logging模块*****
1)功能:
①日志格式的规范
②操作的简化
③日志的分级管理
logging模块不能自动生成你要打印的内容。需要程序员自己在开发的时候定义好在哪些地方需要打印、打印的内容、打印的级别
2)使用
(1)普通配置型
简单的,可定制化差
(2)对象配置型
复杂的,可定制化强
3)认识日志的分级
①调试模式:logging.debug('debug message')
②基础信息模式:logging.info('info message')
③警告:logging.warning('warning message')
④错误:logging.error('error message')
⑤严重错误:logging.critical('critical message')
默认会输出警告以上的错误。如果想显示①②,需要写一句logging.basicConfig(level = logging.DEBUG)
4)logging.basicConfig()函数可配置logging模块的参数
basicConfig矛盾点:不能将一个log信息既输出到文件,又输出到屏幕
5)logger对象的形式来操作日志文件
异常处理
1.什么是一异常,以及异常与错误的区别:
Iteration:异常,是在代码执行过程中引发的
Error:语法错误,在编译代码阶段就检测出来,比较明显
2.异常发生时候的效果:
一旦在程序中发生异常,程序就不再执行了
3.如何看报错信息:
从下到上的看,往往最后一条就是错误的点
4.处理最简单的异常:
5.多分支异常处理:
# try:# num = int(input('num :'))# print(l[num-1])# except ValueError:# print('请输入一个数字')# except IndexError:# print('您输入的数字无效')
6.万能异常:
Exception是所有异常类的父类,包含99.9%的异常;BaseException是Exception的父类,包含剩余的几个不继承Exception的异常。
这里的变量名就是异常的那个实例化对象
7.万能异常与其他分支合作:
except会从上往下走,找一第一个符合的就执行,因此万能异常必须放在所有except的最后
# except NameError:pass# except IndexError:pass# except Exception:pass
8.异常处理的其他机制
9.主动抛异常:
比如填验证码,用户填半天才填好,就可以主动扔一个异常到用户脸上
raise NameError(这里可以写任意的内置异常)
如果只写raise,那么本来是什么异常,就会抛什么异常
10.自定义异常:
11.断言:(基本不用 源码中才会看到)
语法:assert 布尔值
与if条件判断的效果一样,但在源码中会看起来更简洁
assert True就往下走
assert False就抛异常
12.使用异常处理的注意事项:
(1)assert断言、raise主动抛异常,一般只有在写框架的时候才会用到
(2)代码开发过程中
①尽量少用异常处理,能用逻辑规避的应该用代码逻辑规避掉,
②用异常处理时,应该对某一句或几句话进行处理,而不应该在一个函数外部进行try/except
(3)当这个程序已经全部测试完成,没有问题了,那么应该在最外层加一个异常处理,避免程序动不动就闪退或者崩溃
0 条评论
回复 删除
下一页