Skip to content

11. 应用练习:第 3 版

Image

此新版本引入了两项变更:

  • 税务部门提供的、用于计算税款的必要数据被放置在文件 jSON [admindata.json] 中:

{
    "limites": [9964, 27519, 73779, 156244, 0],
    "coeffR": [0, 0.14, 0.3, 0.41, 0.45],
    "coeffN": [0, 1394.96, 5798, 13913.69, 20163.45],
    "PLAFOND_QF_DEMI_PART": 1551,
    "PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION": 21037,
    "PLAFOND_REVENUS_COUPLE_POUR_REDUCTION": 42074,
    "VALEUR_REDUC_DEMI_PART": 3797,
    "PLAFOND_DECOTE_CELIBATAIRE": 1196,
    "PLAFOND_DECOTE_COUPLE": 1970,
    "PLAFOND_IMPOT_COUPLE_POUR_DECOTE": 2627,
    "PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE": 1595,
    "ABATTEMENT_DIXPOURCENT_MAX": 12502,
    "ABATTEMENT_DIXPOURCENT_MIN": 437
}
  • 税款计算结果也将存储在文件 jSON [résultats.json] 中:

[
  {
    "marié": "oui",
    "enfants": 2,
    "salaire": 55555,
    "impôt": 2814,
    "surcôte": 0,
    "décôte": 0,
    "réduction": 0,
    "taux": 0.14
  },
  {
    "marié": "oui",
    "enfants": 2,
    "salaire": 50000,
    "impôt": 1384,
    "surcôte": 0,
    "décôte": 384,
    "réduction": 347,
    "taux": 0.14
  },

  {
    "marié": "oui",
    "enfants": 3,
    "salaire": 200000,
    "impôt": 42842,
    "surcôte": 17283,
    "décôte": 0,
    "réduction": 0,
    "taux": 0.41
  }
]

11.1. 配置脚本 [config.py]

配置脚本如下:


def configure():
    import os

    # 此脚本文件夹的绝对路径
    script_dir = os.path.dirname(os.path.abspath(__file__))
    # 应用程序的依赖项
    absolute_dependencies = [
        f"{script_dir}/../shared",
    ]
    # 应用程序配置
    config = {
        # 纳税人文件的绝对路径
        "taxpayersFilename"f"{script_dir}/../data/taxpayersdata.txt",
        # 结果文件的绝对路径
        "resultsFilename"f"{script_dir}/../data/résultats.json",
        # 税务管理数据文件的绝对路径
        "admindataFilename"f"{script_dir}/../data/admindata.json"
    }
    # 更新系统路径
    from myutils import set_syspath

    set_syspath(absolute_dependencies)

    # 生成配置
    return config
  • 第 8 行:将文件夹 [shared] 添加到 Python 路径中。该文件夹包含主脚本使用的模块 [impôts_module_02]

11.2. 主脚本 [main.py]

第 3 版的 主脚本如下:


# 配置应用程序
import config

config = config.configure()

# 系统路径已配置 - 可以进行导入
from impôts_module_02 import calcul_impôt, get_admindata, get_taxpayers_data, record_results_in_json_file

# 纳税人文件
taxpayers_filename = config['taxpayersFilename']
# 结果文件
results_filename = config['resultsFilename']
# 税务管理数据文件
admindata_filename = config['admindataFilename']
# 代码
try:

    # 税务机关数据读取
    admindata = get_admindata(admindata_filename)
    # 读取纳税人数据
    taxpayers = get_taxpayers_data(taxpayers_filename)
    # 结果列表
    results = []
    # 计算纳税人应纳税额
    for taxpayer in taxpayers:
        # 税款计算返回一个键值对表
        # ['marié', 'enfants', 'salaire', 'impôt', 'surcôte', 'décôte', 'réduction', 'taux']
        result = calcul_impôt(admindata, taxpayer['marié'], taxpayer['enfants'], taxpayer['salaire'])
        # 词典已添加到结果列表中
        results.append(result)
    # 保存结果
    record_results_in_json_file(results_filename, results)
except BaseException as erreur:
    # 可能出现各种错误:文件不存在、文件内容不正确
    # 显示错误并退出应用程序
    print(f"L'erreur suivante s'est produite : {erreur}]\n")
finally:
    print("Travail terminé...")

  • 第 2-4 行,配置应用程序,特别是其 Python 路径;
  • 第 7 行:导入 [main.py] 中所需的函数;
  • 第9-14行:从配置中获取应用程序使用的文件名;
  • 第 3 版的主脚本与第 1 版和第 2 版相比有三个区别:
  • 第21行:税务部门的数据取自文件jSON和[./data/admindata.json]
  • 第32行:税款计算结果保存在文件jSON和[./data/résultats.json]中;
  • 第7行:第3版的函数位于模块[impots.modules.impôts_module_02]中;

