Python ArgParse 子解析器并链接到正确的函数

发布于 2024-11-14 05:59:08 字数 1369 浏览 6 评论 0原文

来管理不同类别的服务器(FTP、HTTP、SSH 等)。

在每种类型的服务器上,我们可以执行不同类型的操作(部署、配置、检查等)。

我正在创建一个小型 Python脚本 基 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. 类型的 操作要创建/管理的服务器

以前,我使用的是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 循环?

I'm creating a small Python script to manage different classes of servers (FTP, HTTP, SSH, etc.)

On each type of server, we can perform different types of actions (deploy, configure, check, etc.)

I have a base Server class, then a separate class for each type of server that inherits from this:

class Server:
    ...
    def check():
        ...

class HTTPServer(Server):
    def check():
        super(HTTPServer, self).check()
        ...
class FTPServer(Server):
    def check():
        super(FTPServer, self).check()
        ...

A sample command line might be:

my_program deploy http

From the command-line, the two mandatory arguments I need are:

  1. Operation to perform
  2. Type of server to create/manage

Previously, I was using argparse and the store operation, and using a dict to match the command-line option to the actual class and function name. For example:

types_of_servers = {
    'http': 'HTTPServer',
    'ftp': 'FTPServer',
    ...
}

valid_operations = {
    'check': 'check',
    'build': 'build',
    'deploy': 'deploy',
    'configure': 'configure',
    'verify': 'verify',
}

(In my actual code, valid_operations wasn't quite a naive 1:1 mapping.)

And then using rather horrible code to create the right type of object, and call the right class.

Then I thought I'd use argparse's subparsers feature to do it instead. So I've made each operation (check, build, deploy, etc.) a subparser.

Normally, I could link each sub-command to a particular function, and have it call it. However, I don't want to just call a generic check() function - I need to create the correct type of object first, and then call the appropriate function within that object.

Is there a good, or pythonic way to do this? Preferably one that doesn't involve a lot of hardcoding, or badly designed if/else loops?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

辞慾 2024-11-21 05:59:08

如果您设置为每个命令使用子解析器,我会做这样的事情。使用 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.

If you are set on using a subparser for each command I would do something like this. Use argparse's type support to call a function that lookups the class you want to instantiate and returns it.

Then call the method on that instance dynamically with 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)()

Output looks something like this:

$ 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.
策马西风 2024-11-21 05:59:08

您可以只在字典中使用对象本身。

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

You could just use the objects themselves in the 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
千秋岁 2024-11-21 05:59:08

这应该可行(但我认为手动映射会更直接):

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)

This should work (but a manual mapping would be more straight forward in my opinion):

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文