无需依赖即可轻松解析命令行中的任意参数
项目描述
minydra 🦎
受Facebook的Hydra启发,具有点访问权限嵌套字典的极简Python命令行解析器。
无需依赖即可轻松解析命令行中的任意参数
pip install minydra
minydra
已在Python 3.7
、3.8
和3.9
上进行了测试。
入门 • 强制类型 • MinyDict • 保存配置 • 防止输入错误 • 使用默认配置 • 示例
入门
from minydra.parser import Parser
if __name__ == "__main__":
parser = Parser(
verbose=0, # print received args
allow_overwrites=False, # allow repeating args in the command-line
warn_overwrites=True, # warn repeating args if they are allowed
parse_env=True, # get environment variable
warn_env=True, # warn if an environment variable is specified but not found
defaults=None, # path to a MinyDict-loadable dictionary of default values for the args
strict=True, # if `defaults` is provided, whether to allow new keys in the command-line
# or restrict to `defaults`' keys
keep_special_kwargs=True, # `defaults` and `strict` can be set from the command-line
# with `@defaults=` and `@strict=`. This argument decides if
# you want to keep those keys in the final arguments.
)
args = parser.args.pretty_print().resolve().pretty_print() # notice .resolve() transforms dotted.keys into nested dicts
from minydra import resolved_args
if __name__ == "__main__":
args = resolved_args()
args.pretty_print()
examples/demo.py
examples/demo.json
from minydra import MinyDict, resolved_args
from pathlib import Path
if __name__ == "__main__":
# parse arbitrary args in 1 line
args = resolved_args()
# override default conf
if args.default:
args = MinyDict.from_json(args.default).update(args)
# protect args in the rest of the code execution
args.freeze()
# print the args in a nice orderly fashion
args.pretty_print()
# access args with dot/attribute access
print(f'Using project "{args.log.project}" in {args.log.outdir}')
# save configuration
args.to_json(Path(args.log.outdir) / f"{args.log.project}.json")
import minydra
from minydra.dict import MinyDict
@minydra.parse_args(verbose=0, allow_overwrites=False) # Parser's init args work here
def main(args: MinyDict) -> None:
args.resolve().pretty_print()
if __name__ == "__main__":
main()
解析
- 简单字符串将自动解析为
float
和int
。 - 单个关键字将被解释为正标志。
- 以“-”开头的单个关键字将被解释为负标志。
- 如果
parse_env
为True
,将评估环境变量。
$ python examples/decorator.py outdir=$HOME/project save -log learning_rate=1e-4 batch_size=64
╭───────────────────────────────────────────╮
│ batch_size : 64 │
│ learning_rate : 0.0001 │
│ log : False │
│ outdir : /Users/victor/project │
│ save : True │
╰───────────────────────────────────────────╯
- 点分隔符键将被解析为嵌套字典键
$ python examples/decorator.py server.conf.port=8000
╭────────────────────╮
│ server │
│ │conf │
│ │ │port : 8000 │
╰────────────────────╯
- 使用
ast.literal_eval(value)
,minydra
将尝试解析更复杂的值作为列表或字典。这些应指定为字符串
$ python examples/decorator.py layers="[1, 2, 3]" norms="{'conv': 'batch', 'epsilon': 1e-3}"
╭──────────────────────────────────────────────────╮
│ layers : [1, 2, 3] │
│ norms : {'conv': 'batch', 'epsilon': 0.001} │
╰──────────────────────────────────────────────────╯
强制类型
将 ___<type>
添加到键中会将此类型强制转换为值。注意 01
被解析为整数 1
,但 04
被解析为字符串(如指定)"04"
,而 hello
被解析为 list
,而不是保留为字符串
$ python examples/decorator.py n_jobs___str=04 job=01 chips___list=hello
╭────────────────────────────────────────╮
│ chips : ['h', 'e', 'l', 'l', 'o'] │
│ job : 1 │
│ n_jobs : 04 │
╰────────────────────────────────────────╯
已知类型在 Parser.known_types
中定义,分隔符(___
)在 Parser.type_separator
中
In [1]: from minydra import Parser
In [2]: Parser.known_types
Out[2]: {'bool', 'float', 'int', 'str'}
In [3]: Parser.type_separator
Out[3]: '___'
命令行配置
您可以使用特殊 @
参数从命令行配置 Parser
。换句话说,所有 __init__(self, ...)
参数都可以使用 @argname=new_value
从命令行设置。
特别是如果您运行 python examples/decorator.py @defaults=./examples/demo.json
,您将看到
╭──────────────────────────────────────╮
│ @defaults : ./examples/demo.json │
│ log │
│ │logger │
│ │ │log_level : DEBUG │
│ │ │logger_name : minydra │
│ │outdir : /some/path │
│ │project : demo │
│ verbose : False │
╰──────────────────────────────────────╯
但是如果您添加 @strict=false @keep_special_kwargs=false
,现在您将得到
$ python examples/decorator.py @defaults=./examples/demo.json @strict=false @keep_special_kwargs=false
╭──────────────────────────────╮
│ log │
│ │logger │
│ │ │log_level : DEBUG │
│ │ │logger_name : minydra │
│ │outdir : /some/path │
│ │project : demo │
│ verbose : False │
╰──────────────────────────────╯
(您需要设置 @strict=false
,因为 demo.json
中未知的 @keep_special_kwargs
。如果脚本本身使用了 strict=false
,则情况将不同(但可以从命令行覆盖!))
MinyDict
Minydra 的参数是围绕原生 dict
的自定义轻量级包装器,允许点访问(args.key
),将点键解析为嵌套字典,并以缩进的方式打印排序后的键和嵌套字典。如果键不存在,它不会失败,而是返回 None(与 dict.get(key, None)
相同)。
MinyDict
继承自 dict
,因此可以使用常用方法 .keys()
、.items()
等。
In [1]: from minydra.dict import MinyDict
In [2]: args = MinyDict({"foo": "bar", "yes.no.maybe": "idontknow"}).pretty_print(); args
╭──────────────────────────────╮
│ foo : bar │
│ yes.no.maybe : idontknow │
╰──────────────────────────────╯
Out[2]: {'foo': 'bar', 'yes.no.maybe': 'idontknow'}
In [3]: args.resolve().pretty_print(); args
╭──────────────────────────╮
│ foo : bar │
│ yes │
│ │no │
│ │ │maybe : idontknow │
╰──────────────────────────╯
Out[3]: {'foo': 'bar', 'yes': {'no': {'maybe': 'idontknow'}}}
In [4]: args.yes.no.maybe
Out[4]: "idontknow"
In [5]: "foo" in args
Out[5]: True
In [6]: "rick" in args
Out[6]: False
In [7]: args.morty is None
Out[7]: True
In [8]: args.items()
Out[8]: dict_items([('foo', 'bar'), ('yes', {'no': {'maybe': 'idontknow'}})])
转储/加载
您可以使用 3 种格式将 MinyDict
保存到或从磁盘读取:不依赖的 json
和 pickle
,以及依赖于 PyYAML
的 yaml
(pip install minydra[yaml]
)。
to_pickle
、to_json
和 to_yaml
方法有 3 个参数
file_path
,作为str
或pathlib.Path
,将进行解析- 展开环境变量(例如
$MYDIR
) - 展开用户(例如
~
) - 使绝对路径
- 展开环境变量(例如
return_path
默认为True
。如果这些方法返回创建的文件的路径allow_overwrites
默认为True
。如果为False
且path
已存在,则抛出FileExistsError
。否则在file_path
上创建/覆盖文件verbose
默认为0
。如果>0
,则打印创建对象的路径
注意
to/from_yaml
如果未安装PyYAML
,将失败并抛出ModuleNotFoundError
。json
标准不允许在字典中使用整数作为键,因此{3: 2}
将被转储为(因此加载为){"3": 2}
。
In [1]: from minydra.dict import MinyDict
In [2]: args = MinyDict({"foo": "bar", "yes.no.maybe": "idontknow"}).resolve(); args
Out[2]: {'foo': 'bar', 'yes': {'no': {'maybe': 'idontknow'}}}
In [3]: json_file_path = args.to_json("./args.json")
In [4]: yaml_file_path = args.to_yaml("./args.yaml")
In [5]: pkl_file_path = args.to_pickle("./args.pkl")
In [6]: _ = args.to_json("./args.json", verbose=1) # verbose argument prints the path
Json dumped to: /Users/victor/Documents/Github/vict0rsch/minydra/args.json
In [7]: MinyDict.from_json("args.json")
Out[7]: {'foo': 'bar', 'yes': {'no': {'maybe': 'idontknow'}}}
In [8]: assert (
MinyDict.from_yaml(yaml_file_path)
== MinyDict.from_json(json_file_path)
== MinyDict.from_pickle(pkl_file_path)
== args
)
python examples/dumps.py path="./myargs.pkl" format=pickle cleanup
╭────────────────────────────╮
│ cleanup : True │
│ format : pickle │
│ path : ./myargs.pkl │
╰────────────────────────────╯
Dumped args to /Users/victor/Documents/Github/vict0rsch/minydra/myargs.pkl
Cleaning up
严格模式
为了防止命令行中的错误,MinyDict.update
方法具有严格模式:使用 strict=True
通过另一个 MinyDict
更新 MinyDict
将在键不存在时抛出 KeyError
。
from minydra import MinyDict, resolved_args
if __name__ == "__main__":
# parse arbitrary args in 1 line
args = resolved_args()
# override default conf
if args.default:
path = args.default
# delete otherwise it will be used to update the conf which does not have
# "default" as a key, therefore raising a KeyError in strict mode
del args.default
args = MinyDict.from_json(path).update(args, strict=True)
args.pretty_print()
无错误
$ python examples/strict.py default=./examples/demo.json log.logger.log_level=INFO
╭──────────────────────────────╮
│ log │
│ │logger │
│ │ │log_level : INFO │
│ │ │logger_name : minydra │
│ │outdir : /some/path │
│ │project : demo │
│ verbose : False │
╰──────────────────────────────╯
错误
$ python examples/strict.py default=./examples/demo.json log.logger.log_leveel=INFO
Traceback (most recent call last):
File "/Users/victor/Documents/Github/vict0rsch/minydra/examples/strict.py", line 13, in <module>
args = MinyDict.from_json(path).update(args, strict=True)
File "/Users/victor/Documents/Github/vict0rsch/minydra/minydra/dict.py", line 111, in update
self[k].update(v, strict=strict)
File "/Users/victor/Documents/Github/vict0rsch/minydra/minydra/dict.py", line 111, in update
self[k].update(v, strict=strict)
File "/Users/victor/Documents/Github/vict0rsch/minydra/minydra/dict.py", line 100, in update
raise KeyError(
KeyError: 'Cannot create a non-existing key in strict mode ({"log_leveel":INFO}).'
使用默认配置
minydra.Parser
类接受一个 defaults=
关键字参数。这可以是
- 一个指向
json
、yaml
或pickle
文件的str
或pathlib.Path
,该文件可以被minydra.MinyDict
加载(from_X
) - 一个
dict
或一个minydra.MinyDict
- 上述类型的
list
,在这种情况下,结果默认值将是上述默认值的顺序更新结果,从而实现分层默认值(第一个默认值是起点,然后每个后续默认值更新它)
当提供 defaults
时,生成的 minydra.MinyDict
将作为从命令行解析的参数的参考
- 如果您使用
strict=True
设置解析器,命令行中的参数仍然具有更高的优先级,但它们必须存在于defaults
中,以防止输入错误或未知参数(请参阅严格模式) - 未在命令行中出现的参数将回退到
defaults
中的值
defaults
实际上可以是一个list
,其更新顺序与列表相同。例如
In [1]: from minydra import Parser
In [2]: Parser(defaults=["./examples/demo.json", "./examples/demo2.json"]).args.pretty_print();
╭─────────────────────────────────╮
│ log │
│ │logger │
│ │ │log_level : INFO │
│ │ │logger_name : minydra │
│ │outdir : /some/other/path │
│ │project : demo │
│ new_key : 3 │
│ verbose : False │
╰─────────────────────────────────╯
如果您需要从命令行设置默认值,可以使用特殊的@defaults
关键字
$ python examples/decorator.py @defaults=./examples/demo.json
╭──────────────────────────────────────╮
│ @defaults : ./examples/demo.json │
│ log │
│ │logger │
│ │ │log_level : DEBUG │
│ │ │logger_name : minydra │
│ │outdir : /some/path │
│ │project : demo │
│ verbose : False │
╰──────────────────────────────────────╯
$ python examples/decorator.py @defaults="['./examples/demo.json', './examples/demo2.json']"
╭───────────────────────────────────────────────────────────────────╮
│ @defaults : ['./examples/demo.json', './examples/demo2.json'] │
│ log │
│ │logger │
│ │ │log_level : INFO │
│ │ │logger_name : minydra │
│ │outdir : /some/other/path │
│ │project : demo │
│ new_key : 3 │
│ verbose : False │
╰───────────────────────────────────────────────────────────────────╯
pretty_print
以框的形式打印MinyDict
,并正确缩进字典。一些参数
indents
,默认值为2
:嵌套字典的缩进量sort_keys
,默认值为True
:在打印前是否按字母顺序排序键
to_dict
要生成本地的Python dict
,请使用args.to_dict()
受保护的属性
MinyDict
的方法(包括dict
类的方法)是受保护的,它们是只读的,因此您不能使用它们的名称设置属性,例如args.get = 2
。如果您确实需要设置get
参数,您可以通过items访问它:args["get"] = 2
。
python examples/protected.py server.conf.port=8000 get=3
╭────────────────────╮
│ get : 3 │
│ server │
│ │conf │
│ │ │port : 8000 │
╰────────────────────╯
<built-in method get of MinyDict object at 0x100ccd4a0>
3
dict_items([('get', 3), ('server', {'conf': {'port': 8000}})])
{'conf': {'port': 8000}}
测试
使用以下命令运行测试和预提交检查(isort
、black
、flake8
)
$ pip install -r requirements-test.txt
$ pre-commit run --all-files
$ pytest -vv --cov=minydra tests/
项目详细信息
下载文件
下载您平台上的文件。如果您不确定选择哪个,请了解更多关于安装软件包的信息。
源分发
构建分发
minydra-0.1.6-py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 89628aa56e6fc5eed9e7ed44654851b42acedbda9ec91a1128a60c953cf80790 |
|
MD5 | 6f9c94fffef5de171756ec0d7cc58870 |
|
BLAKE2b-256 | a06f073e28e53a9ca33d0e77088feb3633b4e795e8e1a2acc92a40ced701cf7d |