@coder-pig
2018-07-26T11:25:55.000000Z
字数 7784
阅读 1297
Python写书
在前面的学习中其实已经陆陆续续用到了一些模块,时间相关的time,datetime模块,文件相关的os和os.path模块,在本章会细化模块相关的概念以及注意事项。
本章主要学习内容:
模块(module)本质上还是一份py文件。可能有些读者会问:"py文件就行了,那我之前写的demo岂不是都可以当成模块来使用?"。答:对的,完全可以,不过有些规则要先了解下。这里还要说下包(package),其实就是多个模块放到一个文件夹或放到多个文件夹里,然后这些文件夹进行嵌套。
比如有a.py和b.py这两个模块,你在a中import b,就可以使用b.py里的函数了。导入示例如下:
代码示例如下:
import sys # 导入整个模块
from sys import argv # 导入模块中的需要用到的部分
from urllib.error import URLError, HTTPError # 多个的时候可以用逗号隔开
from sys import * # 导入模块中的所有
在导入其他模块的时候,会发现你写在里面的测试代码也会执行,你可以通过__name__对模块进行限定,是作为程序运行还是模块导入到其他程序中,而作为程序运行时该属性的值为
'__main__',只有单独运行的时候才会执行,比如简单的代码示例:
if __name__ == '__main__':
test()
问题来了:如果你自定义的模块名字和Python自带的模块冲突了,会加载哪个?
这个就跟Python对于模块搜索的顺序有关了,我们可以通过打印sys.path查看到这些搜索路径,比如我的:
['/Users/jay/Project/Python/Book/Chapter 6',
'/Users/jay/Project/Python/Book', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', '/Users/jay/Library/Python/3.6/lib/python/site-packages', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages']
从上面可以看出Python对于模块的搜索顺序是:程序所在目录,标准库安装目录,Python环境变量所包含的目录。所以如果你自定义的模块与内置的模块重名的话会调用你的模块而非内置的模块,所以在命名模块的时候还是尽量去规避重名这个问题吧。如果硬是要这样做而且两个模块都用到的话,可以使用完整路径引入,并通过as关键字为模块设置别名。另外如果你的模块不在这些搜索路径里,你可以通过调用sys.path.append("路径")把路径添加到搜索路径中。
如果你有看过一些别人的Python开源项目的话,你会发现在他的包文件夹里都会包含一个__init__.py文件。这个文件的主要作用是告诉Python这个文件夹是一个包,__init__.py可以是一个空文件,当然也可以加点东西控制包的导入行为,我们可以在__init__.py文件中批量导入我们所需的模块,而不用一个个导入:
# xxx包里的__init__.py
import a
import b
import c
# 调用这个模块的py文件
import xxx
print(xxx.a,xxx.b,xxx.c)
上述导入模块的代码可以简化下,通过all变量,示例如下:
# xxx包里的__init__.py
__all__ = ['a','b,'c']
# 调用这个模块的py文件
from xxx import *
前面说过了Python是一门实用性非常强的语言,随着不断地学习,你的编写的脚本也会越来越多。比如你写了一个爬取某壁纸网站的爬虫脚本,抓取了很多的壁纸。然后身边的小伙伴看到了,想问你拿U盘拷这些壁纸,偶尔拷一两次还好,多了就会感到厌烦了。想杜绝这样的烦恼,一种方法是为你的小伙伴安装Python环境,然后教会他通过命令行执行这个脚本:python xxx.py,但是大部分的情况是会报某个模块找不到,你需要通过pip命令一个个进行安装,一个小伙伴还好,如果有其他几个小伙伴也想要使用这个脚本呢?每个都走过去pip命令一个个装?显得非常繁琐,可不可以导出一个依赖文件,执行下一些操作就完成相关模块的自动安装呢?
答案肯定是可以的,你先要通过命令:pip install pipreqs 安装下pipreqs模块,然后终端切换到项目路径下,键入下属命令:
pipreqs 导出文件的存放位置
就可以导出一个requirements.txt的以来文件,文件内容就是你项目用到需要另外安装的Python模块了,接着拿着这个文件到小伙伴的电脑上键入下属命令:
pip install -r requirements.txt
即可自动下载需要用到的模块了。另外如果是你想把自己电脑里Python环境中所有的模块迁移到另一台电脑,可以使用下述命令导出依赖文件,然后执行上述操作完成自动下载。
pip freeze > requirements.txt
关于模块的使用注意事项就上面的这些,接下来介绍下笔者觉得非常有用的模块,
如果你想搜索安装某个模块或者发布一个自己的模块可以到移步到:https://pypi.org/
大部分的程序都会有记录运行日志的需求,而日志信息一般有这样几类:
正常的程序运行日志,调试,错误或警告信息的输出等。
而日常开发中我们需要把日志持久化到本地,进行一些观察和统计,错误排查等,如果只用print函数输出的话,显然有点捉襟见寸。Python内置了一个日志模块:logging,它提供了标准的日志接口,支持日志分级和存储。关于合适使用什么等级的日志表如下:
级别 | 建议什么时候使用 |
---|---|
DEBUG | 详细的信息,常用于问题诊断 |
INFO | 记录关键节点信息,用于确认程序是否按照预期运行 |
WARNING | 程序还是正常运行,但某些不期望的事情发生时记录的信息(如磁盘可用空间较低) |
ERROR | 由于更严重的问题导致某些功能不能正常运行时记录的信息 |
CRITICAL | 发生严重错误,导致程序不能继续运行时记录的信息 |
打印各种级别的代码示例如下:
import logging
if __name__ == '__main__':
# 获得一个Logger
logger = logging.getLogger("Test")
# logging提供的简单的配置方法,自行配置的话需要手动添加handler
logging.basicConfig()
# 设置输出的log级别(大于或等于此级别的才会输出),默认级别Warning
logger.setLevel(logging.INFO)
logger.debug("=== Debug 级别的信息 ===") # 不会输出
logger.info("=== Info 级别的信息 ===")
logger.warning("=== Warning 级别的信息 ===")
logger.error("=== Error 级别的信息 ===")
logger.critical("=== Critical 级别的信息 ===")
代码执行结果如下:
INFO:Test:=== Info 级别的信息 ===
WARNING:Test:=== Warning 级别的信息 ===
ERROR:Test:=== Error 级别的信息 ===
CRITICAL:Test:=== Critical 级别的信息 ===
日志默认是输出到Console(屏幕)上,如果我们想把详细的日志输出到log文件里,则需要用到handler了,理论上可以把日志输出到各种流中,stderr、文件、socket等都可以,在logging中已经将各种流handler封装好了,你也可以自己StreamHandler类自己做一些定制,简单的把日志写入到文件中的代码示例如下:
import logging
if __name__ == '__main__':
logger = logging.getLogger("Test")
logger.setLevel(logging.INFO)
# 输出到控制台
logger.addHandler(logging.StreamHandler())
# 输出到文件
logger.addHandler(logging.FileHandler('test.log', encoding='UTF-8'))
logger.debug("=== Debug 级别的信息 ===")
logger.info("=== Info 级别的信息 ===")
logger.warning("=== Warning 级别的信息 ===")
logger.error("=== Error 级别的信息 ===")
logger.critical("=== Critical 级别的信息 ===")
代码执行结果如下(同时生成了一个test.log的文件):
=== Info 级别的信息 ===
=== Warning 级别的信息 ===
=== Error 级别的信息 ===
=== Critical 级别的信息 ===
嗯,你可能有这样的需求,Console打印Warning以上的日志,而log文件保存Debug以上的日志,那么可以修改下上面的代码,修改后的代码如下:
if __name__ == '__main__':
logger = logging.getLogger("Test")
logger.setLevel(logging.INFO)
# 输出到控制台
s_handler = logging.StreamHandler()
s_handler.setLevel(logging.WARNING)
logger.addHandler(s_handler)
# 输出到文件
f_handler = logging.FileHandler('test.log', encoding='UTF-8')
f_handler.setLevel(logging.DEBUG)
logger.addHandler(f_handler)
logger.debug("=== Debug 级别的信息 ===")
logger.info("=== Info 级别的信息 ===")
logger.warning("=== Warning 级别的信息 ===")
logger.error("=== Error 级别的信息 ===")
logger.critical("=== Critical 级别的信息 ===")
代码执行结果如下:
# 控制台输出:
=== Warning 级别的信息 ===
=== Error 级别的信息 ===
=== Critical 级别的信息 ===
# test.log文件:
=== Info 级别的信息 ===
=== Warning 级别的信息 ===
=== Error 级别的信息 ===
=== Critical 级别的信息 ===
纠结完输出到那里,接着就输出日志的格式了,日志一般都是比较规范的,比如日志打印的时间,类型等,而不会我们这样随意拼接一段字符串,对于日志格式的定制可以通过
logging模块的Formatter组件来定制。basicConfig()中的handler自带一个formatter,通过logging.basicConfig(**kwargs)函数进行定制,该函数可接收的关键字参数如下
参数 | 描述 |
---|---|
filename | 指定日志输出目标文件的文件名,设置后信息就不会打印到控制台 |
filemode | 指定日志文件的打开模式,默认为'a',设置了filename这个才会生效 |
format | 指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序 |
datefmt | 指定日期/时间格式,该选项要在format中包含时间字段%(asctime)s时才有效 |
level | 指定日志器的日志级别 |
stream | 指定日志输出目标stream,不能和filename同时使用否则会引起ValueError异常 |
style | Python 3.2新增,默认'%'指定format格式字符串的风格,可取值为'%'、'{'和'$' |
format格式字符串的字段列表如下:
格式 | 描述 |
---|---|
%(asctime)s | 日志发生的时间--人类可读时间,如:2003-07-08 16:49:45,896 |
%(created)f | 日志发生的时间--时间戳,就是当时调用time.time()函数返回的值 |
%(relativeCreated)d | 日志发生的时间相对于logging模块加载时间的相对毫秒数 |
%(msecs)d | 日志发生时间的毫秒部分 |
%(levelname)s | 该日志记录的文字形式的日志级别('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL') |
%(levelno)s | 该日志记录的数字形式的日志级别(10, 20, 30, 40, 50) |
%(name)s | 所使用的日志器名称,默认是'root',因为默认使用的是 rootLogger |
%(message)s | 日志记录的文本内容,通过 msg % args计算得到的 |
%(pathname)s | 调用日志记录函数的源码文件的全路径 |
%(filename)s | pathname的文件名部分,包含文件后缀 |
%(module)s | filename的名称部分,不包含后缀 |
%(lineno)d | 调用日志记录函数的源代码所在的行号 |
%(funcName)s | 调用日志记录函数的函数名 |
%(process)d | 进程ID |
%(processName)s | 进程名称,Python 3.1新增 |
%(thread)d | 线程ID |
%(thread)s | 线程名称 |
简单的代码示例如下:
import logging
if __name__ == '__main__':
logger = logging.getLogger("Test")
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(process)d:%(processName)s- %(levelname)s === %(message)s",
datefmt="%Y-%m-%d %H:%M:%S %p")
logger.debug("Debug 级别的信息")
logger.info("Info 级别的信息")
logger.warning("Warning 级别的信息")
logger.error("Error 级别的信息")
logger.critical("Critical 级别的信息")
代码执行结果如下:
2018-07-13 18:00:43 PM 10868:MainProcess- INFO === Info 级别的信息
2018-07-13 18:00:43 PM 10868:MainProcess- WARNING === Warning 级别的信息
2018-07-13 18:00:43 PM 10868:MainProcess- ERROR === Error 级别的信息
2018-07-13 18:00:43 PM 10868:MainProcess- CRITICAL === Critical 级别的信息
另外要注意一点basicConfig没有设置编码的属性,如果想把日志写入到文件里,而日志里又有中文的话,只能通过一开始那种设置FileHandler对象的方式!除了通过basicConfig()设置日志格式还,还可以自定义一个Formatter对象,然后调用setFormatter方法进行设置。使用代码示例如下:
import logging
if __name__ == '__main__':
logger = logging.getLogger("Test")
logger.setLevel(logging.INFO)
# 自定义Formatter对象
fmt = logging.Formatter("%(asctime)s %(process)d:%(processName)s- %(levelname)s === %(message)s",
datefmt="%Y-%m-%d %H:%M:%S %p")
# 输出到控制台
s_handler = logging.StreamHandler()
s_handler.setLevel(logging.WARNING)
s_handler.setFormatter(fmt)
logger.addHandler(s_handler)
# 输出到文件
f_handler = logging.FileHandler('test.log', encoding='UTF-8')
f_handler.setLevel(logging.DEBUG)
f_handler.setFormatter(fmt)
logger.addHandler(f_handler)
logger.debug("Debug 级别的信息")
logger.info("Info 级别的信息")
logger.warning("Warning 级别的信息")
logger.error("Error 级别的信息")
logger.critical("Critical 级别的信息")
代码执行结果如下:
# 控制台输出:
2018-07-13 18:30:13 PM 11004:MainProcess- WARNING === Warning 级别的信息
2018-07-13 18:30:13 PM 11004:MainProcess- ERROR === Error 级别的信息
2018-07-13 18:30:13 PM 11004:MainProcess- CRITICAL === Critical 级别的信息
# test.log文件:
2018-07-13 18:30:13 PM 11004:MainProcess- INFO === Info 级别的信息
2018-07-13 18:30:13 PM 11004:MainProcess- WARNING === Warning 级别的信息
2018-07-13 18:30:13 PM 11004:MainProcess- ERROR === Error 级别的信息
2018-07-13 18:30:13 PM 11004:MainProcess- CRITICAL === Critical 级别的信息
logging除了Handler和Formatter两个组件外还有,Filter和LoggerAdapter组件,不过用得不多,有兴趣的同学可以自行到官方文档进行查阅:https://docs.python.org/3/library/logging.html