跳转到主要内容

无需依赖即可轻松解析命令行中的任意参数

项目描述

minydra 🦎

受Facebook的Hydra启发,具有点访问权限嵌套字典的极简Python命令行解析器。

无需依赖即可轻松解析命令行中的任意参数

example code example code

pip install minydra

minydra已在Python 3.73.83.9上进行了测试。


入门  •  强制类型  •  MinyDict  •  保存配置  •  防止输入错误  •  使用默认配置  •  示例


入门

examples/parser.py

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

examples/resolved_args.py

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")

examples/decorator.py

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()



解析

  • 简单字符串将自动解析为floatint
  • 单个关键字将被解释为正标志。
  • 以“-”开头的单个关键字将被解释为负标志。
  • 如果parse_envTrue,将评估环境变量。
$ 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 保存到或从磁盘读取:不依赖的 jsonpickle,以及依赖于 PyYAMLyamlpip install minydra[yaml])。

to_pickleto_jsonto_yaml 方法有 3 个参数

  1. file_path,作为 strpathlib.Path,将进行解析
    1. 展开环境变量(例如 $MYDIR
    2. 展开用户(例如 ~
    3. 使绝对路径
  2. return_path 默认为 True。如果这些方法返回创建的文件的路径
  3. allow_overwrites 默认为 True。如果为 Falsepath 已存在,则抛出 FileExistsError。否则在 file_path 上创建/覆盖文件
  4. 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
)

examples/dumps.py

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= 关键字参数。这可以是

  • 一个指向 jsonyamlpickle 文件的 strpathlib.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,并正确缩进字典。一些参数

  1. indents,默认值为2:嵌套字典的缩进量
  2. sort_keys,默认值为True:在打印前是否按字母顺序排序键

to_dict

要生成本地的Python dict,请使用args.to_dict()


受保护的属性

MinyDict的方法(包括dict类的方法)是受保护的,它们是只读的,因此您不能使用它们的名称设置属性,例如args.get = 2。如果您确实需要设置get参数,您可以通过items访问它:args["get"] = 2

请尝试examples/protected.py

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}}



测试

使用以下命令运行测试和预提交检查(isortblackflake8

$ pip install -r requirements-test.txt
$ pre-commit run --all-files
$ pytest -vv --cov=minydra tests/

项目详细信息


下载文件

下载您平台上的文件。如果您不确定选择哪个,请了解更多关于安装软件包的信息。

源分发

此版本没有可用的源分发文件。请参阅生成分发存档的教程。

构建分发

minydra-0.1.6-py3-none-any.whl (17.1 kB 查看哈希值

上传时间 Python 3

由以下机构支持

AWSAWS 云计算和安全赞助商 DatadogDatadog 监控 FastlyFastly CDN GoogleGoogle 下载分析 MicrosoftMicrosoft PSF赞助商 PingdomPingdom 监控 SentrySentry 错误日志 StatusPageStatusPage 状态页面