Python ArgParse 子解析器与正确函数的链接
我正在写一个小的Python脚本,用来管理不同类型的服务器(比如FTP、HTTP、SSH等)。
在每种类型的服务器上,我们可以执行不同的操作(比如部署、配置、检查等)。
我有一个基础的Server
类,然后为每种服务器类型创建一个单独的类,这些类都继承自这个基础类:
class Server:
...
def check():
...
class HTTPServer(Server):
def check():
super(HTTPServer, self).check()
...
class FTPServer(Server):
def check():
super(FTPServer, self).check()
...
一个示例命令行可能是:
my_program deploy http
在命令行中,我需要的两个必填参数是:
- 要执行的操作
- 要创建或管理的服务器类型
之前,我使用argparse
和store
操作,并用一个dict
来将命令行选项与实际的类和函数名称匹配。例如:
types_of_servers = {
'http': 'HTTPServer',
'ftp': 'FTPServer',
...
}
valid_operations = {
'check': 'check',
'build': 'build',
'deploy': 'deploy',
'configure': 'configure',
'verify': 'verify',
}
(在我的实际代码中,valid_operations并不是简单的一对一映射。)
然后我用一些很糟糕的代码来创建正确类型的对象,并调用正确的类。
后来我想用argparse的subparsers
功能来实现这个功能。所以我把每个操作(检查、构建、部署等)都做成了一个子解析器。
通常,我可以将每个子命令链接到一个特定的函数,并让它调用这个函数。然而,我不想只是调用一个通用的check()
函数——我需要先创建正确类型的对象,然后再调用这个对象中的适当函数。
有没有好的、符合Python风格的方法来做到这一点?最好是不要涉及很多硬编码,或者设计得很糟糕的if/else循环?
3 个回答
0
这段代码应该可以正常运行(不过我觉得手动映射会更简单明了):
import argparse
class Proxy:
def __getattr__(thing):
def caller (type):
if type:
server_object = # get instance of server with right type
return getattr(server_object, thing)()
return caller
parser = argparse.ArgumentParser()
entry_parser.add_argument('--server_type', dest='server_type', required=True,choices=['http', 'ftp', 'ssh'],)
subparser = parser.add_subparsers(dest='operation')
for operation in ['check', 'build', 'deploy', 'configure', 'verify']:
entry_parser = subparser.add_parser(operation)
entry_parser.set_defaults(func=getattr(Proxy, command))
options = parser.parse_args()
# this will call proxy function caller with type argument
options.func(options.server_type)
0
你可以直接在字典里使用这些对象。
#!/usr/bin/python
class Server:
def __init__(self):
pass
def identify(self):
print self.__class__.__name__
def check(self):
raise SomeErrorBecauseThisIsAbstract
class HttpServer(Server):
def check(self, args):
if self.verify_http_things():
return True
else:
raise SomeErrorBecauseTheCheckFailed
pass
class FtpServer(Server):
def check(self, args):
if self.verify_ftp_things():
return True
else:
raise SomeErrorBecauseTheCheckFailed
pass
if __name__ == '__main__':
# Hopefully this edit will make my intent clear:
import argparse
parser = argparse.ArgumentParser(description='Process some server commands')
parser.add_argument('-c', dest='command')
parser.add_argument('-t', dest='server_type')
args = parser.parse_args()
servers = {
'http': HttpServer,
'ftp': FtpServer
}
try:
o = servers[args.server_type]()
o.__call__(args.command)
except Exception, e:
print e
3
如果你决定为每个命令使用一个子解析器,我会这样做。可以利用argparse的类型支持,调用一个函数,这个函数会查找你想要创建的类,并返回这个类。
然后,使用getattr()动态地调用这个实例的方法。
import argparse
class Server:
def check(self):
return self.__class__.__name__
class FooServer(Server):
pass
class BarServer(Server):
pass
def get_server(server):
try:
klass = globals()[server.capitalize()+'Server']
if not issubclass(klass, Server):
raise KeyError
return klass()
except KeyError:
raise argparse.ArgumentTypeError("%s is not a valid server." % server)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command')
check = subparsers.add_parser('check')
check.add_argument('server', type=get_server)
args = parser.parse_args()
print getattr(args.server, args.command)()
输出看起来大概是这样的:
$ python ./a.py check foo
FooServer
$ python ./a.py check bar
BarServer
$ python ./a.py check baz
usage: a.py check [-h] server
a.py check: error: argument server: baz is not a valid server.