如何掌握Python代码生成器?

开课吧开课吧锤锤2021-04-01 16:16

    介绍

    程序make_python_prog.py是一个代码生成器,它可以使用Python2.7的'argparse'模块生成解析命令行参数的Python程序。

    程序的目的是为了节省输入。它的目标不是处理'argparse'模块的所有功能,而是生成可以运行的代码,并且,如果需要的话,可以很容易地进行修改,以利用更高级的argparse功能;当然,也可以修改程序来做任何程序要做的事情。

    使make_python_prog.py的命令行输入格式既简单又容易记忆是一个目标。因此,额外的argparse特性,如参数组,和'选择'特性,以及其他argparse特性没有被添加,因为这些会使make_python_prog.py程序的命令行输入复杂化。修改生成的代码,添加这些高级的argparse特性会比较容易。

py

    程序make_python_prog.py在Python2.6或2.7版本下运行,生成的程序可以在Python2.7版本下运行。如果用户将Python2.7的发布文件argparse.py复制到生成的程序文件夹中,生成的程序将运行在Python2.6版本下,甚至可能是更早的版本。

    程序make_python_prog.py和生成的代码都符合Python编码标准PEP8。Python很特别,因为没有特殊的关键字或括号来定义作用域,它完全由缩进定义。这使得它很容易快速地写出代码,但是whitespace就成了一个弊端,因为如果用whitespace把程序结构打乱,程序结构就看不清楚了。

    程序make_python_prog.py仍然在最新的代码投递中。我增加了另外两个程序,make_python_2_prog.py和make_python_3_prog.py。这些分别为Python2.5版本之前的版本,和Python3版本写Python代码。

    在Python2.5之前,string.format方法是不存在的,所以程序make_python_2_prog.py使用旧的方式在字符串中插入字符串。只要为Python2.7之前的Python版本提供一个修改后的文件argparse2.py,它内置了argparse.py,那么make_python_2_prog.py就可以写出在Python2.x上运行的代码,至少可以追溯到Python2.2(我想--我没有测试过那么远)。在字符串中插入字符串的老方法可以在所有后来的Python版本上工作(我想甚至是3.x版本?

    不幸的是,make_python_2_prog.py程序为早期版本的Python生成的代码不能运行模块argparse.py中的代码。下面的说明是如何修改argparse.py,使其能用于该程序。

    程序make_python_3_prog.py是针对Python3.0版本和更高版本的Python。第3版继续支持string.format方法,并有了新的输入方式。旧的2版本的驶入已经不能用了。那么在哪里。

print "Hello" 

    适用于Python2.x。

    Python3.x的print需要括号,因为print现在是一个内置函数。

print("Hello")

    程序make_python_3_prog.py只能运行在Python的3.0后版本上,生成的代码只能运行在3.0及以后版本的Python上。我根本没有测试过这个问题(还没有)!我只是检查了一下代码。我只是检查了一下代码。如果有人有问题,请在下面的留言区报告。

    我仍然在运行Python2.7,我使用程序make_python_prog.py,它支持string.formatmethod。在我使用最新版本的Python之前,这将是我的首选程序。我为那些还在运行旧版本的人和那些已经到了最新版本的人提供了其他的代码生成器。

    顺便说一下,我认识的大多数写Python的人都还在使用Python2.6或Python2.7。Python3.x在语法上更好,功能也更完善,尤其是内置Unicode支持,已经有一段时间的发展了。但是,据我所知,有很多库,特别是第三方库,仍然不能与Python3.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指定符可以按任意顺序排列。

    关于变量名

    变量名称必须始终是每个参数的第一个项目。变量名只能包含英文字母或下划线字符。只有当参数类型是字符串时,变量名的initial_value才应该被双引号包围,即使如此,只有当initial_value字符串包含空格时才需要双引号。

    布尔参数的有效默认值只有False和True。

    关于参数类型

    parameter_type指定器可以是以下字符之一。如果没有为这些类型指定初始值,则使用指定的默认初始值。

    -s-一个字符串参数,默认为空字符串或''。

    -i-一个整数参数,默认为0。

    -f-一个浮点参数,默认值为0.0。

    -b-一个布尔参数,默认为False。

    如果没有指定参数类型,那么参数类型默认为's',表示一个字符串参数。

    关于参数_count_token

    可选的counttoken控制指定参数类型的参数数量。如果数量多于一个,那么指定的变量名称将是一个python列表。最后一个可选的count参数在参数解析器代码中被用作'nargs'。nargs参数通常会是以下之一。

    -*-接受0个或更多的参数类型。

    -+-接受1个以上的参数类型。

    -•?-参数是可选的。

    -正整数]-接受指定的参数数,例如:2。

    如果没有指定parameter_count_token,那么在运行时,在命令行上只为该参数输入一个值。如果parameter_count_token表示有多个值,那么variable_namewill将标识一个Python列表实例,每输入一个值都会被解析代码添加到列表中。

    关于参数_switch

    最初的破折号字符表示参数_开关。单一的破折号字符表示一个短的,或单一字符的开关名称。两个初始破折号字符表示一个长名开关。

    短名开关和长名开关都可以指定。

    '-h'和'--help'开关是自动实现的,不应该被指定为开关参数。使用这些帮助开关中的任何一个都会导致在生成的程序开始时打印__doc__字符串。

    关于布尔参数的附加信息。

    一个参数类型为'b'的布尔参数通常被用作一个可选的开关参数。在生成的程序中使用布尔参数的开关会导致布尔参数的变量名被设置为与初始值相反的值,对于布尔参数的默认值来说,会将变量从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'开关的情况下才为'真'。

