Python ArgParse Subparsers并链接到正确的函数

时间:2011-06-07 08:49:45

标签: python parameters command-line-arguments 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

从命令行,我需要的两个必要参数是:

  1. 执行操作
  2. 要创建/管理的服务器类型
  3. 以前,我使用argparsestore操作,并使用dict将命令行选项与实际的类和函数名匹配。例如:

    types_of_servers = {
        'http': 'HTTPServer',
        'ftp': 'FTPServer',
        ...
    }
    
    valid_operations = {
        'check': 'check',
        'build': 'build',
        'deploy': 'deploy',
        'configure': 'configure',
        'verify': 'verify',
    }
    

    (在我的实际代码中,valid_operations并不是一个天真的1:1映射。)

    然后使用相当可怕的代码来创建正确类型的对象,并调用正确的类。

    然后我想我会使用argparse的subparsers功能代替它。所以我已经完成了每个操作(检查,构建,部署等)subparser

    通常,我可以将每个子命令链接到一个特定的函数,并让它调用它。但是,我不想只调用泛型check()函数 - 我需要先创建正确类型的对象 ,然后调用该对象中的相应函数。

    有没有好的或pythonic方式来做到这一点?最好是一个不涉及大量硬编码或者设计错误的if / else循环的那个?

3 个答案:

答案 0 :(得分:2)

如果你开始为每个命令使用subparser,我会做这样的事情。使用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.

答案 1 :(得分:0)

您可以在dict中使用对象本身。

#!/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

答案 2 :(得分: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)