将使用argparse的python脚本/模块导入到另一个python脚本中。

8
这个问题可能被认为是多余的,但为了辩护,我考虑了类似的问题并给出了解决方案,但它们对我不起作用(或者我只是没有理解),我将展示原因。
我编写了一些Python脚本,它们都是命令行工具(使用argparse接受几个参数),使用pyinstaller构建时正常工作。它们在PyCharm终端和Ubuntu终端中都能按预期工作。现在,我想让每个脚本成为一个模块,可以从另一个Python脚本中调用,传递所需的参数,就像在终端中做的那样。
这是其中一个原始脚本(我缩短了脚本,以便它作为最小示例):
import sys
import argparse
import getpass


if len(sys.argv) < 2:
    print "You haven't specified any arguments. Use -h to get more details on how to use this command."
    sys.exit(1)


parser = argparse.ArgumentParser()
parser.add_argument('--username', '-u', type=str, default=None, help='Username for the login to the WebCTRL server')
parser.add_argument('--password', '-p', type=str, default=None, help='Password for the login to the WebCTRL server')
parser.add_argument('--node', '-n', type=str, default=None,
    help='Path to the point or node whose children you want to retrieve. Start querying at the lowest level with "-n /trees/geographic"')
parser.add_argument('-url', type=str, default='https://my.server.de',
    help="URL of the WebCTRL server as e.g. http://google.de")
args = parser.parse_args()


if args.username is None:
    print 'No user name specified. Login to WebCTRL needs a user name and password. Check all options for this command via -h'
    sys.exit(1)
else:
    username = args.username
if args.password is None:
    password = getpass.getpass('No password specified via -p. Please enter your WebCTRL login password: ')
else:
    password = args.password
if args.node is None:
    print 'No path to a node specified. Check all options for this command via -h'
    sys.exit(1)
if args.url is None:
    print 'No URL given. Specify the URL to the WebCTRL server analogous to http://google.de'
    sys.exit(1)
else:
    wsdlFile = args.url + '/_common/webservices/Eval?wsdl'


# This doesn't belong to my original code. It's rather for demonstration:
# Print the arguments and leave the script
print 'Username: ' + args.username
print 'Node: ' + args.node
print 'URL: ' + args.url

sys.exit(0)

就像我之前说的,在我的IDE(Pycharm)内部...
$ python wc_query_test.py -u wsdl -n /trees/geographic
No password specified via -p. Please enter your WebCTRL login password: 
Username: wsdl
Node: /trees/geographic
URL: https://my.server.de

...而且在Ubuntu终端中它可以正常工作:

$ pyinstaller --distpath dist/. wc_query_test.py
$ ./dist/wc_query_test/wc_query_test -u wsdl -n /trees/geographic
No password specified via -p. Please enter your WebCTRL login password: 
Username: wsdl
Node: /trees/geographic
URL: https://my.server.de

接下来是实际问题: 我希望脚本wc_query_test.py能够成为一个模块,可以被导入到另一个Python脚本中并在那里执行,就像在命令行中传递参数一样。为了实现这一点,我遵循了@Waylan在这个stackoverflow问题中的指示。

这是我设计的代码(wc_query_test.py):

import sys
import argparse
import getpass

def main(**kwargs):
    if kwargs.username is None:
        print 'No user name specified. Login to WebCTRL needs a user name and password. Check all options for this command via -h'
        sys.exit(1)
    else:
        username = kwargs.username
    if kwargs.password is None:
        password = getpass.getpass('No password specified via -p. Please enter your WebCTRL login password: ')
    else:
        password = kwargs.password
    if kwargs.node is None:
        print 'No path to a node specified. Check all options for this command via -h'
        sys.exit(1)
    if kwargs.url is None:
        print 'No URL given. Specify the URL to the WebCTRL server analogous to http://google.de'
        sys.exit(1)
    else:
        wsdlFile = kwargs.url + '/_common/webservices/Eval?wsdl'

    # This doesn't belong to my original code. It's rather for demonstration:
    # Print the arguments and leave the script
    print 'Username: ' + username
    print 'Node: ' + kwargs.node
    print 'URL: ' + kwargs.url

    sys.exit(0)

