tone_sandhi.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import re
  4. from pypinyin.contrib.tone_convert import tone_to_tone2, tone2_to_tone
  5. _re_num = re.compile(r'\d')
  6. class ToneSandhiMixin(object):
  7. """
  8. 按普通话变调规则处理拼音:
  9. * https://en.wikipedia.org/wiki/Standard_Chinese_phonology#Tone_sandhi
  10. * https://studycli.org/zh-CN/learn-chinese/tone-changes-in-mandarin/
  11. """ # noqa
  12. def post_pinyin(self, han, heteronym, pinyin_list, **kwargs):
  13. ret = super(ToneSandhiMixin, self).post_pinyin(
  14. han, heteronym, pinyin_list, **kwargs)
  15. if ret is not None:
  16. pinyin_list = ret
  17. pinyin_list = self._third_tone(han, pinyin_list)
  18. pinyin_list = self._bu(han, pinyin_list)
  19. pinyin_list = self._yi(han, pinyin_list)
  20. return pinyin_list
  21. def _third_tone(self, han, pinyin_list):
  22. """
  23. Third tone sandhi:
  24. The principal rule of third tone sandhi is:
  25. When there are two consecutive third-tone syllables, the first of them is pronounced with second tone.
  26. For example, lǎoshǔ 老鼠 ("mouse") comes to be pronounced láoshǔ [lau̯˧˥ʂu˨˩]. It has been investigated whether the rising contour (˧˥) on the prior syllable is in fact identical to a normal second tone; it has been concluded that it is, at least in terms of auditory perception.[1]: 237 
  27. When there are three or more third tones in a row, the situation becomes more complicated, since a third tone that precedes a second tone resulting from third tone sandhi may or may not be subject to sandhi itself. The results may depend on word boundaries, stress, and dialectal variations. General rules for three-syllable third-tone combinations can be formulated as follows:
  28. If the first word is two syllables and the second word is one syllable, then the first two syllables become second tones. For example, bǎoguǎn hǎo 保管好 ("to take good care of") takes the pronunciation báoguán hǎo [pau̯˧˥kwan˧˥xau̯˨˩˦].
  29. If the first word is one syllable, and the second word is two syllables, the second syllable becomes second tone, but the first syllable remains third tone. For example: lǎo bǎoguǎn 老保管 ("to take care of all the time") takes the pronunciation lǎo báoguǎn [lau̯˨˩pau̯˧˥kwan˨˩˦].
  30. Some linguists have put forward more comprehensive systems of sandhi rules for multiple third tone sequences. For example, it is proposed[1]: 248  that modifications are applied cyclically, initially within rhythmic feet (trochees; see below), and that sandhi "need not apply between two cyclic branches".
  31. """ # noqa
  32. tone2_pinyin_list = [tone_to_tone2(x[0]) for x in pinyin_list]
  33. if '3' not in ''.join(tone2_pinyin_list):
  34. return pinyin_list
  35. changed = False
  36. third_num = 0
  37. for pinyin in tone2_pinyin_list:
  38. if '3' in pinyin:
  39. third_num += 1
  40. else:
  41. third_num = 0
  42. if third_num == 2:
  43. for i, v in enumerate(tone2_pinyin_list):
  44. if '3' in v:
  45. tone2_pinyin_list[i] = v.replace('3', '2')
  46. changed = True
  47. break
  48. elif third_num > 2:
  49. n = 1
  50. for i, v in enumerate(tone2_pinyin_list):
  51. if '3' in v:
  52. if n == third_num:
  53. break
  54. tone2_pinyin_list[i] = v.replace('3', '2')
  55. changed = True
  56. n += 1
  57. if changed:
  58. return [[tone2_to_tone(x)] for x in tone2_pinyin_list]
  59. return pinyin_list
  60. def _bu(self, han, pinyin_list):
  61. """
  62. For 不 bù:
  63. 不 is pronounced with second tone when followed by a fourth tone syllable.
  64. Example: 不是 (bù+shì, "to not be") becomes búshì [pu˧˥ʂɻ̩˥˩]
  65. In other cases, 不 is pronounced with fourth tone. However, when used between words in an A-not-A question, it may become neutral in tone (e.g., 是不是 shìbushì).
  66. """ # noqa
  67. if '不' not in han:
  68. return pinyin_list
  69. tone2_pinyin_list = [tone_to_tone2(x[0]) for x in pinyin_list]
  70. changed = False
  71. for i, h in enumerate(han):
  72. current_pinyin = tone2_pinyin_list[i]
  73. if h == '不' and i < len(han) - 1:
  74. next_pinyin = tone2_pinyin_list[i+1]
  75. if '4' in next_pinyin:
  76. tone2_pinyin_list[i] = current_pinyin.replace('4', '2')
  77. changed = True
  78. else:
  79. tone2_pinyin_list[i] = _re_num.sub('4', current_pinyin)
  80. changed = True
  81. elif h == '不':
  82. tone2_pinyin_list[i] = _re_num.sub('4', current_pinyin)
  83. changed = True
  84. if changed:
  85. return [[tone2_to_tone(x)] for x in tone2_pinyin_list]
  86. return pinyin_list
  87. def _yi(self, han, pinyin_list):
  88. """
  89. For 一 yī:
  90. 一 is pronounced with second tone when followed by a fourth tone syllable.
  91. Example: 一定 (yī+dìng, "must") becomes yídìng [i˧˥tiŋ˥˩]
  92. Before a first, second or third tone syllable, 一 is pronounced with fourth tone.
  93. Examples:一天 (yī+tiān, "one day") becomes yìtiān [i˥˩tʰjɛn˥], 一年 (yī+nián, "one year") becomes yìnián [i˥˩njɛn˧˥], 一起 (yī+qǐ, "together") becomes yìqǐ [i˥˩t͡ɕʰi˨˩˦].
  94. When final, or when it comes at the end of a multi-syllable word (regardless of the first tone of the next word), 一 is pronounced with first tone. It also has first tone when used as an ordinal number (or part of one), and when it is immediately followed by any digit (including another 一; hence both syllables of the word 一一 yīyī and its compounds have first tone).
  95. When 一 is used between two reduplicated words, it may become neutral in tone (e.g. 看一看 kànyikàn ("to take a look of")).
  96. """ # noqa
  97. if '一' not in han:
  98. return pinyin_list
  99. tone2_pinyin_list = [tone_to_tone2(x[0]) for x in pinyin_list]
  100. changed = False
  101. for i, h in enumerate(han):
  102. current_pinyin = tone2_pinyin_list[i]
  103. if h == '一' and i < len(han) - 1:
  104. next_pinyin = tone2_pinyin_list[i + 1]
  105. if '4' in next_pinyin:
  106. tone2_pinyin_list[i] = current_pinyin.replace('4', '2')
  107. changed = True
  108. else:
  109. tone2_pinyin_list[i] = _re_num.sub('4', current_pinyin)
  110. changed = True
  111. elif h == '一':
  112. tone2_pinyin_list[i] = _re_num.sub('1', current_pinyin)
  113. changed = True
  114. if changed:
  115. return [[tone2_to_tone(x)] for x in tone2_pinyin_list]
  116. return pinyin_list