Python文本分析案例:近体诗格律分析

作者:长行

时间:2020.05.26

Github原文Week-03/Example-0301

在这个案例中,我们将要实现近体诗格律的分析。具体的,我们从如下角度分析近体诗的格律:

  1. 诗句数量、诗句字数是否符合近体诗的要求,即是否为五绝、七绝、五律、七律中的一种(暂不考虑排律、六言的情况)
  2. 是否押了平声韵,所押的韵脚是什么平水韵部(暂不考虑首句押韵的情况)
  3. 诗句是否有拗句,是否存在孤平和拗救的情况
  4. 诗文是否符合对黏的要求

如果当时该诗不符合第1个或第2个要求,则不再分析;如果符合第1个和第2个要求,则以如下方式分析该诗的格律。

《首春》 李世民

寒随穷律变,春逐鸟声开。

平平平仄仄 平仄仄平平(十灰) —— 仄仄脚正格 平平脚正格

初风飘带柳,晚雪间花梅。

平平平仄仄 仄仄中平平(十灰) —— 【失黏】仄仄脚正格 平平脚正格

碧林青旧竹,绿沼翠新苔。

仄平平仄仄 仄仄仄平平(十灰) —— 【失黏】仄仄脚正格 平平脚正格

芝田初雁去,绮树巧莺来。

平平平仄中 仄中中平平(十灰) —— 【失黏】仄仄脚正格 平平脚正格

下面我们开始实现以上功能。

首先,我们读取案例0201-全唐诗文本整理案例中已经整理好的全唐诗文本进行遍历。

with open("全唐诗.json", encoding="UTF-8") as file:
    poem_json = json.loads(file.read())

平水韵表详见“全唐诗.json”

第1个要求

检查诗句数量

近体诗在诗句数量上主要绝句、律诗和排律,绝句(律绝)由四句组成,律诗由八句组成,排律则超过八句,当前只考虑绝句和律诗的情况;在具体实现上,我们可以通过正则表达式,依据标点符号来拆分诗文中的各个句子,并统计诗句的总数是否为4或8。

sentence_list = [sentence for sentence in re.split("[,。?!]", content) if sentence != ""]  # 生成句子列表
if len(sentence_list) != 4 and len(sentence_list) != 8:
    print("[古体诗]诗句数量:", len(sentence_list))

检查诗句字数

近体诗在诗句字数上通常包括五言和七言两种(六言律诗传世极少,暂不考虑);在具体实现上,我们使用列表生成式和内置函数all判断诗中句子是否均为五言或七言。

if not all([len(sentence) == 5 or len(sentence) == 7 for sentence in sentence_list]):
    print("[古体诗]诗句并非五言或七言", [len(sentence) for sentence in sentence_list])

诗句平仄标注

第2个、第3个、第4个要求的分析都需要我们先标注诗句的平仄,即完成类似于如下方式的标注。

寒随穷律变,春逐鸟声开。

平平平仄仄 平仄仄平平

我们使用查平水韵表的方式,来判断诗句中每个字的平仄并标注。其中阴平和阳平均属于平声,标注为“平”;上声、去声、入声均属于仄声,标注为“仄”。若某字不存在于平水韵表中,或同时存在于多个不同平仄的韵部中,均认为该字平仄无法判断,标注为“中”;在后续的分析中,不考虑这些无法判断平仄的字的影响。

在具体实现上,包括如下步骤:

  1. 导入平水韵表并将其整理为字典;其中key为字,value为所有包含该字的韵部的名称及音调的列表;
  2. 使用导入平水韵表的字典,实现判断单个字平仄的函数;
  3. 使用判断单个字平仄的函数,实现诗句平仄的标注。

导入平水韵表

