使用 python 和 typer 为类方法编写命令行

问题描述 投票:0回答:2

我正在使用 Typer logo Typer 使用 python 编写命令行程序。

代码

这是我遇到麻烦的示例脚本。

import typer

app = typer.Typer()

@app.command()
def hello(name):
    typer.echo(f'Hello!')

@app.command()
def goodbye():
    typer.echo(f'Goodbye.')

class Grettings:

    @app.command()
    def aloha(self):
        typer.echo('Aloha!')

    @app.command()
    def bonjour(self):
        typer.echo('Bonjour!')

if __name__ == '__main__':
    app()

当在终端中输入以下命令时,会给出预期的输出。

python main.py hello   
python main.py goodbye

问题

但是,当调用类方法时,出现以下异常。

python main.py aloha  
python main.py bonjour

    Usage: main.py aloha [OPTIONS] SELF
    Try 'main.py aloha --help' for help.
    
    Error: Missing argument 'SELF'.

显然,这是因为类尚未初始化。但这似乎是一个常见问题,所以我认为这个问题有一个简单的解决方案。

研究

我发现的可能的解决方案包括在正在使用的类/方法上使用装饰器,或者使用需要继承的特殊类来“公开”类方法。

来自答案的提示:实例方法的装饰器可以访问类吗? :

任何装饰器都会在类构建之前被调用,因此装饰器不知道。

python command-line-interface typer
2个回答
4
投票
修饰实例方法的问题

Typer 尝试以 Grettings().aloha()

 的形式调用回调。
这在 Python 中会失败并出现错误:

类型错误:hallo() 缺少 1 个必需的位置参数:'self'

Typer中命令调用的演示

请参阅以下在Python shell中录制的演示:

第 1 部分:它是如何工作的(使用静态函数,无自变量)

>>> import typer >>> app = typer.Typer() >>> app <typer.main.Typer object at 0x7f0713f59c18> >>> app.__dict__ {'_add_completion': True, 'info': <typer.models.TyperInfo object at 0x7f0713f59c50>, 'registered_groups': [], 'registered_commands': [], 'registered_callback': None} >>> @app.command() ... def hello(): ... typer.echo('hello') ... >>> app.__dict__['registered_commands'] [<typer.models.CommandInfo object at 0x7f0711e69cf8>] >>> app.__dict__['registered_commands'][0].cls <class 'typer.core.TyperCommand'> >>> app.__dict__['registered_commands'][0].callback <function hello at 0x7f070f539378> >>> app.__dict__['registered_commands'][0].callback() hello
第 2 部分:它如何不起作用(使用实例方法,需要 self 参数)

>>> class German: ... @app.command() ... def hallo(self): ... typer.echo('Hallo') ... >>> app.__dict__['registered_commands'][1] <typer.models.CommandInfo object at 0x7f070f59ccf8> >>> app.__dict__['registered_commands'][1].callback <function German.hallo at 0x7f070f539158> >>> app.__dict__['registered_commands'][1].callback() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: hallo() missing 1 required positional argument: 'self' >>> app.__dict__['registered_commands'][1].callback(German()) Hallo
注意:在最后一个语句中,我将一个新实例作为参数 

self

 传递给回调,并且调用成功并获得预期输出。

固定代码

我改变了三件事:

    将您的班级
  1. Grettings
     重命名为 
    Greetings
    (拼写)
  2. 将 2 个现有方法重新定义为静态类方法,如
  3. Barmar 的评论建议
  4. 此外,我添加了一个新的实例方法
  5. nihao(self)
    来演示失败。
import typer app = typer.Typer() @app.command() def hello(name): typer.echo(f'Hello!') @app.command() def goodbye(): typer.echo(f'Goodbye.') class Greetings: @app.command() def aloha(): # function or class-method (implicitly static) typer.echo('Aloha!') @staticmethod # explicitly static @app.command() def bonjour(): # no self argument! typer.echo('Bonjour!') @app.command() def nihao(self): # callback invocation fails because missing self argument typer.echo('Nihao!') if __name__ == '__main__': app()
行为和输出符合预期

尽管提供的命令仍然将

nihao

 列为可用,但它的调用将像您所经历的一样失败。

但是现在可以调用命令修饰的静态方法了。

$ python3 SO_typer.py --help Usage: SO_typer.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion [bash|zsh|fish|powershell|pwsh] Install completion for the specified shell. --show-completion [bash|zsh|fish|powershell|pwsh] Show completion for the specified shell, to copy it or customize the installation. --help Show this message and exit. Commands: aloha bonjour goodbye hello nihao
🇨🇳️中文问候语失败,因为没有参数

self

通过调用:

$ python3 SO_typer.py nihao Usage: SO_typer.py nihao [OPTIONS] SELF Try 'SO_typer.py nihao --help' for help. Error: Missing argument 'SELF'.
🏴筛选 筛选 夏威夷问候语有效,因为现在可以静态调用:

$ python3 SO_typer.py aloha Aloha!
另请参阅

  • Python中我们真的需要@staticmethod装饰器来声明静态方法吗
  • 单击文档 (8.0.x):
  • 回调调用

0
投票

skycaptain

正在回复它 - 
https://github.com/tiangolo/typer/issues/309#issuecomment-1934028088

我已经实现了一种模式来自动获取命令方法。

import inspect import typer class MyKlass: some_default_value: str def __init__(self, some_default_value: str): self.some_default_value = some_default_value self.app = typer.Typer() for method, _ in inspect.getmembers(self, predicate=inspect.ismethod): if not method.startswith('cmd'): continue cmd_name = method.strip('cmd_') self.app.command( name=cmd_name, help=self.some_default_value )(eval(f'self.{method}')) def cmd_run(self): print("Running") def cmd_sleep(self): print("sleep") if __name__ == "__main__": MyKlass(some_default_value="it's a command").app()
Usage: python -m legalops_commons.utils.foo [OPTIONS] COMMAND [ARGS]...

Options:
  --install-completion  Install completion for the current shell.
  --show-completion     Show completion for the current shell, to copy it or
                        customize the installation.
  --help                Show this message and exit.

Commands:
  run    it's a command
  sleep  it's a command
    
© www.soinside.com 2019 - 2024. All rights reserved.