Python: 如何在命令行调用时启用kwargs?(也许使用argparse)

19

假设我有一个名为myscript.py的模块; 这个模块是生产代码,经常被称为 %dir%>python myscript.py foo bar

我想扩展它以接受关键字参数。 我知道可以使用下面的脚本来接受这些参数,但不幸的是必须使用 %dir%>python myscript.py main(foo, bar) 来调用它。

我知道可以使用argparse模块,但我不确定该如何操作。

import sys

def main(foo,bar,**kwargs):
    print 'Called myscript with:'
        print 'foo = %s' % foo
        print 'bar = %s' % bar
        if kwargs:
            for k in kwargs.keys():
                print 'keyword argument : %s' % k + ' = ' + '%s' % kwargs[k]   

if __name__=="__main__":
    exec(''.join(sys.argv[1:]))

你似乎已经提供了解决方案,但希望我们为你编写程序。你可以先参考文档,尝试一些方法,如果在使用argparse时遇到困难,请再次发布问题。 - Ian
我认为不需要使用argparse,我很快会想出一个答案。 - Moon Cheesez
没有必要使用argparse,但它能让用可选参数调用脚本变得更容易。 - chepner
使用我的当前解决方案,需要显式调用main方法,我想知道如何在没有这种限制的情况下实现它。 - Dr. John A Zoidberg
什么是关键字参数?它如何传递到程序中? - Moon Cheesez
5个回答

29

@Moon已经给出了类似的解决方案,但我建议事先进行解析并传入实际的kwargs

@Moon提供了与我类似的解决方案,但是我建议在传递参数时预先进行解析,并传递实际的kwargs

import sys

def main(foo, bar, **kwargs):
    print('Called myscript with:')
    print('foo = {}'.format(foo))
    print('bar = {}'.format(bar))
    for k, v in kwargs.items():
        print('keyword argument: {} = {}'.format(k, v))

if __name__=='__main__':
    main(sys.argv[1], # foo
         sys.argv[2], # bar
         **dict(arg.split('=') for arg in sys.argv[3:])) # kwargs

# Example use:
# $ python myscript.py foo bar hello=world 'with spaces'='a value'
# Called myscript with:
# foo = foo
# bar = bar
# keyword argument: hello = world
# keyword argument: with spaces = a value

11

首先,您不会将任意的Python表达式作为参数传递。这是脆弱且不安全的。

要设置参数解析器,您需要定义所需的参数,然后将它们解析以生成包含命令行调用指定信息的Namespace对象。

import argparse
p = argparse.ArgumentParser()
p.add_argument('foo')
p.add_argument('bar')
p.add_argument('--add-feature-a', dest='a', action='store_true', default=False)

在你的__main__块中,你将解析参数,然后将从Namespace生成的字典传递给main函数。

if __name__ == '__main__':
    args = p.parse_args()
    main(**vars(args))

那么你可以使用类似下面这行的代码来调用你的脚本:

# foo = "3", bar = "6", a = True
python myscript.py 3 6 --add-feature-a
或者
# foo = "moo", bar="7.7", a = False
python myscript.py moo 7.7

你可以使用argparse做更多的事情,但这只是一个简单的示例,用于将其产生的值传递到main中。


6

如果您想像在主函数中一样传递关键字参数,即key=value,可以按如下方式进行:

import sys

def main(foo, bar, *args):
    print "Called my script with"

    print "foo = %s" % foo
    print "bar = %s" % bar

    for arg in args:
        k = arg.split("=")[0]
        v = arg.split("=")[1]

        print "Keyword argument: %s = %s" % (k, v)


if __name__ == "__main__":
    if len(sys.argv) < 3:
        raise SyntaxError("Insufficient arguments.")
    if len(sys.argv) != 3:
        # If there are keyword arguments
        main(sys.argv[1], sys.argv[2], *sys.argv[3:])
    else:
        # If there are no keyword arguments
        main(sys.argv[1], sys.argv[2])

一些例子:

$> python my_file.py a b x=4
Called my script with
foo = a
bar = b
Keyword argument: x = 4
$> python my_file.py foo bar key=value
Called my script with
foo = foo
bar = bar
Keyword argument: key = value

然而,这种假设是前提是键和值之间没有任何空格,key = value 是行不通的。

如果您正在寻找类似于 --argument 的关键字参数,则应该使用 argparse


我不确定你在这个答案中使用了哪种“关键字参数”的解释,但它并不是 Python 中该术语通常所指的。 - Vladimir Panteleev

6

只需要两行代码,我就可以获得args和kwargs,并且可以像标准的args和kwargs一样进行操作:

import sys


if __name__=='__main__':
  argv=argv[1:] 
  kwargs={kw[0]:kw[1] for kw in [ar.split('=') for ar in argv if ar.find('=')>0]}
  args=[arg for arg in argv if arg.find('=')<0]

  #and you can the use args and kwargs as so:
  if 'reset' in args:
    do_some_functions_with_reset()
  a_device_var=kwargs.get('device',False):
  #or whatever you want to do with args and kwargs

结果是:

$python test.py reset device=foo format=1 bar
->args=['reset','bar']
->kwargs={'device':'foo','format':'1'}

在kwargs={'device':'foo','format':'1'}中,“=”应该被替换为“:”。 - J.K

1
通过一些内省,可以从函数的签名中设置ArgumentParser,从而直接将命令行参数映射到函数参数:
import argparse
import inspect

def myfun(mode, count=1, frobify=False, *files):
    print('Doing %s %d times on %s (%sfrobifying)' % (
        mode, count, files, '' if frobify else 'not '
    ))

def funopt(fun, argv=None):
    parser = argparse.ArgumentParser()

    if hasattr(inspect, 'getfullargspec'):
        spec = inspect.getfullargspec(fun)
    else:
        spec = inspect.getargspec(fun)

    num_defaults = len(spec.defaults) if spec.defaults is not None else 0
    for i in range(len(spec.args)):
        if i < len(spec.args) - num_defaults:
            parser.add_argument(spec.args[i])
        elif spec.defaults[i - len(spec.args)] is False:
            parser.add_argument('--' + spec.args[i], 
                                default=False, action='store_true')
        else:
            default = spec.defaults[i - len(spec.args)]
            parser.add_argument('--' + spec.args[i],
                                default=default,
                                type=type(default))
    if spec.varargs is not None:
            parser.add_argument(spec.varargs,
                                nargs='*')

    kwargs = vars(parser.parse_args(argv))
    args = []
    for arg in spec.args:
        args += [kwargs[arg]]
    if spec.varargs is not None:
        args += kwargs[spec.varargs]

    fun(*args)


funopt(myfun)

结果:

$ python test.py               
usage: test.py [-h] [--count COUNT] [--frobify] mode [files [files ...]]
test.py: error: too few arguments

$ python test.py myaction a b c
Doing myaction 1 times on ('a', 'b', 'c') (not frobifying)

$ python test.py --frobify --count=5 myaction a b c 
Doing myaction 5 times on ('a', 'b', 'c') (frobifying)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接