def run():
    parser = argparse.ArgumentParser()
    parser.add_argument('--username', '-u', type=str, default=None, help='Username for the login to the WebCTRL server')
    parser.add_argument('--password', '-p', type=str, default=None, help='Password for the login to the WebCTRL server')
    parser.add_argument('--node', '-n', type=str, default=None,
        help='Path to the point or node whose children you want to retrieve. Start querying at the lowest level with "-n /trees/geographic"')
    parser.add_argument('-url', type=str, default='https://my.server.de',
        help="URL of the WebCTRL server as e.g. http://google.de")
    args = parser.parse_args()

    main(**args)

以下是导入模块并调用它的脚本(test.py):

import wc_query_test
wc_query_test.main(username='wsdl', password='aaaaaa', node='/trees/geographic')

当我在Python终端运行它时,我得到了以下输出:
~/PycharmProjects/webctrl$ python wc_query_test.py
~/PycharmProjects/webctrl$ python test.py 
Traceback (most recent call last):
  File "test.py", line 3, in <module>
    wc_query_test.main(username='wsdl', password='aaaaaa', node='/trees/geographic/#geb_g', url='https://webctrl.rz-berlin.mpg.de')
  File "/home/stefan/PycharmProjects/webctrl/wc_query_test.py", line 23, in main
    if kwargs.username is None:
AttributeError: 'dict' object has no attribute 'username'

运行 wc_query_test.py 没有输出,我知道为什么。那只是一个测试。但是运行 test.py 也会出现错误。我有个想法为什么这不起作用,但我无法用语言表达出来。run()方法怎么样?有它存在的意义吗?我需要如何修改代码才能获得“我想要做的事情”中描述的双重功能?感谢您提前的帮助!
更新: 我摆脱了错误消息。例如,我将 kwargs.username 改为 kwargs.get('username'),因为 kwargs 是一个字典。现在代码看起来像这样:
import sys
import argparse
import getpass

def main(**kwargs):
    if kwargs.get('username') is None:
        print 'No user name specified. Login to WebCTRL needs a user name and password. Check all options for this command via -h'
        sys.exit(1)
    else:
        username = kwargs.get('username')
    if kwargs.get('password') is None:
        password = getpass.getpass('No password specified via -p. Please enter your WebCTRL login password: ')
    else:
        password = kwargs.get('password')
    if kwargs.get('node') is None:
        print 'No path to a node specified. Check all options for this command via -h'
        sys.exit(1)
    if kwargs.get('url') is None:
        print 'No URL given. Specify the URL to the WebCTRL server analogous to http://google.de'
        sys.exit(1)
    else:
        wsdlFile = kwargs.get('url') + '/_common/webservices/Eval?wsdl'

    # This doesn't belong to my original code. It's rather for demonstration:
    # Print the arguments and leave the script
    print 'Username: ' + username
    print 'Node: ' + kwargs.get('node')
    print 'URL: ' + kwargs.get('url')

    sys.exit(0)

def run():
    parser = argparse.ArgumentParser()
    parser.add_argument('--username', '-u', type=str, default=None, help='Username for the login to the WebCTRL server')
    parser.add_argument('--password', '-p', type=str, default=None, help='Password for the login to the WebCTRL server')
    parser.add_argument('--node', '-n', type=str, default=None,
        help='Path to the point or node whose children you want to retrieve. Start querying at the lowest level with "-n /trees/geographic"')
    parser.add_argument('-url', type=str, default='https://webctrl.rz-berlin.mpg.de',
        help="URL of the WebCTRL server as e.g. http://google.de")
    args = parser.parse_args()

    main(**args)

在Python终端中运行它,得到了预期的结果:
$ python test.py 
Username: wsdl
Node: /trees/geographic
URL: https://my.server.de

但是通过pyinstaller构建并作为命令行工具运行它时,没有输出:

~/PycharmProjects/webctrl$ ./dist/wc_query_test/wc_query_test -h
~/PycharmProjects/webctrl$

我该如何修改 wc_query_test.py 使其接受参数并作为命令行工具使用?

2
kwargs 是一个字典,没有 'username' 属性,就像错误所述。你想要访问项:kwargs['username']。请注意,main(**args) 也会失败,因为 parse_args() 返回的 Namespace 对象不是映射。一个快速而简单的解决方案是使用 main(**vars(args)) - Ilja Everilä
此外,您的 main() 函数不应该打印错误消息并调用 sys.exit(),而是应该引发异常(带有错误消息)。 - bruno desthuilliers
没有详细研究代码,我注意到了一个明显的问题。最好在 if __name__ 块中调用解析器。这样只有当脚本作为脚本被调用时才会使用它,而不是被另一个脚本导入时也运行解析器。在几个被导入的脚本中运行解析器会引发麻烦。 - hpaulj
2个回答

