1. What:gflags-py的前世今生
1.1 什么是command line flags
command line flags,也就是命令行参数方式,是linux系统上,最常见的命令行参数方式。
例如,在wc -l中,-l就是一个命令行标志。
相比较其他的配置方式,简单清晰。
相比较xml,json的配置文件的复杂性,大多数小型程序,以及习惯linux开发环境的用户,使用命令行参数的非常多。
1.2 gflags做了什么
gflags是Google开源的一套命令行参数处理的开源库,包括C++的版本和Python版本。
和getopt()之类的库不同,flag的定义可以散布在各个源码中,而不用放在一起。一个源码文件可以定义一些它自己的flag,链接了该文件的应用都能使用这些flag。
这样就能非常方便地复用代码。如果不同的文件定义了相同的flag,链接时会报错。
gflags使用起来比getopt方便,但是不支持参数的简写(例如getopt支持--list缩写成-l,gflags不支持)。
c++版本gflags的地址是:gflags工程地址,gflags文档地址
1.3 gflags-python的演变
python版本的gflags曾经是一个独立的代码项目,gflags-python,后来,被整合进入abseil-py仓库中,成为几个公共组件的一部分。
新的gflags-py文档地址。
2. Why:为什么是gflags
在python使用命令行参数,通常有几个方式:argparse,sys。下面就分别举例子看下。
2.1 argparse的使用
argparse模块是Python自带的处理命令行参数的模块,它是Python标准库的一部分。
argparse使用主要有四个步骤:
- 导入argparse包
- 创建 ArgumentParser() 参数对象
- 调用 add_argument() 方法往参数对象中添加参数
- 使用 parse_args() 解析添加参数的参数对象,获得解析对象
- 程序其他部分,当需要使用命令行参数时,使用解析对象.参数获取
import argparse
def main():
# 定义一个ArgumentParser实例:
parser = argparse.ArgumentParser(
prog='test', # 程序名
description='Test.', # 描述
epilog='Copyright(r), 2023' # 说明信息
)
# 定义参数:
parser.add_argument('--host', default='localhost')
# 定义参数必须为int类型:
parser.add_argument('--port', default='3306', type=int)
# 解析参数:
args = parser.parse_args()
# 打印参数:
print(f'host = {args.host}')
print(f'port = {args.port}')
if __name__ == '__main__':
main()
2.2 sys的使用
sys的方式比较直接,就是按照linux程序的方式直接读取参数,全部当成字符串处理,没有变量的检查和定义,全部需要程序自己处理,没有约束和规则。
import sys
print(sys.argv)
source = sys.argv[1]
target = sys.argv[2]
# TODO:
2.3 gflags的使用简介
gflags,这里指的是gflags-py,使用起来就非常简单,这里只是举一个简单的例子,详细的使用在后面介绍:
from absl import app
from absl import flags
FLAGS = flags.FLAGS
flags.DEFINE_string('host', 'localhost', 'input host')
flags.DEFINE_integer('port', 3306, 'input port')
def main(argv):
print(f'host = {FLAGS.host}')
print(f'port = {FLAGS.port}')
if __name__ == '__main__':
app.run(main)
可以看到,gflags做了很好的封装,定义和类型限制很直接,使用也比较方便,而且支持list,enum等多种类型的定义,支持配置写在单独的文件中直接加载,或者多个文件中互相引用加载。
这是高等级的封装,也是我们选用这个库的主要原因。
3. How:基本的使用
3.1 基本的使用
可以先看官方文档的一个例子:
from absl import app
from absl import flags
FLAGS = flags.FLAGS
# Flag names are globally defined! So in general, we need to be
# careful to pick names that are unlikely to be used by other libraries.
# If there is a conflict, we'll get an error at import time.
flags.DEFINE_string('name', 'Jane Random', 'Your name.')
flags.DEFINE_integer('age', None, 'Your age in years.', lower_bound=0)
flags.DEFINE_boolean('debug', False, 'Produces debugging output.')
flags.DEFINE_enum('job', 'running', ['running', 'stopped'], 'Job status.')
def main(argv):
if FLAGS.debug:
print('non-flag arguments:', argv)
print('Happy Birthday', FLAGS.name)
if FLAGS.age is not None:
print('You are %d years old, and your job is %s' % (FLAGS.age, FLAGS.job))
if __name__ == '__main__':
app.run(main)
上面代码中,flags.DEFINE_**是定义不同类型的参数配置。
每个参数至少需要三个配置:第一个参数是配置名称,第二个参数是配置的默认值,第三个参数是注释。
还可以有更多的参数,分别对应更高级的功能,下面会详细介绍。
FLAGS.xx是代码中访问定义配置的方式。
需要注意的是,需要首先显示的执行app.run(main),这样才能把参数详细的解析。这行是必不可少的。
3.2 多种类型支持
gflags支持多种参数类型:
- DEFINE_string:字符串类型
- DEFINE_bool:布尔类型
- 对于这种类型,推荐在参数配置时,使用--myflag来配置True,使用--nomyflag来配置为False。
- --myflag=true and --myflag=false 这种方式配置也是可以的,但是不推荐。
- DEFINE_float:浮点类型
- 这中类型在定义是支持额外的参数lower_bound和upper_bound。如果超过这个范围,会抛出FlagError异常。
- lower_bound:最小的限制
- upper_bound:最大的限制
- DEFINE_integer:整数类型
- 支持额外的lower_bound和upper_bound参数。
- DEFINE_enum:枚举类型
- 约束传入的字符串是一个限制的内容列表内,如果不在限制的列表,会抛出异常,如果是限制内的字符串,配置就会设置为这个字符串。
- flags.DEFINE_enum('job', 'running', ['running', 'stopped'], 'Job status.') 这里面,第一个参数是配置名称,第二个参数是配置的默认值,第三个参数是配置的限制值列表。
- DEFINE_list:列表类型(逗号)
- 输入为使用逗号分隔的字符串,会转化为python的list变量。
- 举例:--myspacesepflag "foo,bar,baz"
- DEFINE_spaceseplist:列表类型(空格)
- 输入为使用空格分隔的字符串,会转化为python的list变量。
- 举例:--myspacesepflag "foo bar baz"
- DEFINE_multi_string:多行字符串类型
- 支持配置多行字符串的参数,返回一个list变量。
- 返回的是一个list,哪怕只配置了一行。
- DEFINE_multi_integer
- 支持配置多行整数的参数,返回一个list变量。
- 返回的是一个list,哪怕只配置了一行。
- DEFINE_multi_enum
- 支持配置多行枚举字符串类型的参数,返回一个list变量。
- 返回的是一个list,哪怕只配置了一行。
DEFINEmulti** 定义和使用举例:
# 配置定义
from absl import flags
flags.DEFINE_multi_string(
'gin_file', None, 'List of paths to the config files.')
flags.DEFINE_multi_string(
'gin_param', None, 'Newline separated list of Gin parameter bindings.')
# 配置使用方式
.../xx_bin \
--gin_file=$CONFIGS_PATH/cartpole_balance.gin \
--gin_file=$CONFIGS_PATH/base_dqn.gin \
--gin_file=$CONFIGS_PATH/eval.gin \
--gin_param='evaluate.num_episodes_eval = 10' \
--gin_param='evaluate.generate_videos = False' \
--gin_param='evaluate.eval_interval_secs = 60'
3.3 配置文件支持
可以使用一个文件,放入所有配置的具体配置,然后通过一个特殊的配置--flagfile=filename来加载这个配置文件。
在配置文件中,#和//都表示注释。
配置文件中,可以递归使用--flagfile=A来引用。
所有的--flagfile都是依据当前工作目录,和引入的配置文件目录无关。
3.4 一些工程的配置定义例子
(作者信息)
数字老K
quantgalaxy@outlook.com
欢迎交流