用于命令行脚本和导入模块的 Python“模板”模块

Posted

技术标签:

【中文标题】用于命令行脚本和导入模块的 Python“模板”模块【英文标题】:Python "template" module for both command line scripts and imported module 【发布时间】:2018-09-06 04:40:24 【问题描述】:

我想编写一个 python“模板”模块,以便为我的所有脚本提供相同的行为。

行为如下:

如果脚本在命令行中运行,它会接受使用 argparse 处理的参数。这些论点基本上是: 从标准输入、文件或字符串参数中输入json; 在标准输出或文件中输出json。 如果脚本作为模块导入,则它具有管理以下情况的类/函数: 接受来自谁调用的对象的输入; 输出一个对象,以便调用它的人可以使用它。

我做了什么:

“模板”部分 template.py

​​>

多亏了这些建议,它在命令行中的行为完全符合我的要求: Python argparse mutually exclusive with stdin being one of the options

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import json,sys,argparse,os

def main():

    parser = argparse.ArgumentParser(description='Template for python script managing JSON as input/output format. \
                                                  A JSON file can be [], , "string", 123, true, false, null.')

    infile=['-i','--input-file']
    kwinfile='type':argparse.FileType('r'), 'help':'Input file name containing a valid JSON. Default and priority: standard input.'

    jstring=['-j','--json']
    kwjstring='type':str,  'nargs':'?', 'help':'Input file name containing a valid JSON. Default and priority: standard input.'

    outfile=['-o','--output-file']
    kwoutfile='type':argparse.FileType('w'), 'help':'Output file name. Default: standard output.', 'default':sys.stdout

    pretty=['-p','--pretty']
    kwpretty='action':'store_true', 'help':'If set, JSON output  will be formatted in pretty print.'

    group = parser.add_mutually_exclusive_group()

    group.add_argument(*infile, **kwinfile)
    group.add_argument(*jstring, **kwjstring)
    parser.add_argument(*outfile, **kwoutfile)
    parser.add_argument(*pretty, **kwpretty)

    args = parser.parse_args()

    return(args)

def input(*data):
    args=main()

    # if data :
    #     datain=data[0]

    # else :
    if not sys.stdin.isatty(): # pipe
        data=sys.stdin.read()
    else:  # no pipe
        if not len(sys.argv) > 1 or (args.input_file == None and args.json == None) : # no arguments or no input
            data='null'
        else :
            data = args.json or args.input_file.read()

    try:
        datain = json.loads(data)
    except:
        output('script_name':(sys.argv[0]),
                'error': 'Input is not a valid JSON.',
                'data': data)
        sys.exit(0)

    return(datain)


def output(*datain) :
    args=main()
    if datain :
        datain=datain[0]

    indent = 2 if args.pretty else None

    dataout = json.dumps(datain, indent=indent, ensure_ascii=False)
    args.output_file.write(dataout+os.linesep)

    return(dataout)

if __name__ == "__main__":
    main()

我希望这是实现它的最佳方式。

示例“calculate_area”

现在,如果我使用

在脚本中导入它
import template as t

def main():

    inp=t.input() # "x":8, "y":2

    out='area' : inp['x'] * inp['y'] 

    return(t.output(out))

if __name__ == "__main__":
    main()

脚本按照我的意愿在命令行中运行:

$ echo '"x":8, "y":2' | ./calculate_area.py -p

  "area": 16

“calculate_sqrt”脚本将其作为一个模块进行测试

现在我想要第三个脚本将其作为模块导入。

import template as t
import calculate_area as i
import numpy as np
import json

def main():

    inp=json.loads(i.main())

    out='sqrt of area' : np.sqrt(inp['area']) 

    return(t.output(out))

if __name__ == "__main__":
    main()

问题从这里开始:

$ echo '"x":8, "y":2' | ./calculate_sqrt.py -p

  "area": 16


  "sqrt of area": 4.0

为什么我得到两个输入而不是最后一个?

此外:

如何避免在json中输入?而是说:“如果模块是通过import 调用的,那么输入/输出将通过对象,否则它将通过命令行中的json”?

我在这里保存了我的代码: https://github.com/orsa-unige/python-templates/tree/simplified-example

【问题讨论】:

在您的Template 代码中,您调用main(),但不要对返回的args 执行任何操作。在inputoutput 中再次调用它,这次节省了args。但我没有看到对这两个函数的任何调用。 FileType 设置为接受- 表示stdinstdout。这应该让您无需特殊测试即可接受重定向(甚至可能是管道?)输入/输出。 @hpaulj 我不明白...我调用它是为了可以使用args i.main() 产生area 字符串。它返回它,但也写入文件。 如何避免呢?注释掉 args.output_file.write 导致没有输出;将 return 替换为 print 会产生相同的行为 【参考方案1】:

在我看来,这是一个好的基本脚本的大纲:

import json,sys,argparse,os

def parser(argv=None):
    # if argv is None, uses the sys.argv[1:]
    parser = argparse.ArgumentParser(....)
    ...
    args = parser.parse_args(argv)
    return(args)

def input(args, *data):

    # if data :
    #     datain=data[0]

    if args.input_file is not None: 
        # input_file might be sys.stdin (if '-')   
        data = args.input_file.read()
    # stdin should work for < redirection
    # I don't know if works for pipe
    ...
    return(datain)

def output(args, *datain) :
    if datain :
        datain=datain[0]
    # output_file might be stdout
    ....
    return(dataout)

def main(args):
    datain = input(args, [])
    dataout = output(args, datain)
    return dataout

if __name__ == "__main__":
    args = parser()
    main(args)

如果作为脚本调用,它只会运行一次解析器。如果导入,则由导入器脚本来运行此解析器。

parser 可以运行多次,但通常不需要 - 至少在 Namespace 可以传递的情况下不需要。但是每次调用解析器都会打开输入/输出文件。由于以写入模式打开一个文件,可能会导致重叠打开。

解析器可能会被测试:

args = parser(['-i', 'inputfile.py', ....]

另一个脚本可以做

from template import parser, input, output
def main(args):
    ... input
    # do its own thing
    ... output
# etc

【讨论】:

感谢您的示例。它只允许运行解析器一次。但是现在,当import template as t 时,我必须调用t.input(args,...)t.output(args,...),而这不是必需的,因为使用模板的所有脚本的参数选项应该相同。 Python 中的导入模块提供了导入器可以使用的函数和类。您的模板模型不合适。如果脚本需要额外的控件怎么办?无论如何,如果多次调用解析器,我建议摆脱FileTypes。仅根据需要自行打开和/或创建文件,最好在with 上下文中。 input 函数不应创建 output 文件。 output 函数不应打开它不使用的input 文件。如果您提供它无法识别的值,解析器不应阻塞。 感谢您的有用建议。我会更新代码

以上是关于用于命令行脚本和导入模块的 Python“模板”模块的主要内容,如果未能解决你的问题,请参考以下文章

从命令行运行 python 要求我导入模块

在命令行中启动脚本时导入模块

如何根据命令行输入包含py文件?

python—模块optparse的用法

python——内建模块optparse的用法

Python3之命令行参数处理