def load_rhythm_list():
    """
    载入平水韵表并转换为dict形式
    """
    with open("平水韵表.txt", encoding="UTF-8") as file:
        rhythm_lines = file.readlines()
    rhythm_dict = dict()
    for rhythm_line in rhythm_lines:
        rhythm_name = re.search(".*(?=[平上去入]声:)", rhythm_line).group()  # 读取韵部名称
        rhythm_tune = re.search("[平上去入](?=声:)", rhythm_line).group()  # 读取韵部声调
        rhythm_characters = re.sub(".*[平上去入]声:", "", rhythm_line)  # 获取韵部包含的所有文字
        for character in rhythm_characters:
            if character not in rhythm_dict:
                rhythm_dict[character] = list()
            rhythm_dict[character].append([rhythm_name, rhythm_tune])
    return rhythm_dict

平水韵表详见“平水韵表.txt”

实现计算单个字的平仄

def get_tone(rhythm_list):
    """
    依据字所在的韵部,返回当前字的平仄(若当前字为无法确定平仄的多音字,则返回“中”)

    :return: <str> 平/仄/中
    """
    tone_set = set()
    for rhythm_item in rhythm_list:
        tone_set.add(rhythm_item[1])
    if len(tone_set) == 1:  # 若当前字不是多音字或是平仄相同的多音字
        return list(tone_set)[0]
    else:
        return "中"

实现计算诗句中各字的平仄情况

sentence_tone = list()
for sentence in sentence_list:
    sentence_tone.append([get_tone(character) for character in sentence])

第2个要求

在近体诗中,我们将奇数句称为出句,将偶数句称为对句,一组出句和对句称为一联。通常来说,近体诗的对句需要押相同的平声韵,首句如押韵可押邻韵“借韵”。因此,我们检查诗句是否押平声韵,只需要检查所有偶数句是否押平声韵即可。在具体的实现上,我们使用列表生成式和内置函数all判断诗文是否押了平声韵。

if not all([sentence_tone[i][-1] in ["平", "中"] for i in range(len(sentence_tone)) if i % 2 == 1]):
    print("诗文未押韵或押仄声韵")

第3个要求

“拗句”是指诗句不符合格律。诗句的格律大体上可以用“一三五不论,二四六分明“来概括,包括多种正格、变格,情况十分复杂。在具体的实现上,我们使用的方法,利用正则表达式来匹配所有正格和变格,由此判断诗句是否为拗句。

def inspect_sentence_tone(sentence_tone):
    """
    判断诗句是否为拗句,是否存在孤平拗救的情况

    :return: <bool> 诗句是否正确, <str> 诗句的正格(用于判断对黏), <str> 诗句情况详细说明
    """
    if re.match("[平仄中]?[平中]?[平仄中][仄中][平中][平中][仄中]", sentence_tone):  # (仄)仄平平仄
        return True, "仄仄平平仄", "平仄脚正格"
    elif re.match("[平仄中]?[仄中]?[平仄中][平中][平中][仄中][仄中]", sentence_tone):  # (平)平平仄仄
        return True, "平平平仄仄", "仄仄脚正格"
    elif re.match("[平仄中]?[平中]?[平仄中][仄中][仄中][平中][平中]", sentence_tone):  # (仄)仄仄平平
        return True, "仄仄仄平平", "平平脚正格"
    elif re.match("[平仄中]?[仄中]?[平中][平中][仄中][仄中][平中]", sentence_tone):  # 平平仄仄平
        return True, "平平仄仄平", "仄平脚正格"
    elif re.match("[平仄中]?[平中]?[平仄中][仄中][仄中][平中][仄中]", sentence_tone):  # (仄)仄仄平仄
        return True, "仄仄平平仄", "平仄脚变格(半拗可救可不救,若救对句五言第第三字、七言第五字用平)"
    elif re.match("[平仄中]?[平中]?[平仄中][仄中][平仄中][仄中][仄中]", sentence_tone):  # (仄)仄(平)仄仄
        return True, "仄仄平平仄", "平仄脚变格(大拗必救,对句五言第第三字、七言第五字用平)"
    elif re.match("[平仄中]?[仄中]?[平中][平中][仄中][平仄中][仄中]", sentence_tone):  # 平平仄(平)仄
        return True, "平平平仄仄", "仄仄脚变格(五言第一字、七言第三字必平)"
    elif re.match("[平仄中]?[仄中]?[平仄中][仄中][平中][平中][平中]", sentence_tone):  # (仄)仄平平平
        return True, "仄仄仄平平", "平平脚变格(极其罕见)"
    elif re.match("[平仄中]?[仄中]?[仄中][平中][平中][仄中][平中]", sentence_tone):  # 仄平平仄平
        return True, "平平仄仄平", "仄平脚变格(孤平拗救,五言第一字、七言第三字必平)"
    elif re.match("[平仄中]?[仄中]?[平中][平中][平中][仄中][平中]", sentence_tone):  # 平平平仄平
        return True, "平平仄仄平", "仄平脚变格(出句拗对句救,五言第三字、七言第五字用平)"
    else:
        return False, "", "拗句"

