Argparse和Logging
Argparse和Logging是Python实验中常用的两个模块. 之前没有整理过, 特此整理.
Argparse
Argparse是用来解析Python命令行的标准库.
框架
大致使用框架如下:
import argparse
parser = argparse.ArgumentParser(description="This is the description for this python script")
'''
Add some arugments...
'''
args = parser.parse_args()
在命令行中调用脚本时可以附加参数:
python argparse_test.py -n anning
总体来说, 流程如下:
- 需要导入
Argparse
. - 创建一个
parser
, 并对其添加各种参数. - 使用
parse_args()
解析参数. - 使用Python命令行时可以附加相应的参数.
添加参数
添加参数统一使用函数parser.add_argument()
, 在该函数中可以设置参数的各种属性和约束. 在下文会逐一说明.
获取参数
设定的argument可以作为属性, 在解析参数后, 使用args.argument_name
来获取.
位置参数
位置参数当然是必选的参数, 在启动Python脚本时必须按照位置依次输入. 例如:
parser.add_argument('name', type=str, help='Your name')
添加了参数name
, 并将其设置为str
类型的变量.
可选参数
顾名思义, 在执行脚本时可选参数可能会被用户附加, 也有可能不附加. 参数的添加方式与位置参数类似, 必须在参数名前加-
. 约定上-
后应只跟单个字母, 为某个参数的缩写, --
后跟参数的全拼. 例如:
parser.add_argument('-n', type=str, help='Your name')
parser.add_argument('--age', type=int, help='Your age')
添加了两个可选参数n
和age
, 分别为str
和int
类型.
当然, 想要同时将缩写和全拼指向同一个变量也是可以的:
parser.add_argument('-a', '--age', type=int, help='Your age')
此时, 你既可以通过-a
, 也可以通过--age
来定义年龄.
如果你同时指定了参数的缩写和全拼, 那么在访问该参数值时, 必须通过全拼来访问.
默认值和类型
default
设定字段的默认值, type
可以指定附加参数后将其转化为什么数据类型. 例如:
parser.add_argument('--name', type=str, default='anning', help='Your name')
添加了一个参数name
, 当命令行中不显式声明时缺省值为'anning'
, 类型为字符串.
别名
dest
可以指定该参数所对应的args
的别名, 在Python中获取该参数可以通过指定的别名来获取. 例如:
parser.add_argument('--name', dest='user_name', type=str, help='Your name')
在没指定别名时, 访问参数name
可以通过args.name
获取, 而指定别名后, 只能通过args.user_name
来获取.
必须参数
required
设定参数是否必须填入. 否则会提示该参数没有指定, 因此可以将可选参数转化为必选参数.
动作
action
一般常用于可选参数, 例如有参数--verbose
时, 该属性设置为True:
parser.add_argument('--verbose', action='store_true')
此时, 如果在调用脚本时加上了--verbose
, 那么在解析后, 对应的args.verbose
就为True
.
当然这只是action
其中一种用法, 更多请参见节尾处的链接.
值约束
choices
可以设定用户添加参数时, 参数的取值范围. 例如:
parser.add_argument('--name',choices=['anning', 'daning'] type=str, help='Your name')
那么用户只能在'anning'
和'daning'
之间选择name
.
多参数设定
nargs='N'
可以将命令行的N个参数汇总到一个列表中, nargs='+'
或nargs='*'
能将所有当前参数汇聚到一个列表中. 例如:
parser.add_argument('--salary', dest='salary', default=[1, 2, 5], nargs="+", type=int)
在调用脚本时, 能够传入多个符合条件的值, 它们将以列表的形式共同存在.
互斥参数组
有的时候不希望用户按照自己想象之外的使用方法传入参数, 就需要用到互斥参数. 例如:
ab_group = parser.add_mutually_exclusive_group()
ab_group.add_argument('-a')
ab_group.add_argument('-b')
那么, 在调用Python脚本时, 两个参数是不能同时启用的, 否则会报错.
Tips
var(args)
能将解析好后的内容直接转为字典.
在使用Notebook时, 与
argparse
相关的代码以.py
形式存在, 不方便直接从里面把参数扒下来, 而模型必须使用args
初始化.这时可以使用
pickle
库将args
直接以对象的形式保存下来, 然后再使用pickle.load
在Notebook中重新读取出来, 再对模型初始化. 示例:import pickle # 保存 with open('args.pkl', 'wb') as f: pickle.dump(args, f) # 读取 with open('args.pkl', 'rb') as f: args = pickle.load(f)
上述列举的只是我遇到的关于Argparse的用法, 可能说的比较碎而且不全面. 更多内容请参见Python官方文档和argparse - 命令行选项与参数解析(译).
Logging
Logging可以记载Python脚本运行的过程, 即记录日志信息. 其实实验中用到的Logging非常简单.
Logger
简单配置
如果我们只是想简单的记录日志, 不考虑程序的后续维护问题, 那么我们只需要简单的进行logger
的初始化. 例如:
logging.basicConfig(level=logging.DEBUG,
filename='output.log',
datefmt='%Y/%m/%d %H:%M:%S',
format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s')
logger = logging.getLogger("process_name")
basicConfig参数
- filename: 即日志输出的文件名, 如果指定了这个信息之后, 实际上会启用 FileHandler, 而不再是 StreamHandler, 这样日志信息便会输出到文件中了.
- filemode: 这个是指定日志文件的写入方式, 有两种形式, 一种是
w
, 一种是a
, 分别代表清除后写入和追加写入. - format: 指定日志信息的输出格式, 详细格式会在后面补出.
- datefmt: 指定时间的输出格式.
- style: 如果 format 参数指定了, 这个参数就可以指定格式化时的占位符风格, 如 %、{、$ 等.
- level: 指定日志输出的类别, 程序会输出大于等于此级别的信息.
- stream: 在没有指定 filename 的时候会默认使用 StreamHandler, 这时 stream 可以指定初始化的文件流.
- handlers: 可以指定日志处理时所使用的 Handlers, 必须是可迭代的.
配置文件配置
一般情况下, 为了把配置写活, 人们都会将配置写入配置文件, 在记录日志的时候, 读取配置文件中的配置, 方便管理.
无论以何种方式读取文件, 例如yaml
, json
, 只要读取成Python的字典, 就可以完成初始化, 例如:
config_dict = json.load(config_path)
logging.config.dictConfig(config_dict)
logger = logging.getLogger("process_name")
在这里, 以json
作为示范, 在配置文件中写入的内容有:
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"simple": {
"format": "%(asctime)s - %(name)s - [%(levelname)s] - %(message)s"
}
},
"handlers": {
"file_handler": {
"class": "logging.FileHandler",
"level": "DEBUG",
"formatter": "simple",
"filename": "python_logging.log",
"encoding": "utf8"
}
},
"root": {
"level": "DEBUG",
"handlers": ["file_handler"]
}
}
Level
日志记录分为五个级别, 分别为CRITICAL > ERROR > WARNING > INFO > DEBUG.
它们分别可以通过下述命令来记录在日志中:
logger.debug("mess")
logger.info("mess")
logger.warning("mess")
logger.error("mess""mess")
logger.critical("mess")
logger
中的level
参数可以指定程序输出的信息级别.
Handler
Handler
是处理日志的方法. 我们可以不使用basicConfig
来配置logger
, 而是单独对logger
指定处理方法:
import logging
logger = logging.getLogger("process_name")
logger.setLevel(level=logging.INFO)
handler = logging.FileHandler('output.log')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
我们常用的是FileHandler
, 这样能将日志以文件形式导出. 除此以外, 还有如下Handler
:
- StreamHandler: logging.StreamHandler, 日志输出到流, 可以是 sys.stderr, sys.stdout 或者文件.
- FileHandler: logging.FileHandler, 日志输出到文件.
- BaseRotatingHandler: logging.handlers.BaseRotatingHandler, 基本的日志回滚方式.
- RotatingHandler: logging.handlers.RotatingHandler, 日志回滚方式, 支持日志文件最大数量和日志文件回滚.
- TimeRotatingHandler: logging.handlers.TimeRotatingHandler, 日志回滚方式, 在一定时间区域内回滚日志文件.
- SocketHandler: logging.handlers.SocketHandler, 远程输出日志到 TCP/IP sockets.
- DatagramHandler: logging.handlers.DatagramHandler, 远程输出日志到 UDP sockets.
- SMTPHandler: logging.handlers.SMTPHandler, 远程输出日志到邮件地址.
- SysLogHandler: logging.handlers.SysLogHandler, 日志输出到 syslog.
- NTEventLogHandler: logging.handlers.NTEventLogHandler, 远程输出日志到 Windows NT/2000/XP 的事件日志.
- MemoryHandler: logging.handlers.MemoryHandler, 日志输出到内存中的指定 buffer.
- HTTPHandler: logging.handlers.HTTPHandler, 通过”GET” 或者”POST” 远程输出到 HTTP 服务器.
实际上, 我们可以给logger
添加多个Handler
:
import logging
from logging.handlers import HTTPHandler
import sys
logger = logging.getLogger("process_name")
logger.setLevel(level=logging.DEBUG)
# StreamHandler
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(level=logging.DEBUG)
logger.addHandler(stream_handler)
# FileHandler
file_handler = logging.FileHandler('output.log')
file_handler.setLevel(level=logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# HTTPHandler
http_handler = HTTPHandler(host='localhost:8001', url='log', method='POST')
logger.addHandler(http_handler)
# Log
logger.info('This is a log info')
logger.debug('Debugging')
logger.warning('Warning exists')
logger.info('Finish')
Formatter
在对日志格式化输出时, 可以不借助basicConfig
来全局化输出格式, 使用Formatter
灵活单独配置:
logger = logging.getLogger("process_name")
logger.setLevel(level=logging.WARN)
formatter = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y/%m/%d %H:%M:%S')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
更多的信息输出格式如下:
- %(levelno) s : 打印日志级别的数值.
- %(levelname) s : 打印日志级别的名称.
- %(pathname) s : 打印当前执行程序的路径, 其实就是 sys.argv [0].
- %(filename) s : 打印当前执行程序名.
- %(funcName) s : 打印日志的当前函数.
- %(lineno) d : 打印日志的当前行号.
- %(asctime) s : 打印日志的时间.
- %(thread) d : 打印线程 ID.
- %(threadName) s : 打印线程名称.
- %(process) d : 打印进程 ID.
- %(processName) s : 打印线程名称.
- %(module) s : 打印模块名称.
- %(message) s : 打印日志信息.
上述内容也并非全部的使用方法, 参考了Python官方文档和Python 中 logging 模块的基本用法.