用 Python 编写的 Python 代码生成器






4.90/5 (15投票s)
代码生成器编写参数解析代码。
简介
程序 make_python_prog.py 是一个代码生成器,它生成使用 Python 2.7 的 'argparse' 模块解析命令行参数的 Python 程序。
该程序的目的是节省打字时间。它的目标不是处理 'argparse' 模块的所有功能,而是生成可以运行的代码,并且如果需要,可以轻松修改以利用更高级的 argparse 功能;当然,还可以修改程序以实现程序预期的任何功能。
它的目标是使 make_python_prog.py 的命令行输入格式既简单又易于记忆。因此,不会添加其他 argparse 功能,例如参数组、'choices' 功能和其他 argparse 功能,因为这些功能会使 make_python_prog.py 程序的命令行输入复杂化。修改生成的代码以添加这些高级 argparse 功能会更容易。
程序 make_python_prog.py 在 Python 2.6 或 2.7 版本下运行,并生成在 Python 2.7 版本下运行的程序。如果用户将 Python 2.7 分发文件 argparse.py 复制到生成的程序文件夹中,则生成的程序将在 Python 2.6 版本下运行,甚至可能在更早的版本下运行。
程序 make_python_prog.py 和生成的代码都符合 Python 编码标准 PEP8。Python 不同寻常,因为没有特殊的关键字或括号来定义作用域,它完全由缩进定义。这使得快速编写代码变得容易,但空格成为一个缺点,因为如果程序被空格打断,程序结构就看不清楚了。
更新:2013 年 11 月 10 日
程序 make_python_prog.py 仍在最新的代码发布中。我添加了另外两个程序,make_python_2_prog.py 和 make_python_3_prog.py。它们分别编写适用于 Python 2.5 版本之前和 Python 3 版本的 Python 代码。
在 Python 2.5 之前,string.format
方法不存在,因此程序 make_python_2_prog.py 使用旧方法将字符串插入字符串中。只要为 Python 2.7 之前的 Python 版本提供了修改后的文件 argparse2.py(其中内置了 argparse.py),那么 make_python_2_prog.py 将编写可在 Python 2.x 上运行的代码,至少可追溯到 Python 2.2(我认为 - 我没有测试那么远)。旧的字符串插入字符串方法适用于所有后续版本的 Python(我认为甚至包括 3.x 版本?)。
不幸的是,make_python_2_prog.py 程序为早期版本的 Python 生成的代码无法运行模块 argparse.py 中的代码。下面有关于如何修改 argparse.py 以与该程序一起使用的说明。
程序 make_python_3_prog.py 适用于 Python 3.0 及更高版本的 Python。版本 3 继续支持 string.format
方法,并有一种新的打印方式。旧的版本 2 打印方式不再有效。因此,在:
print "Hello"
适用于 Python 2.x 的情况下,
Python 3.x 的 print 需要括号,因为 print
现在是一个内置函数。
print("Hello")
程序 make_python_3_prog.py 只能在 3.0 之后的 Python 版本上运行,并且生成的代码也只能在 3.0 及更高版本的 Python 上运行。我根本没有测试过这个(还没有)!我只是检查了代码。如果有人遇到问题,请在下面的消息部分报告。
我仍在运行 Python 2.7,我使用支持 string.format
方法的程序 make_python_prog.py。在我升级到最新版本的 Python 之前,那将是我的首选程序。我为那些仍在运行旧版本和那些已升级到最新版本的人提供了其他代码生成器。
顺便说一下,我认识的大多数编写 Python 的人仍然在使用 Python 2.6 或 Python 2.7。Python 3.x 在语法上更好,并且具有更好的功能,特别是内置的 Unicode 支持,它已经获得了一些进展。然而,据我所知,许多库,特别是第三方库,仍然不适用于 Python 3.x。
使用程序 make_python_prog.py
此程序生成解析命令行参数并显示解析值的 python 程序。此程序的目的是节省打字时间,并允许快速创建可根据预期目的修改的 python 程序。
传递给此程序的参数指定程序名称和生成的程序参数的创建,包括参数名称(也称为变量名称)、参数类型、参数可以输入的次数,以及参数是否可选,以及参数是通过指定命令行开关输入或控制的。
将创建一个与命令行上传递的程序名称的基本名称相同的文件夹,并在该文件夹中创建生成的代码。
用法
请参阅下面进一步的命令行示例。
命令行格式
python make_python_prog.py <program name> <parameter text>
程序命令行的正式规范采用以下形式
Below <x> indicates x is required and [y] indicates that y is optional
python make_python_prog.py <program_name> [parameter 1] [parameter 2} ... [parameter N]
每个参数都是一个逗号分隔的列表,形式为
<variable_name[="initial_value"]>[,parameter_type][,parameter_count_token][,parameter_switch][,parameter_switch]
parameter_type
、parameter_count_token
和任何 parameter_switch
规范可以以任何顺序出现。
关于 variable_name
变量名必须始终是为每个参数指定的第一个项。变量名只能包含英文字母或下划线字符。如果参数类型是字符串,则变量名的 initial_value
应仅用双引号字符括起来,即使如此,双引号也仅在 initial_value
字符串包含空格时才必要。
布尔参数的唯一有效默认值为 False 和 True。
关于 parameter_type
parameter_type
规范可以是以下字符之一。如果未为每种类型指定 initial_value
,则使用指示的初始值默认值。
- s - 字符串参数,默认为空字符串或 ''。
- i - 整型参数,默认为 0。
- f - 浮点参数,默认为 0.0。
- b - 布尔参数,默认为 False。
如果未指定 parameter_type
,则 parameter_type
默认为 's',表示字符串参数。
关于 parameter_count_token
可选的计数标记控制指定参数类型接受的参数数量。如果数量大于一,则由给定名称指定的变量将是 Python 列表。此最终可选计数参数在参数解析器代码中用作 'nargs
'。nargs
参数通常是以下之一
- * - 接受 0 个或更多指定类型的参数。
- + - 接受 1 个或更多指定类型的参数。
- ? - 参数是可选的。
- [一个正整数] - 接受指定数量的参数,例如 2
如果未指定 parameter_count_token
,则在运行时,命令行上只输入该参数的一个值。如果 parameter_count_token
指示多个值,则 variable_name
将标识一个 Python 列表实例,并且每个输入的值都将由解析代码添加到列表中。
关于 parameter_switch
初始的破折号字符表示 parameter_switch
。单个破折号字符表示短名称或单字符开关名称。两个初始破折号字符指定长名称开关。
可以同时指定短名称开关和长名称开关。
'-h' 和 '--help' 开关是自动实现的,不应指定为开关参数。使用这些帮助开关中的任何一个都会导致打印生成的程序开头的 __doc__
字符串。
关于布尔参数的附加信息。
布尔参数,其 parameter_type
为 'b',通常用作可选开关参数。在生成的程序中使用布尔参数的开关会导致布尔参数的变量名被设置为 initial_value
的反面,对于布尔参数的默认值,这将变量从 False 更改为 True。
命令行示例
python make_python_prog.py foo alpha,i beta,f,+ file_name gamma,-g,--gm,b
此命令行生成一个名为 'foo.py' 的程序,该程序接受一个名为 'alpha' 的整型参数,一个或多个浮点参数(位于名为 'beta' 的列表中),然后是一个名为 file_name
的字符串参数,最后是一个名为 'gamma' 的可选布尔值参数,仅当指定 '-g' 开关或 '--gm' 开关时才为 'True'。
python make_python_prog.py foo file_name='foo.txt',?
此命令行中的 ? 使 file_name
参数成为可选参数。如果未指定任何参数,则变量 'file_name
' 设置为 'foo.txt'。
程序结构
程序末尾附近的 'main
' 函数开始参数处理。main 在程序的最后被调用。
对于每个命令行参数,主函数使用一个 ArgInfo
类实例来存储变量名和变量属性,包括类型、默认(初始)值以及与变量相关的其他数据。解析逗号分隔的参数数据并存储在 ArgInfo
类实例中后,该实例存储在一个列表中,由变量名 'param_info_list
' 指定。
ArgInfo
类和方法可以在这里看到。self.name
参数存储变量名。其余部分应该很明显。
class ArgInfo:
""" Instances of the ArgInfo class store argument properties. """
name_regex = re.compile('[A-Za-z_]*[0-9A-Za-z_]')
def __init__(self):
self.name = ''
self.default_value = ''
self.data_type = ''
self.count_token = ''
self.switch_name_list = []
def get_switch_name_list(self):
return self.switch_name_list
def set_switch_name_list(self, switch_name_list):
# Validate each switch name in the switch name list.
for switch_name in switch_name_list:
# Strip off up to two leading hyphen characters.
if switch_name.startswith('-'):
switch_name = switch_name[1:]
if switch_name.startswith('-'):
switch_name = switch_name[1:]
self.validate_name(switch_name)
self.switch_name_list = switch_name_list
def get_name(self):
return self.name
def set_name(self, name):
self.validate_name(name)
self.name = name
def get_default_value(self):
return self.default_value
def set_default_value(self, default_value):
self.default_value = default_value
def get_type(self):
return self.data_type
def set_type(self, type):
self.data_type = type
def get_count_token(self):
return self.count_token
def set_count_token(self, count):
self.count_token = count
def validate_name(self, name):
result = ArgInfo.name_regex.match(name)
if not result:
raise ValueError('Illegal name: {0}'.format(name))
创建用于存储程序文件的文件夹后,主函数打开新生成的程序文件并调用 write_program
函数,传递文件规范符、base_program_name
(对于 "foobar.py" 将是 "foobar
")以及 ArgInfo
实例列表。
# Open the program file and write the program.
with open(program_name, 'w') as outfile:
write_program(outfile, base_program_name, param_info_list)
write_program
函数非常简单。首先,调用 write_program_header
函数。该函数编写每个 Python 程序开头的样板代码和程序导入,包括从 argparse 模块导入 ArgumentParser
类。
接下来,'write_primary_main_function
' 函数编写一个接受逗号分隔参数列表的函数,并编写代码以打印每个参数名称和值。
最后,write_program_end
函数为生成的程序编写 'main
' 函数,其中包含参数解析代码。write_program_end
函数是最复杂的函数。它生成 python argparse.ArgumentParser
实例解析命令行参数所需的代码行。对于 param_info_list
中的每个 ArgInfo
实例,都会为 argparse.ArgumentParser
实例编写单独的行。
def write_program(outfile, base_program_name, param_info_list):
""" Main function to write the python program. """
# Extract just the argument names to a list.
param_list = []
for param_info in param_info_list:
param_list.append(param_info.get_name())
write_program_header(outfile, base_program_name, param_list)
write_primary_main_function(outfile, base_program_name, param_list)
write_program_end(outfile, base_program_name, param_list, param_info_list)
如果程序命令行是
python make_python_prog.py Enroll name age,i company="The Company",? height,f,-h,--height is_member,b,-m,--member
然后变量 'name
'、'age
'、'company
'、'height
' 和 'is_member
' 由 write_program_end
函数创建。生成的代码中的 'main' 函数是
def main(argv=None):
# Initialize the command line parser.
parser = ArgumentParser(description='TODO: Text to display before the argument help.',
epilog='Copyright (c) 2013 TODO: your-name-here.',
add_help=True,
argument_default=None, # Global argument default
usage=__doc__)
parser.add_argument(action='store', dest='name', help='TODO:')
parser.add_argument(action='store', dest='age', type=int, help='TODO:')
parser.add_argument(action='store', dest='company', default=The Company, nargs='?', help='TODO:')
parser.add_argument('-h', '--height', action='store', dest='height', type=float, default=0.0, help='TODO:')
parser.add_argument('-m', '--member', action='store_true', dest='is_member', default=False, help='TODO:')
# Parse the command line.
arguments = parser.parse_args(args=argv)
name = arguments.name
age = arguments.age
company = arguments.company
height = arguments.height
is_member = arguments.is_member
status = 0
try:
Enroll_main(name, age, company, height, is_member)
except ValueError as value_error:
print value_error
status = -1
except EnvironmentError as environment_error:
print environment_error
status = -1
return status
此代码将按原样运行,但用户会想要搜索字符串 'TODO:' 并将其替换为有意义的文本。例如,应为添加到解析器的每一行代码添加帮助文本。
parser.add_argument(action='store', dest='name', help='TODO:')
程序 make_python_prog.py 源代码
#!/usr/bin/env python
#=======================================================================
# Copyright (C) 2013 William Hallahan
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#=======================================================================
"""
This program generates python programs that parse command-line
arguments and displays the parsed values. The purpose of this
program is to save typing and allowing the rapid creation of
python programs that can be modified for some intended purpose.
Arguments passed to this program specify the program name and the
creation of generated program arguments, including the parameter
names, also known as the variable names, parameter types, number
of times a parameter may be entered, and whether a parameter is
optional, and whether the parameter is entered or controlled by
specifying a command-line switch.
Usage:
See further below for example command lines.
Command line format:
python make_python_prog.py <program name> <parameter text>
The formal specification for the program command line is in the form of:
Below <x> indicates x is required and [y] indicates that y is optional.
python make_python_prog.py <program_name> [parameter 1] [parameter 2} ... [parameter N]
Each parameter is a comma delimited list of the form:
<variable_name[="initial_value"]>[,parameter_type][,parameter_count_token][,parameter_switch][,parameter_switch]
The parameter_type, parameter_count_token, and any parameter_switch specifier
can be in any order.
About variable_name
The variable name must always be the first item specified for each
parameter. The variable name may only contain letters of the
English alphabet or the underscore character. The initial_value for
a variable name should only be surrounded by double quote characters
if the parameter type is a string, and even then the double quotes
are only necessary if the inital_value string contains whitespace.
The only valid default values for a Boolean parameter are False and True.
About parameter_type
The parameter_type specifier can be one of the following characters.
If no initial_value is specified for each of these types, the
indicated initital value default is used.
s - A string parameter, defaults to the empty string, or ''.
i - An integer parameter, defaults to 0.
f - A floating point parameter, defaults to 0.0.
b - A Boolean parameter, defaults to False.
If the parameter_type is not specified, then the parameter_type defaults
to 's', which indicates a string argument.
About parameter_count_token
The optional count token controls the number of arguments that
are accepted for specified argument type. If the number is more
than one, then the variable specified by the given name will be a
python list. This final optional count parameter is used as 'nargs'
in the argument parser code. The nargs parameter would typically
be one of the following:
* - Accept 0 or more of the argument type.
+ - Accept 1 or more of the argument type.
? - The argument is optional.
[A positive integer] - Accept the specified number
of arguments, e.g. 2
If the parameter_count_token is not specified, then at runtime, only
one value is entered on the command line for that parameter. if the
parameter_count_token indicates multiple values, then variable_name
will identify a Python list instance, and each value entered will
be added to the list by the parsing code.
About parameter_switch
An initial dash character indicate a parameter_switch. A single
dash character indicates a short, or single character, switch name.
Two initial dash characters specify a long-name switch.
Both a short-name switch and a long-name switch can be specified.
The '-h' and '--help' switches are implemented automatically and
should not be specified as switch parameters. Using either one of
these help switches results in the __doc__ string at the start of
the generated program being printed.
Additional information regarding Boolean parameters.
A Boolean parameter, with parameter_type 'b', is typically used as
an optional switch parameter. Using the switch for a Boolean
parameter in the generated program results in the variable name for
the Boolean argument being set to the opposite of the initial_value,
which for the default for a Boolean parameter, changes the variable
from False to True.
Example command lines:
python make_python_prog.py foo alpha,i beta,f,+ file_name gamma,-g,--gm,b
This command line generates a program named 'foo.py' that takes
an integer parameter named 'alpha', and one or more floating point
parameters that are in a list named 'beta', then a string parameter
named file_name, and finally an optional parameter named 'gamma'
that is a Boolean value that is only 'True' if either the '-g'
switch or the '--gm' switch are specified.
python make_python_prog.py foo file_name='foo.txt',?
The ? in this command line makes the file_name parameter an optional
parameter. If no argument is specified, then variable 'file_name'
is set to 'foo.txt'.
"""
import sys
import os
import re
import time
import keyword
class ArgInfo:
""" Instances of the ArgInfo class store argument properties. """
name_regex = re.compile('[A-Za-z_]*[0-9A-Za-z_]')
def __init__(self):
self.name = ''
self.default_value = ''
self.data_type = ''
self.count_token = ''
self.switch_name_list = []
def get_switch_name_list(self):
return self.switch_name_list
def set_switch_name_list(self, switch_name_list):
# Validate each switch name in the switch name list.
for switch_name in switch_name_list:
# Strip off up to two leading hyphen characters.
if switch_name.startswith('-'):
switch_name = switch_name[1:]
if switch_name.startswith('-'):
switch_name = switch_name[1:]
self.validate_name(switch_name)
self.switch_name_list = switch_name_list
def get_name(self):
return self.name
def set_name(self, name):
self.validate_name(name)
self.name = name
def get_default_value(self):
return self.default_value
def set_default_value(self, default_value):
self.default_value = default_value
def get_type(self):
return self.data_type
def set_type(self, type):
self.data_type = type
def get_count_token(self):
return self.count_token
def set_count_token(self, count):
self.count_token = count
def validate_name(self, name):
result = ArgInfo.name_regex.match(name)
if not result:
raise ValueError('Illegal name: {0}'.format(name))
def validate_no_duplicate_switches(arg_info_list):
""" Throw an exception if there are any duplicate switch names. """
switch_list = []
for arg_info in arg_info_list:
for switch_name in arg_info.get_switch_name_list():
if switch_name in switch_list:
raise ValueError('Duplicate switch name {0}.'.format(switch_name))
switch_list.append(switch_name)
def is_integer(s):
""" Returns True if and only if the passed string specifies
an integer value.
"""
try:
x = int(s)
is_int = True
except ValueError:
is_int = False
return is_int
def is_floating_point(s):
""" Returns True if and only if the passed string specifies
a floating point value.
"""
try:
x = float(s)
is_float = True
except ValueError:
is_float = False
return is_float
def write_program_header(outfile, base_program_name, param_list):
""" Writes a program header in the following form:
#!/usr/bin/env python
<three double quotes>
python template.py --switch1 <value1> --switch2 <value2>
<three double quotes>
import sys
from argparse import ArgumentParser
"""
outfile.write('#!/usr/bin/env python\n')
outfile.write('"""\n')
outfile.write(' python {0}.py\n\n'.format(base_program_name))
outfile.write(' TODO: Add usage information here.\n')
outfile.write('"""\n')
outfile.write('import sys\n')
outfile.write('# TODO: Uncomment or add imports here.\n')
outfile.write('#import os\n')
outfile.write('#import re\n')
outfile.write('#import time\n')
outfile.write('#import subprocess\n')
if param_list != None and len(param_list) > 0:
outfile.write('from argparse import ArgumentParser\n')
outfile.write('\n')
return
def get_function_call_string(function_name, param_list):
""" Writes a function call, such as:
'somename_main(input_file_name)'
"""
function_call = '{0}('.format(function_name)
number_of_params = len(param_list)
for i in xrange(0, number_of_params):
function_call = '{0}{1}'.format(function_call, param_list[i])
if i != (number_of_params - 1):
function_call = '{0}, '.format(function_call)
function_call = '{0})'.format(function_call)
return function_call
def write_function_start(outfile, function_name, param_list):
""" Writes a function call, such as:
'def somename_main(input_file_name):'
<three double quotes> TODO: <three double quotes>
"""
if function_name == 'main':
outfile.write('# Start of main program.\n')
# write the function declaration.
function_call = get_function_call_string(function_name, param_list)
function_declaration = 'def {0}:\n'.format(function_call)
outfile.write(function_declaration)
if function_name != 'main':
outfile.write(' """ TODO: Add docstring here. """\n')
return
def write_primary_main_function(outfile, base_program_name, param_list):
""" Writes a function with the following form:
<three double quotes> <myprogram>_main
<three double quotes>
def <myprogram>_main(<argument_list>):
# TODO: Add code here.
return 0
"""
function_name = '{0}_main'.format(base_program_name)
write_function_start(outfile, function_name, param_list)
outfile.write(' # TODO: Add or delete code here.\n')
outfile.write(' # Dump all passed argument values.\n')
for param in param_list:
outfile.write(" print '{0} = {1}0{2}'.format(repr({3}))\n".format(param, '{', '}', param))
# End of test code.
outfile.write(' return 0\n\n')
return
def get_year():
""" Returns a string that contains the year. """
now = time.ctime()
now_list = now.split()
year = now_list[4]
return year
def get_default_value_for_type(arg_type):
""" Get the argument default value based on the argument type. """
# An empty argument type defaults to type 'string'.
arg_default_value_dict = {'string' : "''", 'boolean' : 'False', 'int' : '0', 'float' : '0.0'}
return arg_default_value_dict[arg_type]
def write_argument_parsing_code(outfile, param_info_list):
""" Write argument parsing code. The form of the code
varies depending on the passed param_info_list, but
will be similar to:
# Initialize the command line parser.
parser = ArgumentParser(description='TODO: Text to display before the argument help.',
epilog='TODO: Text to display after the argument help.',
add_help=True,
argument_default=None, # Global argument default
usage=__doc__)
parser.add_argument('-s', '--switch1', action='store', dest='value1', required=True,
help='The something1')
parser.add_argument('-t', '--switch2', action='store', dest='value2',
help='The something2')
parser.add_argument('-b', '--optswitchbool', action='store_true', dest='optboolean1', default=False,
help='Do something')
# Parse the command line.
args = parser.parse_args()
value1 = args.value1
value2 = args.value2
optboolean1 = args.optboolean1
"""
if len(param_info_list) > 0:
outfile.write(' # Initialize the command line parser.\n')
outfile.write(" parser = ArgumentParser(description='TODO: Text to display before the argument help.',\n")
outfile.write(" epilog='Copyright (c) {0} TODO: your-name-here - All Rights Reserved.',\n".format(get_year()))
outfile.write(" add_help=True,\n")
outfile.write(" argument_default=None, # Global argument default\n")
outfile.write(" usage=__doc__)\n")
# For each parameter, write code to add an argument variable to the argument parser.
for param_info in param_info_list:
# Get the argument data.
switch_name_list = param_info.get_switch_name_list()
arg_name = param_info.get_name()
arg_default_value = param_info.get_default_value()
arg_type = param_info.get_type()
arg_count = param_info.get_count_token()
# Create a string to add the argument to the parser.
argument_string = ' parser.add_argument('
if len(switch_name_list) > 0:
switches_string = repr(switch_name_list)
switches_string = switches_string.replace('[', '')
switches_string = switches_string.replace(']', '')
argument_string = "{0}{1},".format(argument_string, switches_string)
if not argument_string.endswith('('):
argument_string = "{0} ".format(argument_string)
if arg_type == 'boolean':
if not arg_default_value or arg_default_value == 'False':
argument_string = "{0}action='store_true',".format(argument_string)
elif arg_default_value == 'True':
argument_string = "{0}action='store_false',".format(argument_string)
else:
argument_string = "{0}action='store',".format(argument_string)
# Add text to set the argument name.
argument_string = "{0} dest='{1}',".format(argument_string, arg_name)
if arg_type == 'int':
argument_string = "{0} type=int,".format(argument_string)
elif arg_type == 'float':
argument_string = "{0} type=float,".format(argument_string)
elif arg_type != 'boolean':
# The default type is 'string'.
arg_type = 'string'
# If the parameter is not a required parameter, then set a default value.
if len(switch_name_list) > 1 or arg_count == '?':
# If there is no default value then specify a default value based on the type
# of the argument.
if not arg_default_value:
arg_default_value = get_default_value_for_type(arg_type)
argument_string = '{0} default={1},'.format(argument_string, arg_default_value)
elif arg_default_value:
print "'{0}' is a required parameter. Default argument value '{1}' ignored".format(arg_name, arg_default_value)
print "Use parameter_count_token '?' to change to a non-required parameter."
# Set the optional argument count.
if arg_count and arg_count in '*+?':
argument_string = "{0} nargs='{1}',".format(argument_string, arg_count)
elif is_integer(arg_count):
argument_string = "{0} nargs={1},".format(argument_string, int(arg_count))
# Add text to set the argument help string.
argument_string = "{0} help='TODO:')\n".format(argument_string)
# Write the line of code that adds the argument to the parser.
outfile.write(argument_string)
# Write code the parse the arguments
outfile.write(' # Parse the command line.\n')
outfile.write(' arguments = parser.parse_args(args=argv)\n')
# Write code to extract each parameter value from the argument parser.
for param_info in param_info_list:
arg_name = param_info.get_name()
outfile.write(" {0} = arguments.{1}\n".format(arg_name, arg_name))
return
def write_program_end(outfile, base_program_name, param_list, param_info_list):
""" Writes code with the following form:
<three double quotes> main
<three double quotes>
def main():
<Argument parsing code here>
[primary_main-function call here]
return 0
if __name__ == "__main__":
sys.exit(main())
"""
write_function_start(outfile, 'main', ['argv=None'])
if param_list != None and len(param_list) > 0:
write_argument_parsing_code(outfile, param_info_list)
function_name = '{0}_main'.format(base_program_name)
function_call = get_function_call_string(function_name, param_list)
outfile.write(' status = 0\n')
outfile.write(' try:\n')
function_call = ' {0}\n'.format(function_call)
outfile.write(function_call)
outfile.write(' except ValueError as value_error:\n')
outfile.write(' print value_error\n')
outfile.write(' status = -1\n')
outfile.write(' except EnvironmentError as environment_error:\n')
outfile.write(' print environment_error\n')
outfile.write(' status = -1\n')
outfile.write(' return status\n\n')
outfile.write('if __name__ == "__main__":\n')
outfile.write(' sys.exit(main())\n')
return
def write_program(outfile, base_program_name, param_info_list):
""" Main function to write the python program. """
# Extract just the argument names to a list.
param_list = []
for param_info in param_info_list:
param_list.append(param_info.get_name())
write_program_header(outfile, base_program_name, param_list)
write_execute_function(outfile, base_program_name, param_list)
write_program_end(outfile, base_program_name, param_list, param_info_list)
def file_exists(file_name):
""" Return True if and only if the file exists. """
exists = True
try:
with open(file_name) as f:
pass
except IOError as io_error:
exists = False
return exists
def validate_arg_default_value(arg_type, arg_default_value, argument):
""" Validate the argument default value based on the argument type. """
# An empty argument type defaults to type 'string'.
if not arg_type or arg_type == 'string':
if arg_default_value:
# String default values must begin and end with single quote characters.
if not arg_default_value.startswith("'") or not arg_default_value.endswith("'"):
arg_default_value = repr(arg_default_value)
# If the parameter is a boolean parameter, then the default
# value must be either the string value 'False' or 'True'.
elif arg_type == 'boolean':
if arg_default_value:
if arg_default_value != 'False' and arg_default_value != 'True':
raise ValueError("Boolean default value {0} in {1} must be either 'False' or 'True'".format(arg_default_value, argument))
elif arg_type == 'int':
if arg_default_value and not is_integer(arg_default_value):
raise ValueError('Integer default value {0} in {1} is not a valid number.'.format(arg_default_value, argument))
else: # arg_type == 'float':
if arg_default_value and not is_floating_point(arg_default_value):
raise ValueError('Floating point default value {0} in {1} is not a valid floating point number.'.format(arg_default_value, argument))
return
def validate_variable_name(arg_name, unique_name_list):
""" Validate the variable name. """
# Ensure the variable name is not a python keyword.
if keyword.iskeyword(arg_name):
raise ValueError('Variable name "{0}" is a python keyword.'.format(arg_name))
# Ensure the variable name is unique.
if arg_name in unique_name_list:
raise ValueError('Variable name "{0}" was already specified.'.format(arg_name))
unique_name_list.append(arg_name)
def create_folder_under_current_directory(folder_name):
current_path = os.getcwd()
new_path = os.path.join(current_path, folder_name)
if not os.path.exists(folder_name):
os.makedirs(folder_name)
return new_path
# Start of main program.
def main(argv=None):
if argv == None:
argv = sys.argv
# Set the default return value to indicate success.
status = 0
# There must be at least one argument that is the program name.
if len(argv) < 2:
print 'Program: make_python_prog.py\nUse -h or --help for more information.'
return status
# Get the program name or "-h" or "--help".
base_program_name = argv[1]
if base_program_name == '-h' or base_program_name == '--help':
print __doc__
return status
program_name = base_program_name
# Make sure the base program name does not have the '.py' extension.
if base_program_name.endswith('.py'):
base_program_name = base_program_name[:-3]
# Make sure the base program name is a valid program name.
param_info = ArgInfo()
try:
param_info.validate_name(base_program_name)
# Add the file extension '.py'.
program_name = '{0}.py'.format(base_program_name)
# Don't allow programs to be created with the same name as this program.
lower_case_program_name = program_name.lower()
if lower_case_program_name == 'make_python_prog.py':
raise ValueError('The generated program name cannot be the same name as this program.')
# The argument list to this program can start with an optional argument
# switch, followed by the argument name followed by the type of the argument,
# each separated by a comma character. The argument type must be one of the
# following: 's' for string, 'b' for boolean, 'i' for int, or 'f' for float.
arg_type_dict = {'s' : 'string', 'b' : 'boolean', 'i' : 'int', 'f' : 'float'}
unique_name_list = []
param_info_list = []
for i in xrange(2, len(argv)):
# Get the current argument string.
argument = argv[i].strip()
arg_item_list = argument.split(',')
# Create a new ArgInfo instance to store the argument settings.
param_info = ArgInfo()
# Get the argument name and remove it from the list.
arg_name = arg_item_list.pop(0)
# Check for a default value for the argument name.
arg_default_value = ''
arg_name_list = arg_name.split('=')
if len(arg_name_list) > 1:
arg_name = arg_name_list[0]
arg_default_value = arg_name_list[1]
# Declare optional argument setting variables.
arg_switch_list = []
arg_count_token = ''
arg_type = ''
# Loop and parse any optional comma-delimited parameters.
for arg_item in arg_item_list:
# Does the parameter specify an argument switch?
if arg_item.startswith('-'):
arg_switch_list.append(arg_item)
continue
# Does the parameter specify an argument count token?
elif (len(arg_item) == 1 and arg_item in '*+?') or is_integer(arg_item):
# Was an argument count token already found?
if arg_count_token:
raise ValueError(
'Argument count token {1} in {0} is a duplicate count token.'.format(arg_item, argument))
arg_count_token = arg_item
continue
# Does the parameter specify an argument type?
elif (len(arg_item) == 1 and arg_item in 'sbif'):
# Was an argument type already found?
if arg_type:
raise ValueError('Argument type {1} in {0} is a duplicate type.'.format(arg_item, argument))
# Look up the argument type token.
arg_type = arg_type_dict.get(arg_item)
continue
# The input is invalid.
raise ValueError('Parameter {0} contains invalid setting {1}.'.format(argument, arg_item))
# Validate the argument default value and the variable name.
validate_arg_default_value(arg_type, arg_default_value, argument)
validate_variable_name(arg_name, unique_name_list)
# Save the argument parameters.
param_info.set_name(arg_name)
param_info.set_default_value(arg_default_value)
param_info.set_switch_name_list(arg_switch_list)
param_info.set_count_token(arg_count_token)
param_info.set_type(arg_type)
# Add the argument info to the list of arguments.
param_info_list.append(param_info)
validate_no_duplicate_switches(param_info_list)
# If the output file already exists, then prompt the user to overwrite the file.
if file_exists(program_name):
print "File '{0}' already exists. Enter 'y' or 'Y' to overwrite the file. >".format(program_name),
c = raw_input()
if c != 'y' and c != 'Y':
return status
# Create a 'base_program_name' folder.
new_path = create_folder_under_current_directory(base_program_name)
# Change the current working directory to the new folder.
os.chdir(new_path)
# Open the program file and write the program.
with open(program_name, 'w') as outfile:
write_program(outfile, base_program_name, param_info_list)
print 'Created program {0}'.format(program_name)
except EnvironmentError as environment_error:
print environment_error
status = -1
except ValueError as value_error:
print value_error
status = -1
return status
if __name__ == "__main__":
sys.exit(main())
程序 make_python_2_prog.py 的 argparse
由 make_python_2_prog.py 程序生成的程序,适用于 Python 2.4 版本(2.5?)之前的早期版本,需要修改后的 argparse.py 文件。以下是修改文件使其可在早期版本的 Python 上运行的步骤。
- 下载文件 argparse.py。将其重命名为 argparse2.py。
- 将 argparse2.py 复制到生成的程序文件夹。
- 当您尝试运行程序时,您会在文件 argparse2.py 中收到两个易于修复的错误。
具体来说,更改文件 argparse.py 中的第 1131 行
except IOError as e:
to
except IOError, e:
并将第 1595 行从
default_prefix = '-' if '-' in prefix_chars else prefix_chars[0]
to
default_prefix = '-' if '-' not in prefix_chars: default_prefix = prefix_chars[0]
将新文件 argparse2.py 复制到包含生成程序的文件夹中,然后程序将运行。
关注点
即使您不想使用 argparse 模块来解析参数,也许是因为您正在运行不包含 argparse 模块的早期版本的 Python,并且您不想查找和下载文件 argparse.py,此程序仍然可以节省打字时间,因为它编写了良好 Python 程序所需的大部分样板代码。
程序 make_python_prog.py 和生成的代码都符合 Python PEP8。Python 不同寻常,因为没有特殊的关键字或括号来定义作用域,它完全由缩进定义。这使得快速编写代码变得容易,但空格成为一个缺点,因为如果程序文本被空格打断,程序结构就看不清楚了。
历史
- 首次发布。
- 修复了“兴趣点”下关于空白的最后一段。文本以“如果程序被打破”结尾,而不是“如果程序文本被空白打破”。
- 删除了生成代码版权声明中的“保留所有权利”字样。更新了文章中的 zip 文件和代码文本。
- 除了程序 make_python_prog.py,现在还提供了另外两个版本:make_python_2_prog.py 和 make_python_3_prog.py。
- 将“write_execute_function”函数重命名为“write_primary_main_function”。
将生成的函数名从“execute_<somename>”重命名为“<somename>_main”。