python make_python_prog.py foo file_name='foo.txt',?

    该命令行中的?使file_name参数成为一个可选参数。如果没有指定参数,那么变量'file_name'将被设置为'foo.txt'。

    程序结构

    靠近程序结尾的'main'函数开始处理参数。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程序的开始和程序导入的boilerplate,包括从模块argparse导入ArgumentParser类。

    接下来,'write_primary_main_function'函数写一个函数,它接受一个以逗号分隔的参数列表,并写代码打印每个参数名和值。

    最后,write_program_end函数写出生成程序的'main'函数,其中包含参数解析代码。write_program_end函数是最复杂的函数。它产生了pythonargparse.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

    然后通过write_program_end函数创建变量'name'、'age'、'company'、'height'和'is_member'。在生成的代码中,产生的'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程序生成的程序,适用于Python2.4(2.5?)之前的早期版本,需要修改argparse.py文件。下面是修改该文件的步骤,使其能在早期版本的Python上工作。

    1.下载文件argparse.py。将其改名为argparse2.py。

    2.将argparse2.py复制到生成的程序文件夹中。

    3.当你尝试运行程序时,你会在文件argparse2.py中得到两个错误,这将很容易修复。

    具体来说,修改文件argparse.py中的1131行。

except IOError as e:

    到:

except IOError, e:

    并将第1595行改为:

default_prefix = '-' if '-' in prefix_chars else prefix_chars[0]

    到:

default_prefix = '-'
if '-' not in prefix_chars: 
    default_prefix = prefix_chars[0]

    将新文件argparse2.py复制到包含生成程序的文件夹中,然后程序就会运行。

    兴趣点

    即使你不想使用argparse模块来解析参数,也许是因为你运行的Python早期版本不包含argparse模块,而且你也不想查找和下载argparse.py文件,这个程序仍然可以节省输入,因为它编写了一个好的Python程序所必需的大部分锅炉模板。

    程序make_python_prog.py和生成的代码都符合PythonPEP8的要求。Python很特别,因为没有特殊的关键字或括号来定义作用域,它完全是一种

    由缩进定义。这使得它很容易快速地编写代码,但是留白就成了一个弊端,因为如果用留白打断了程序文本,程序结构就看不清楚了。更多Python教程尽在开课吧Python教程频道

有用
分享
全部评论快来秀出你的观点
登录 后可发表观点…
发表
暂无评论,快来抢沙发!
零基础轻松入门Python