import fire
def hello(name="World"):
return f"Hello, {name}!"
if __name__ == '__main__':
fire.Fire(hello)
将上述代码保存为hello.py,然后在命令行中执行:
python hello.py
输出结果为:
Hello, World!
如果想要指定名字,可以这样调用:
python hello.py --name Alice
输出结果为:
Hello, Alice!
示例2:为类创建命令行接口
import fire
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
if __name__ == '__main__':
fire.Fire(Calculator)
保存为calculator.py,在命令行中执行加法操作:
python calculator.py add 5 3
输出结果为:
8
执行减法操作:
python calculator.py subtract 5 3
输出结果为:
2
示例3:嵌套命令结构
import fire
class IngestionStage:
def run(self):
return "Running ingestion stage"
class ProcessingStage:
def run(self, algorithm="default"):
return f"Running processing stage with {algorithm} algorithm"
class Pipeline:
def __init__(self):
self.ingestion = IngestionStage()
self.processing = ProcessingStage()
def run(self):
return "Running entire pipeline"
if __name__ == '__main__':
fire.Fire(Pipeline)
保存为pipeline.py,可以执行嵌套命令:
python pipeline.py ingestion run
输出结果为:
Running ingestion stage
python pipeline.py processing run --algorithm advanced
输出结果为:
Running processing stage with advanced algorithm
高级用法
参数类型自动推断
python-fire会自动推断参数类型,例如:
import fire
def multiply(a, b):
return a * b
if __name__ == '__main__':
fire.Fire(multiply)
执行以下命令:
python multiply.py 3 4
输出结果为:
12
这里参数被正确地识别为整数类型。如果需要指定其他类型,可以使用命令行标志,例如:
python multiply.py 3.5 4 --a=float --b=int
自定义命令行参数解析
有时需要更复杂的参数解析逻辑,可以使用fire.Fire的name和command参数:
import fire
def custom_command(name, age):
return f"{name} is {age} years old"
if __name__ == '__main__':
fire.Fire({
'info': custom_command
})
执行命令:
python custom.py info --name Alice --age 30
输出结果为:
Alice is 30 years old
生成帮助文档
python-fire会自动为命令行工具生成帮助文档,只需添加--help参数:
python calculator.py --help
输出结果类似:
NAME
calculator.py
SYNOPSIS
calculator.py COMMAND [--flags...]
COMMANDS
COMMAND is one of the following:
add
a b
subtract
a b
python main.py db create users # 执行嵌套命令
python main.py db drop logs
(2)命令分组(按功能分类)
# 按功能分组命令
@app.command()
def server(start: bool = True):
"""管理服务器"""
status = "启动" if start else "停止"
print(f"服务器已{status}")
@app.command()
def config(show: bool = False, update: str = None):
"""管理配置文件"""
if show:
print("当前配置...")
if update:
print(f"更新配置为:{update}")
3. 类型校验与错误处理
(1)自定义类型校验
from typing import Annotated
from typer import Argument, BadParameter
def validate_age(value: int):
if value < 0 or value > 150:
raise BadParameter("年龄必须在0-150之间")
return value
@app.command()
def check_age(age: Annotated[int, Argument(callback=validate_age)]):
"""校验年龄参数"""
print(f"年龄校验通过:{age}")
(2)捕获异常并自定义提示
import typer
from typer.exceptions import Exit
@app.command()
def risky_operation(force: bool = False):
"""危险操作(需谨慎)"""
if not force:
raise Exit(code=1, message="错误:未启用 --force 选项,操作被终止!")
print("危险操作已执行(请确保已备份数据)!")
4. 自动补全与扩展功能
(1)启用命令自动补全(bash/zsh/fish/powershell)
# 在主函数中添加补全支持(需安装 click-completion)
if __name__ == "__main__":
app()
from tqdm import tqdm # 需安装 tqdm 库
import time
@app.command()
def progress():
"""显示进度条示例"""
for i in tqdm(range(10), desc="Processing"):
time.sleep(0.5)
print("完成!")
User List
┌────┬──────────────┬───────────────────┐
│ ID │ Name │ Email │
├────┼──────────────┼───────────────────┤
│ 1 │ Alice Smith │ [email protected] │
│ 2 │ Bob Johnson │ [email protected] │
│ 3 │ Charlie Brown │ [email protected] │
└────┴──────────────┴───────────────────┘
import os
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
from rich.tree import Tree
from rich.table import Table
from datetime import datetime
3.2.2 定义文件扫描函数
def scan_directory(path):
files = []
total = sum(len(files) for _, _, files in os.walk(path)) # 计算总文件数
with Progress(
SpinnerColumn(), # 旋转动画
TextColumn("[bold blue]{task.description}"),
BarColumn(),
TextColumn("[green]{completed}/{total} files"),
) as progress:
task = progress.add_task("Scanning...", total=total)
for root, dirs, files in os.walk(path):
for file in files:
file_path = os.path.join(root, file)
files.append(file_path)
progress.update(task, advance=1) # 更新进度
return files
3.2.3 按类型分类文件
def categorize_files(files):
categories = {}
for file in files:
ext = os.path.splitext(file)[1].lower()[1:] # 获取扩展名
if ext:
if ext not in categories:
categories[ext] = []
categories[ext].append(file)
return categories
3.2.4 生成目录树
def build_directory_tree(path):
tree = Tree(f"[bold green]{os.path.basename(path)}")
for root, dirs, files in os.walk(path, topdown=True):
current_tree = tree
relative_path = os.path.relpath(root, path)
if relative_path != ".":
nodes = relative_path.split(os.sep)
for node in nodes:
current_tree = current_tree.add(node)
for file in files:
file_size = os.path.getsize(os.path.join(root, file))
mod_time = datetime.fromtimestamp(os.path.getmtime(os.path.join(root, file))).strftime("%Y-%m-%d %H:%M")
current_tree.add(f"[blue]{file}[/blue] ({file_size} bytes, {mod_time})")
return tree
import click
@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
help='The person to greet.')
def hello(count, name):
"""简单的问候命令"""
for x in range(count):
click.echo(f'Hello {name}!')
if __name__ == '__main__':
hello()
在这个示例中,我们添加了两个选项:
--count:用于指定问候的次数,默认值为1
--name:用于指定问候的对象,如果用户没有提供这个选项,Click会提示用户输入
你可以这样使用这个命令:
python hello.py --count 3 --name Alice
输出结果:
Hello Alice!
Hello Alice!
Hello Alice!
如果你不提供--name选项,程序会提示你输入:
python hello.py --count 2
输出:
Your name: Bob
Hello Bob!
Hello Bob!
3.3.2 短选项
Click支持为选项定义短形式,例如-c作为--count的短选项。修改上面的代码:
@click.option('-c', '--count', default=1, help='Number of greetings.')
@click.option('-n', '--name', prompt='Your name',
help='The person to greet.')
现在你可以使用短选项:
python hello.py -c 3 -n Alice
3.3.3 布尔选项
布尔选项用于表示真假值。Click提供了两种方式来定义布尔选项:
import click
@click.command()
@click.option('--shout/--no-shout', default=False, help='Shout the greeting.')
def hello(shout):
"""带有布尔选项的问候命令"""
greeting = 'Hello World!'
if shout:
greeting = greeting.upper()
click.echo(greeting)
if __name__ == '__main__':
hello()
import click
import time
@click.command()
@click.argument('count', type=click.INT)
def slow_process(count):
"""显示进度条的慢处理示例"""
with click.progressbar(range(count), label='Processing items') as bar:
for i in bar:
# 模拟耗时操作
time.sleep(0.1)
if __name__ == '__main__':
slow_process()
# main.py
import gin
from math_operations import add
@gin.configurable
def run_calculation():
result = add()
print(f"The result of addition is: {result}")
if __name__ == "__main__":
gin.parse_config_file("config.gin")
run_calculation()
from decouple import config
import re
# 校验邮箱格式
email = config('ADMIN_EMAIL')
if not re.match(r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', email):
raise ValueError("管理员邮箱格式错误")
print(f"管理员邮箱:{email}")