6
感谢所有回复的人。在同事的帮助下,我得到了问题的答案。这是运行代码: :
import sys
import argparse
import getpass

def main(args):
    if args['username'] is None:
        print 'No user name specified. Login to WebCTRL needs a user name and password. Check all options for this command via -h'
        sys.exit(1)
    else:
        username = args['username']
    if args['password'] is None:
        password = getpass.getpass('No password specified via -p. Please enter your WebCTRL login password: ')
    else:
        password = args['password']
    if args['node'] is None:
        print 'No path to a node specified. Check all options for this command via -h'
        sys.exit(1)
    if args['url'] is None:
        print 'No URL given. Specify the URL to the WebCTRL server analogous to http://google.de'
        sys.exit(1)
    else:
        wsdlFile = args['url'] + '/_common/webservices/Eval?wsdl'

    # This doesn't belong to my original code. It's rather for demonstration:
    # Print the arguments and leave the script
    print 'Username: ' + args['username']
    print 'Node: ' + args['node']
    print 'URL: ' + args['url']


# The parser is only called if this script is called as a script/executable (via command line) but not when imported by another script
if __name__=='__main__':
    if len(sys.argv) < 2:
        print "You haven't specified any arguments. Use -h to get more details on how to use this command."
        sys.exit(1)
    parser = argparse.ArgumentParser()
    parser.add_argument('--username', '-u', type=str, default=None, help='Username for the login to the WebCTRL server')
    parser.add_argument('--password', '-p', type=str, default=None, help='Password for the login to the WebCTRL server')
    parser.add_argument('--node', '-n', type=str, default=None,
        help='Path to the point or node whose children you want to retrieve. Start querying at the lowest level with "-n /trees/geographic"')
    parser.add_argument('-url', type=str, default='https://webctrl.rz-berlin.mpg.de',
        help="URL of the WebCTRL server as e.g. http://google.de")
    args = parser.parse_args()

    # Convert the argparse.Namespace to a dictionary: vars(args)
    main(vars(args))
    sys.exit(0)

现在,有三种方法来执行wc_query_test,这就是我想要实现的:
1)从命令行调用wc_query_test.py:
~/PycharmProjects/webctrl$ python wc_query_test.py -u aawrg -p wgAWER -n YWERGAEWR

2)从命令行编译和调用wc_query_test:

~/PycharmProjects/webctrl$ pyinstaller --distpath dist/. wc_query_test.py
~/PycharmProjects/webctrl$ ./dist/wc_query_test/wc_query_test -u aawrg -p wgAWER -n YWERGAEWR

3) 从另一个 Python 脚本中调用 wc_query_test,这将走向模块类型的使用方向:

import wc_query_test
myDictonary = {'username':'wsdl', 'password':'aaaaaa', 'node':'/trees/geographic', 'url':'https://my.server.de'}
wc_query_test.main(myDictonary)

所有三个版本的输出结果都和预期相同,例如:

~/PycharmProjects/webctrl$ ./dist/wc_query_test/wc_query_test -u aawrg -p wgAWER -n YWERGAEWR
Username: aawrg
Node: YWERGAEWR
URL: https://webctrl.rz-berlin.mpg.de

我曾经遇到过同样的问题。显然,我必须将解析器代码放在主函数中,而不是导入脚本的全局范围内。 - Wildhammer

0

**kwargs 是用于将字典或 关键字=值 对传递给函数的。 args = parser.parse_args() 返回一个 argparse.Namespace 对象,而不是一个字典。但是,vars(args) 可以从中生成一个字典。

In [2]: def foo1(args):
   ...:     print(args)
   ...:     print(args.foo)
   ...:     
In [3]: def foo2(**kwargs):
   ...:     print(kwargs)
   ...:     print(kwargs['foo'])

In [12]: p = argparse.ArgumentParser()
In [13]: p.add_argument('--foo');
In [14]: args = p.parse_args('--foo one'.split())
In [15]: args
Out[15]: Namespace(foo='one')

args传递给foo1:
In [16]: foo1(args)
Namespace(foo='one')
one

使用foo2试试看

In [17]: foo2(args)
...
TypeError: foo2() takes 0 positional arguments but 1 was given

In [20]: foo2(**args)
TypeError: foo2() argument after ** must be a mapping, not Namespace

但传递扩展的字典版本:

In [18]: vars(args)
Out[18]: {'foo': 'one'}
In [19]: foo2(**vars(args))
{'foo': 'one'}
one

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