一个库,供用户以 Python Dict 或 JSON 格式编写(研究中的实验)配置,同时可以从命令行读取参数
项目描述
请加星 请加星
如果你觉得这个工具不错,请点击按钮漂亮的明星点击此项目曝光度!谢谢
如果您觉得这个工具不错,请在本页面右上角轻点Star按钮,增加项目曝光度,谢谢!
中文文档
易于使用的命令行配置工具
一个库,供用户以 Python Dict 或 JSON 格式编写(研究中的实验)配置,同时可以从命令行读取参数以修改值。
Python、命令行、命令行、配置、参数、配置标签、标签配置、配置、传参、参数修改。
Github 网址:https ://github.com/NaiboWang/CommandlineConfig
简单示例
# Install via pip
pip install commandline_config
# import package
from commandline_config import Config
# Define configuration dictionary
config = {
"index":1,
"lr": 0.1,
"dbinfo":{
"username": "NUS"
}
}
# Generate configuration class based on configuration dict
c = Config(config)
# Print the configuration of the parameters
print(c)
# Read and write parameters directly via dot . and support multiple layers.
c.index = 2
c.dbinfo.username = "ZJU"
print(c.index, c.dbinfo.username, c["lr"])
# On the command line, modify the parameter values with --
python example.py --index 3 --dbinfo.username XDU
# Get the parameter descriptions via the help method in the code, or on the command line via -h or -help (customization required, see detailed documentation below for details)
c.help()
python example.py -h
目录
用法
请提交问题
如果您在使用本工具的过程中遇到任何问题,请在本项目的github页面提出问题,我会第一时间解决遇到的bug和问题。
同时,欢迎提交问题,提出您想在此工具中添加哪些功能,我会尽可能实现。
安装
有两种方法可以安装这个库:
-
- 通过 pip 安装:
pip install commandline_config
如果已经安装,可以通过以下命令升级:
pip install commandline_config --upgrade
-
- 直接从github项目的文件夹中导入commandline_config.py文件
/commandline_config到自己的项目目录下,需要安装依赖包prettytable:
pip install prettytable
- 直接从github项目的文件夹中导入commandline_config.py文件
配置方式
-
- 导入库:
from commandline_config import Config
-
- 以 JSON/Python Dict 格式设置参数名称和初始值,并通过
#注释添加参数说明。目前支持将一个 dict 嵌套在另一个 dict 中,并且可以嵌套无限层。
preset_config = { "index": 1, # Index of party "dataset": "mnist", 'lr': 0.01, # learning rate 'normalization': True, "pair": (1,2), "multi_information": [1, 0.5, 'test', "TEST"], # list "dbinfo": { "username": "NUS", "password": 123456, "retry_interval_time": 5.5, "save_password": False, "pair": ("test",3), "multi":{ "test":0.01, }, "certificate_info": ["1", 2, [3.5]], } }
即生成程序的初始配置。dict中定义的每个key
preset_config都是参数名,每个value都是参数的初始值,同时根据设置值的类型自动检测参数的初始值类型。上面的配置包含七个参数:
index, dataset, batch, normalization, pair, multi_information and dbinfo,其中参数索引的类型自动检测为int,默认值为1,描述为“当事人的索引”。同理,第二至第五个参数的类型和默认值为 string:
"mnist"; float:0.01; bool:True; tuple:(1,2); list:[1,0.5,'test', "TEST"]。第七个参数是一个dict类型的嵌套字典,其中也包含7个参数,与前7个参数的类型和默认值相同,这里不再赘述。
- 以 JSON/Python Dict 格式设置参数名称和初始值,并通过
-
preset_config通过将dict传递给Config您想要的任何函数来创建配置类对象。
if __name__ == '__main__': config = Config(preset_config) # Or give the configuration a name: config_with_name = Config(preset_config, name="Federated Learning Experiments") # Or you can store the preset_config in local file configuration.json and pass the filename to the Config class. config_from_file = Config("configuration.json")
这意味着配置对象已成功生成。
-
print参数配置可以通过函数直接打印:
print(config_with_name)
输出结果为:
Configurations of Federated Learning Experiments: +-------------------+-------+--------------------------+ | Key | Type | Value | +-------------------+-------+--------------------------+ | index | int | 1 | | dataset | str | mnist | | lr | float | 0.01 | | normalization | bool | True | | pair | tuple | (1, 2) | | multi_information | list | [1, 0.5, 'test', 'TEST'] | | dbinfo | dict | See sub table below | +-------------------+-------+--------------------------+ Configurations of dict dbinfo: +---------------------+-------+---------------------+ | Key | Type | Value | +---------------------+-------+---------------------+ | username | str | NUS | | password | int | 123456 | | retry_interval_time | float | 5.5 | | save_password | bool | False | | pair | tuple | ('test', 3) | | multi | dict | See sub table below | | certificate_info | list | ['1', 2, [3.5]] | +---------------------+-------+---------------------+ Configurations of dict multi: +------+-------+-------+ | Key | Type | Value | +------+-------+-------+ | test | float | 0.01 | +------+-------+-------+这里所有参数的信息都会以表格的形式打印出来。如果要更改打印样式,可以通过 修改
config_with_name.set_print_style(style='')。可以取值为style:both,table,json表示同时打印table和json,只打印table,只打印json字典。例如:
# 只打印 json config_with_name 。set_print_style ( 'json' ) print ( config_with_name ) print ( "----------" ) # 同时打印表和json config_with_name . set_print_style ( 'table' ) print(config_with_name)
输出结果为:
Configurations of Federated Learning Experiments: {'index': 1, 'dataset': 'mnist', 'lr': 0.01, 'normalization': True, 'pair': (1, 2), 'multi_information': [1, 0.5, 'test', 'TEST'], 'dbinfo': 'See below'} Configurations of dict dbinfo: {'username': 'NUS', 'password': 123456, 'retry_interval_time': 5.5, 'save_password': False, 'pair': ('test', 3), 'multi': 'See below', 'certificate_info': ['1', 2, [3.5]]} Configurations of dict multi: {'test': 0.01} ---------- Configurations of Federated Learning Experiments: +-------------------+-------+--------------------------+ | Key | Type | Value | +-------------------+-------+--------------------------+ | index | int | 1 | | dataset | str | mnist | | lr | float | 0.01 | | normalization | bool | True | | pair | tuple | (1, 2) | | multi_information | list | [1, 0.5, 'test', 'TEST'] | | dbinfo | dict | See sub table below | +-------------------+-------+--------------------------+ {'index': 1, 'dataset': 'mnist', 'lr': 0.01, 'normalization': True, 'pair': (1, 2), 'multi_information': [1, 0.5, 'test', 'TEST'], 'dbinfo': 'See below'} Configurations of dict dbinfo: +---------------------+-------+---------------------+ | Key | Type | Value | +---------------------+-------+---------------------+ | username | str | NUS | | password | int | 123456 | | retry_interval_time | float | 5.5 | | save_password | bool | False | | pair | tuple | ('test', 3) | | multi | dict | See sub table below | | certificate_info | list | ['1', 2, [3.5]] | +---------------------+-------+---------------------+ {'username': 'NUS', 'password': 123456, 'retry_interval_time': 5.5, 'save_password': False, 'pair': ('test', 3), 'multi': 'See below', 'certificate_info': ['1', 2, [3.5]]} Configurations of dict multi: +------+-------+-------+ | Key | Type | Value | +------+-------+-------+ | test | float | 0.01 | +------+-------+-------+ {'test': 0.01}
配置参数读写方式
写法
配置参数值可以用三种方式写入。
-
- 要接收命令行参数,只需传递
--index 1命令行来修改 to 的index值1。此外,将值传递给不同类型的参数的注意事项是:
- 传递 bool 类型时,可以使用
0orFalse为False,1或Trueorno value after the parameter为True:--normalization 1或all--normalization True可以将配置中--normalization的参数值设置为True。normalization - 传递列表类型时,可以传递空数组和多维数组。
- 修改嵌套dict中的值,请使用
--nested-parameter-name.sub-parameter-name.sub-parameter-name.….sub-parameter-name value修改嵌套对象中的值,例如将子对象中的参数--dbinfo.password 987654值改为; 将子对象中的dict中的参数值更改为```。目前这个工具可以支持无限层/级别的嵌套。passworddbinfo987654--dbinfo.multi.test 1testmultidbinfo - 请注意,参数索引必须在
preset_config上面定义的对象中:
python test.py --dbinfo.password 987654 --dbinfo.multi.test 1 --index 0 --dataset emnist --normalization 0 --multi_information [\'sdf\',1,\"3.3\",,True,[1,[]]]
- 要接收命令行参数,只需传递
-
- 直接在代码中使用
config.index = 2,将参数的值index改为2. 同样,列表类型参数可以分配为空数组或多维数组。对于嵌套对象,您可以使用将sub dictconfig.dbinfo.save_password=True中的参数值修改为.save_passworddbinfoTrue
- 直接在代码中使用
-
- 方式1和2会触发类型检查,即如果预定义dict中赋值的类型和默认值的类型
preset_config不匹配,程序会报错,因此,如果不想强制类型检查,可以使用config["index"] = "sdf"强制参数索引的值到字符串sdf(不推荐,会造成意想不到的影响)。
- 方式1和2会触发类型检查,即如果预定义dict中赋值的类型和默认值的类型
阅读方法
dataset通过config.dataset或直接读取参数值config["dataset"]。
print(config.dataset, config["index"])
参数的值a将按以下顺序读取:最后修改config.a = *的值>--a 2命令行指定的值> "a":1preset_config定义的初始值。
对于list类型,如果传递的是多维数组,则可以通过python的标准切片读取信息:
config.dbinfo.certificate_info = [1,[],[[2]]]
print(config.dbinfo.certificate_info[2][0][0])
对于单个嵌套对象中的参数,读取参数值的方式有四种,都可以读取成功:
print(config.dbinfo.username)
print(config["dbinfo"].password)
print(config.dbinfo["retry_interval_time"])
print(config["dbinfo"]["save_password"])
将配置传递给函数
只需将上述配置对象作为参数传递给函数并调用它:
def print_dataset_name(c):
print(c.dataset, c["dataset"], c.dbinfo.certificate_info)
print_dataset_name(c=config)
复制配置
可以通过以下deepcopy方法制作配置对象的深层副本:
from copy import deepcopy
copy_config = deepcopy(config)
# Modify new configuration's parameter value, will not affect the orignal configuration
copy_config.index=15
将配置参数存储到本地文件或数据库
整个参数配置可以保存到本地文件,也可以上传到远程服务器,如mongodb,只需config.save()将配置保存为config name (or config if there is no name).json目录中的文件即可,也可以指定文件名和路径,如下:
config.save("config/test_config.json")
然后我们成功将配置保存到文件夹configuration.json内的本地文件中config。文件内容如下:
{
"index": 1,
"dataset": "mnist",
"lr": 0.01,
"normalization": true,
"pair": [1, 2],
"multi_information": [1, 0.5, "test", "TEST"],
"dbinfo": {
"username": "NUS",
"password": 123456,
"retry_interval_time": 5.5,
"save_password": false,
"pair": ["test", 3],
"multi": { "test": 0.01 },
"certificate_info": ["1", 2, [3.5]]
}
}
存入数据库如mongodb,需要先用info = config.get_config()命令获取参数对应的json序列,再用json库序列化。
例如,要将config_with_name配置存储到mongodb:
import pymongo
myclient = pymongo.MongoClient('mongodb://username:example.com:27017/', connect=False)
mydb = myclient['exps']
table = mydb["table"]
# Get the configurations
configuration = config.get_config()
# Insert configuration dict into mongodb table
table.insert_one(configuration)
# Or make configuration as part of a bigger dict
all_info = {
"exp_time":"20220925",
"configuration":configuration
}
table.insert_one(all_info)
请注意,JSON 不支持元组,因此无论是存储在本地还是存储在数据库中,元组参数都将转换为列表。
高级选项
将参数输入值限制为固定枚举类型
options通过将参数的Config参数传递给类来设置高级选项,例如枚举 Enum 类型Config。
option={}
config = Config(preset_config, options=option)
如果要将参数的值限制在一定范围内,可以通过配置来实现:
advanced_options = {
'lr': {
"enum": [0.001, 15.5, 0.01, 0.1] # restrict the lr value to one of 0.001, 15.5, 0.01, 0.1
},
'index': {
"enum": [1, 2, 3] # Restrict the index value to 1, 2 and 3
},
"dbinfo": {
"username": {
"enum": ["XDU", "ZJU", "NUS"] # restrict the dbinfo.username field to XDU, ZJU and NUS
},
"multi":{
"test":{
"enum": [1,0.1, 0.01, 15] # 3 layers nested
}
}
},
}
config = Config(preset_config, options=advanced_options)
如果设置了enum,下面三种将参数设置为限定/指定值以外的值的方式都会报错。
-
- 的初始值设置为除in
index以外的值:1,2,3preset_config
preset_config = { "index":4, }
- 的初始值设置为除in
-
lr命令行为参数传递不合格/未指定的值
python example.py --lr 0.02 -
- 代码将 的值更改为
dbinfo.username以外的值XDU, ZJU and NUS。
config.dbinfo.username = "UEST"
输出是:
AttributeError: Can not set value 4 because the key 'index' has set enum list and you the value 4 is not in the enum list [1, 2, 3]! AttributeError: Can not set value 0.02 because the key 'lr' has set enum list and you the value 0.02 is not in the enum list [0.001, 15.5, 0.01, 0.1]! AttributeError: Can not set value nus because the key 'username' has set enum list and you the value nus is not in the enum list ['XDU', 'ZJU', 'NUS']!
- 代码将 的值更改为
打印参数帮助说明
设置参数说明
helpers通过在Config类中指定参数来设置参数描述助手。
helpers = {
"index": "index of information",
"dbinfo_help": "information dict for database",
"dbinfo": {
"username": "username for database",
"multi":{
"test":"test information"
}
}
}
config = Config(preset_config, helpers=helpers)
注意,由于dbinfo参数是a dict,如果要为 设置参数描述dbinfo,需要设置一个dbinfo_help参数来写helpers字典的描述,即_help在dict参数名后面加,设置dict字段的参数描述。
打印参数帮助
打印参数描述的两种方法,通过在命令行上传递-h或调用,或者通过在代码中调用函数。-helphelp()
config_with_name.help()
或者
python example.py -h
# OR
python example.py -help
请注意,它只是一个短斜杠-,没有添加其他命令行参数来获取帮助说明,两种方法的输出都是:
Parameter helps for Federated Learning Experiments:
+-------------------+-------+-------------------------------+
| Key | Type | Comments |
+-------------------+-------+-------------------------------+
| index | int | index of information |
| dataset | str | - |
| lr | float | - |
| normalization | bool | - |
| pair | tuple | - |
| multi_information | list | - |
| dbinfo | dict | information dict for database |
+-------------------+-------+-------------------------------+
Parameter helps for dict dbinfo:
+---------------------+-------+-----------------------+
| Key | Type | Comments |
+---------------------+-------+-----------------------+
| username | str | username for database |
| password | int | - |
| retry_interval_time | float | - |
| save_password | bool | - |
| pair | tuple | - |
| multi | dict | Multiple Parameters |
| certificate_info | list | - |
+---------------------+-------+-----------------------+
Parameter helps for dict multi:
+------+-------+------------------+
| Key | Type | Comments |
+------+-------+------------------+
| test | float | test information |
+------+-------+------------------+
需要注意的事情
与 Argparse 冲突
该库无法与 argparse 库同时读取命令行参数,因此请不要args = parser.parse_args()在使用该库时读取命令行参数。
输入值强制转换
参数的类型会被自动检测为与 中设置的初始值相同的类型preset_config,并强制将命令行参数的值转换为对应的类型值,如index上面preset_configdict中的默认值为1,那么参数索引的类型是int初始值为1。如果--index 15.5在命令行指定,参数index会自动赋值给 value 15,也就是15.5会自动强制转换为inttype 。
如果命令行参数上指定的参数值不能强制转换为特定类型,会报错,比如如果命令行指定--index sdf,as sdf with orignal format ofstring不能转换为int类型,会报错一个错误。
命令行传递时,list参数需要在字符串元素引号前加上反斜杠
当命令行参数设置为输入list类型时,如果列表中的元素是 a 则string,必须backslash \在 each 之前添加 a 才能single/double quote正确解析,否则参数值将被视为 an intorfloat类型。如果spaces命令行中有的话会自动合并(但是命令行环境不行zsh,如果是zsh环境则必须去掉list里面的所有空格,bash不sh存在这个问题,就是zsh里面环境中,您不能在 ) 之间添加任何15空格。\'12\'--a [15,\'12\']
如果参数可以设置如下:
python test.py --array [1,2.3,\'sdf\']
即能正确解析出值为a的数组参数list和a的内容[1,2.3,'sdf', "qwe"],即同时包含int、float、string类型数据的list。
元组参数的命令行赋值需要引号,字符串元素前面必须有反斜杠
当命令行参数设置为输入tuple类型时,指定的元组类型值必须用 ; 括起来quotes。如果元组中的元素是 a string,则backslash必须在每个单/双引号之前添加a\以便正确解析,否则参数值将被视为 an intorfloat类型。同样的,如果spaces命令行中有的话会自动合并(但是命令行环境不能zsh,如果是zsh环境则必须去掉所有内部空格,bash和sh没有这个问题)。
例如,可以将参数设置为
python test.py --pair "(1,2,\'msg\')"
pair 参数的值是一个类型(1,2, "msg")的元组,即一个类型为int, float,的元组string。
参数命名约定
如果参数名称中包含-+.或space或等特殊字符other python reserved characters,则必须使用middle bracket []来读写参数值,而不是。例如,如果参数名称是multi-information,则只能通过 访问config["multi-information"],不能做config.multi-information,因为减号sign -是python语言的保留符号。
无限层嵌套对象
现在该工具可以支持无限层嵌套,其他支持的参数类型有:int, float, string, bool, tuple and list.
参数完整性检查,所有要修改的参数必须预先定义
命令行传递的参数名称必须preset_config事先定义好,否则会报错,例如
python test.py --arg1 1
由于dictarg1中没有定义参数名preset_config,所以报错,表示arg1参数没有定义。该功能设置为执行参数完整性检查,以避免通过命令行输入错误的参数名称。
zsh环境下的特殊配置
如果zsh: no matches found在zsh Shell环境中传递list参数时出现,请setopt no_nomatch在文件末尾添加一行~/.zshrc,保存后source ~/.zshrc在命令行运行刷新zsh,问题就解决了。
完整转换示例
下面将举例说明该工具与该工具相比的便利性argparse。
需要使用该argparse工具编写的代码:
parser = argparse.ArgumentParser(description='PyTorch local error training')
parser.add_argument('--model', default='vgg8b',
help='model, mlp, vgg13, vgg16, vgg19, vgg8b, vgg11b, resnet18, resnet34, wresnet28-10 and more (default: vgg8b)')
parser.add_argument('--dataset', default='CIFAR10',
help='dataset, MNIST, KuzushijiMNIST, FashionMNIST, CIFAR10, CIFAR100, SVHN, STL10 or ImageNet (default: CIFAR10)')
parser.add_argument('--batch-size', type=int, default=128,
help='input batch size for training (default: 128)')
parser.add_argument('--num-layers', type=int, default=1,
help='number of hidden fully-connected layers for mlp and vgg models (default: 1')
parser.add_argument('--lr', type=float, default=5e-4,
help='initial learning rate (default: 5e-4)')
parser.add_argument('--lr-decay-milestones', nargs='+', type=int, default=[200,300,350,375],
help='decay learning rate at these milestone epochs (default: [200,300,350,375])')
parser.add_argument('--optim', default='adam',
help='optimizer, adam, amsgrad or sgd (default: adam)')
parser.add_argument('--beta', type=float, default=0.99,
help='fraction of similarity matching loss in predsim loss (default: 0.99)')
args = parser.parse_args()
args.cuda = not args.no_cuda and torch.cuda.is_available()
if args.cuda:
cudnn.enabled = True
cudnn.benchmark = True
使用此工具转换后要编写的代码:
'''
:param model: model, mlp, vgg13, vgg16, vgg19, vgg8b, vgg11b, resnet18, resnet34, wresnet28-10 and more (default: vgg8b)
:param dataset: dataset, MNIST, KuzushijiMNIST, FashionMNIST, CIFAR10, CIFAR100, SVHN, STL10 or ImageNet (default: CIFAR10)
:param batch-size: input batch size for training (default: 128)
:param num-layers: number of hidden fully-connected layers for mlp and vgg models (default: 1)
:param lr: initial learning rate (default: 5e-4)
:param lr-decay-milestones: decay learning rate at these milestone epochs (default: [200,300,350,375])
:param optim: optimizer, adam, amsgrad or sgd (default: adam)
:param beta: fraction of similarity matching loss in predsim loss (default: 0.99)
'''
config = {
'model':'vgg8b',
'dataset':'CIFAR10',
'batch-size':128,
'num-layers':1,
'lr':5e-4,
'lr-decay-milestones':[200,300,350,375],
'optim':'adam',
'beta':0.99,
}
args = Config(config, name='PyTorch local error training')
args.cuda = not args.no_cuda and torch.cuda.is_available()
if args.cuda:
cudnn.enabled = True
cudnn.benchmark = True
正如我们所看到的,代码量减少了,并且更加结构化和整洁。
图书馆点击的另一个案例:
import click
@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
help='The person to greet.')
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo(f"Hello {name}!")
if __name__ == '__main__':
hello()
可以转换为以下代码:
from commandline_config import Config
def hello ( o ):
"""简单的程序,向 NAME 打招呼总共 COUNT 次。"""
for x in range ( o . count ):
print ( f "Hello { o . name } !" )
if __name__ == '__main__' :
args = {
"count" : 1 , # 问候次数。
"name" : "" , # 要打招呼的人。
}
选项 = 配置(参数)
你好(选项)
示例运行脚本
您可以example.py在 Github 项目中运行来测试整个工具,文件中已经提供了大多数函数的代码:
# Get help for all parameters of example.py
python example.py -h
# Specify parameter values
python example.py --dbinfo.multi.test 0.01 --dbinfo.username NUS
破碎的想法
下面描述了作者个人开发的原因和这个包的好处/方便。
对于我们经常进行研究实验的我们来说,是否经常需要在一个python文件的开头设置很多命令行