第4个要求

诗句的对黏,是指相邻诗句之间的平仄关系。简单来说,同一联中的出句和对句中的第2字、第4字、第6字的平仄应不同,即“对”,若平仄相同则称为“失对”;上一联的对句和下一联的出句的第2字、第4字、第6字的平仄应相同,即“黏”,若平仄不同则称为“失黏”。在判断过程中,如果诗句是变格,则以其原始正格的平仄为准。

判断诗句“对”的情况

def is_tone_differ(tone_1, tone_2):
    """
    判断两个字平仄是否不同
    """
    if (tone_1 == "仄" or tone_1 == "中") and (tone_2 == "平" or tone_2 == "中"):
        return True
    elif (tone_1 == "平" or tone_1 == "中") and (tone_2 == "仄" or tone_2 == "中"):
        return True
    else:
        return False

def inspect_corresponding(first_type, second_type):
    """
    判断句子的对是否正确

    :param first_type: <str> 出句的正格
    :param second_type: <str> 对句的正格
    :return: <bool>
    """
    if len(first_type) != len(second_type):
        return False
    return is_tone_differ(first_type[-2], second_type[-2]) and is_tone_differ(first_type[-1], second_type[-1])

判断诗句“黏”的情况

def is_tone_same(tone_1, tone_2):
    """
    判断两个字平仄是否相同
    """
    if (tone_1 == "仄" or tone_1 == "中") and (tone_2 == "仄" or tone_2 == "中"):
        return True
    elif (tone_1 == "平" or tone_1 == "中") and (tone_2 == "平" or tone_2 == "中"):
        return True
    else:
        return False

def inspect_sticky(last_second_type, this_first_type):
    """
    判断句子的黏是否正确

    :param last_second_type: <str> 前句对句的正格
    :param this_first_type: <str> 当前出句的正格
    :return: <bool>
    """
    if len(last_second_type) != len(this_first_type):
        return False
    return is_tone_same(last_second_type[-2], this_first_type[-2])

整理并输出结果

在以上部分,我们已经可以实现所有的需求目标了,下面通过调用以上代码以实现诗文格律分析。