11.3. 模块 [impots.v02.modules.impôts_module_02]

模块 [impots.v02.modules.impôts_module_02] 的结构如下:

Image

  • 该模块中包含版本1所用模块中已有的函数,但存在一个区别。当版本2的模块复用版本1模块中的函数时,会增加一个额外参数:[adminData](第29、51、77、127行)。 该参数代表来自文件 jSON 和 [adminData.json] 的税务数据字典。在版本 1 的模块中,这些数据无需传递给函数,因为它们已在函数中全局定义,因此函数本身已知晓这些数据;

11.4. 读取税务部门的数据

函数 [get_admindata] 如下所示:


# 从税务机关文件中读取数据 jSON
# ----------------------------------------
def get_admindata(admindata_filename: str) -> dict:
    # 读取税务部门的数据
    # 允许抛出可能的异常:文件不存在、jSON 内容不正确
    file = None
    try:
        # 以只读模式打开 jSON 文件
        file = codecs.open(admindata_filename, "r", "utf8")
        # 将内容导入字典
        admin_data = json.load(file)
        # 返回结果
        return admin_data
    finally:
        # 若文件已打开,则关闭
        if file:
            file.close()
  • 第9行:从已读取的jSON文件中获取图像字典;

11.5. 记录结果

函数 [record_results_in_json_file] 如下:


# 将结果写入文件 jSON
# ----------------------------------------
def record_results_in_json_file(results_filename: str, results: list):
    file = None
    try:
        # 打开结果文件
        file = codecs.open(results_filename, "w", "utf8")
        # 批量写入
        json.dump(results, file, ensure_ascii=False)
    finally:
        # 若文件已打开,则关闭该文件
        if file:
            file.close()
  • 第7行:创建一个编码文件UTF-8;
  • 第 9 行:将列表 [results] 写入文件 jSON。字符 UTF-8 未进行转义(ensure_ascii=False);

11.6. 函数修改

某些函数现在会接收一个额外的参数 [admin_data]。这会稍微改变它们的写法。以函数 [calcul_impôt] 为例:


# 计算税款 - 步骤 1
# ----------------------------------------
def calcul_impôt(admin_data: dict, marié: str, enfants: int, salaire: int) -> dict:
    # 已婚:是,否
    # 子女:子女人数
    # 工资:年薪
    # 限额、系数:用于计算税款的数据表
    #
    # 含子女的税款计算
    result1 = calcul_impôt_2(admin_data, marié, enfants, salaire)
    impot1 = result1["impôt"]
    # 不含子女的税款计算
    if enfants != 0:
        result2 = calcul_impôt_2(admin_data, marié, 0, salaire)
        impot2 = result2["impôt"]
        # 家庭收入商数上限的适用
        if enfants < 3:
            # PLAFOND_QF_DEMI_PART 欧元(适用于前两名子女)
            impot2 = impot2 - enfants * admin_data['plafond_qf_demi_part']
        else:
            # PLAFOND_QF_DEMI_PART 欧元(前两名子女),后续子女金额翻倍
            impot2 = impot2 - 2 * admin_data['plafond_qf_demi_part'] - (enfants - 2) * 2 * admin_data[
                'plafond_qf_demi_part']
    else:
        impot2 = impot1
        result2 = result1

    # 取税率最高的那项,并适用相应的税率和附加费
    if impot1 > impot2:
        impot = impot1
        taux = result1["taux"]
        surcôte = result1["surcôte"]
    else:
        surcôte = impot2 - impot1 + result2["surcôte"]
        impot = impot2
        taux = result2["taux"]

    # 计算可能的减免额
    décôte = get_décôte(admin_data, marié, salaire, impot)
    impot -= décôte
    # 计算可能的减税额
    réduction = get_réduction(admin_data, marié, salaire, enfants, impot)
    impot -= réduction
    # 结果
    return {"marié": marié, "enfants": enfants, "salaire": salaire, "impôt": math.floor(impot), "surcôte": surcôte,
            "décôte": décôte, "réduction": réduction, "taux": taux}

  • [calcul_impôt] 调用其他函数时,它将 [admin_data] 作为第一个参数传递(第 10、14、39、42 行);
  • [calcul_impôt] 使用税务常量时,现在通过字典 [admin_data] 进行传递(第 19、22 行);

所有接收 [admin_data] 作为参数的函数,均会经历此类修改。

11.7. 结果

所得结果即第8.3节开头所示。