def poem_analyse(title, author, content):
    sentences = [sentence for sentence in re.split("[,。?!]", content) if sentence != ""]
    punctuations = re.findall("[,。?!]", content)

    # 检查诗文的句子数量是否为绝句或律诗
    if len(sentences) != 4 and len(sentences) != 8:
        print("《" + title + "》", author, "诗句句数不是绝句或律诗")
        return False

    # 检查诗文中句子的字数是否为五言或七言
    if not all([len(sentence) == 5 or len(sentence) == 7 for sentence in sentences]):
        print("《" + title + "》", author, "诗文中句子的字数不是五言或七言")
        return False

    # 计算诗句中每个字的平仄情况
    sentence_tone_list = list()
    for sentence in sentences:
        sentence_tone_list.append("".join([get_tone(character) for character in sentence]))

    # 判断是否押平声韵
    if not all([sentence_tone_list[i][-1] in ["平", "中"] for i in range(len(sentences)) if i % 2 == 1]):
        print("《" + title + "》", author, "诗文没有押韵或押仄声韵")
        return False

    print("《" + title + "》", author)

    last_second_type = ""

    for i in range(int(len(sentences) / 2)):
        first_sentence = sentences[2 * i + 0]  # 出句内容
        second_sentence = sentences[2 * i + 1]  # 对句内容

        first_tone = sentence_tone_list[2 * i + 0]  # 出句的平仄
        second_tone = sentence_tone_list[2 * i + 1]  # 对句的平仄

        second_rhythm = "(" + get_rhythm(second_sentence[-1]) + ")"  # 对句的韵脚

        first_correct, first_type, first_explanation = inspect_sentence_tone(first_tone)
        second_correct, second_type, second_explanation = inspect_sentence_tone(second_tone)

        other_analysis = ""
        if first_correct and second_correct:
            if not inspect_corresponding(first_type, second_type):  # 判断是否对
                other_analysis += "【失对】"
            if last_second_type is not None and inspect_sticky(last_second_type, first_type):  # 判断是否黏
                other_analysis += "【失黏】"
                
        last_second_type = second_type
        
        output_sentence = first_sentence + punctuations[2 * i + 0] + second_sentence + punctuations[2 * i + 1]  # 第一行输出
        output_analysis = first_tone + " " + second_tone + second_rhythm  # 第二行输出
        output_analysis += " —— " + other_analysis + first_explanation + " " + second_explanation
        print(output_sentence)
        print(output_analysis)
    return True


if __name__ == "__main__":
    with open("全唐诗.json", encoding="UTF-8") as file:  # 载入整理完成的全唐诗文本语料
        poem_json = json.loads(file.read())

    for poem_item in poem_json["data"]:
        if poem_analyse(poem_item["title"], poem_item["author"], poem_item["content"].replace("\n", "")):
            print("点击回车继续...")
            input()

运行结果实现了需求:

......
《春日望海》 李世民 诗句句数不是绝句或律诗
《临洛水》 李世民
春蒐驰骏骨,总辔俯长河。
平平平仄仄 中仄仄中平(五歌) —— 仄仄脚正格 平平脚正格
霞处流萦锦,风前漾卷罗。
平仄平平仄 平平仄中平(五歌) —— 【失黏】平仄脚正格 仄平脚正格
水花翻照树,堤兰倒插波。
仄平平仄仄 平平仄仄平(五歌) —— 【失对】【失黏】仄仄脚正格 仄平脚正格
岂必汾阴曲,秋云发棹歌。
仄仄平平仄 平平仄仄平(五歌) —— 平仄脚正格 仄平脚正格
点击回车继续...

完整源代码详见诗词格律分析(面对过程).py诗词格律分析(面对对象).py

已标记关键词 清除标记
相关推荐
<p> <strong><span style="font-size:20px;color:#FF0000;">本课程主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者</span></strong> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">1. 包含:<span style="color:#FFFF00;background-color:#FF0000;">项目源码、</span><span style="color:#FFFF00;background-color:#FF0000;">项目文档、数据库脚本、软件工具</span>等所有资料</span></strong></span> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">2. 手把手的带你从零开始部署运行本套系统</span></strong></span> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">3. 该项目附带的源码资料可作为毕设使用</span></strong></span> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">4. 提供技术答疑和远程协助指导</span></strong></span><strong><span style="font-size:18px;"></span></strong> </p> <p> <br /> </p> <p> <span style="font-size:18px;"><strong>项目运行截图:</strong></span> </p> <p> <strong><span style="font-size:18px;">1)系统登陆界面</span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241015433522.png" alt="" /><br /> </span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">2)学生模块</span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241015575966.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">3)教师模块</span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241016127898.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">4)系统管理员</span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241016281177.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241016369884.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><br /> </span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">更多Java毕设项目请关注我的毕设系列课程 <a href="https://edu.csdn.net/lecturer/2104">https://edu.csdn.net/lecturer/2104</a></span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><br /> </span></strong> </p>
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页