SUPPORT_UCS4 = len('\U00020000') == 1
PY2 = sys.version_info < (3, 0)
    PY2 = False  # pragma: no cover
    def callable_check(obj): PHRASES_DICT = {} PINYIN_DICT = pinyin_dict.pinyin_dict class Style(IntEnum):
    """拼音风格"""

    #: 普通风格，不带声调。如： 中国 -> ``zhong guo``
    NORMAL = 0
    #: 标准声调风格，拼音声调在韵母第一个字母上（默认风格）。如： 中国 -> ``zhōng guó``
    TONE = 1
    #: 声调风格2，即拼音声调在各个韵母之后，用数字 [1-4] 进行表示。如： 中国 -> ``zho1ng guo2``
    TONE2 = 2
    #: 声调风格3，即拼音声调在各个拼音之后，用数字 [1-4] 进行表示。如： 中国 -> ``zhong1 guo2``
    TONE3 = 8
    #: 声母风格，只返回各个拼音的声母部分（注：有的拼音没有声母，详见 `#27`_）。如： 中国 -> ``zh g``
    INITIALS = 3
    #: 首字母风格，只返回拼音的首字母部分。如： 中国 -> ``z g``
    FIRST_LETTER = 4
    #: 韵母风格，只返回各个拼音的韵母部分，不带声调。如： 中国 -> ``ong uo``
    FINALS = 5
    #: 标准韵母风格，带声调，声调在韵母第一个字母上。如：中国 -> ``ōng uó``
    FINALS_TONE = 6
    #: 韵母风格2，带声调，声调在各个韵母之后，用数字 [1-4] 进行表示。如： 中国 -> ``o1ng uo2``
    FINALS_TONE2 = 7
    #: 韵母风格3，带声调，声调在各个拼音之后，用数字 [1-4] 进行表示。如： 中国 -> ``ong1 uo2``
    FINALS_TONE3 = 9
    #: 注音风格，带声调，阴平（第一声）不标。如： 中国 -> ``ㄓㄨㄥ ㄍㄨㄛˊ``
    BOPOMOFO = 10
    #: 注音风格，仅首字母。如： 中国 -> ``ㄓ ㄍ``
    BOPOMOFO_FIRST = 11
    #: 汉语拼音与俄语字母对照风格，声调在各个拼音之后，用数字 [1-4] 进行表示。如： 中国 -> ``чжун1 го2``
    CYRILLIC = 12
    #: 汉语拼音与俄语字母对照风格，仅首字母。如： 中国 -> ``ч г``
    CYRILLIC_FIRST = 13
    #: 威妥玛拼音/韦氏拼音/威式拼音风格，无声调
    WADEGILES = 14 RE_HANS = re.compile(
    r'^(?:['
    r'\u3007'                  # 〇
    r'\ue815-\ue864'
    r'\ufa18'
    r'\u3400-\u4dbf'           # CJK扩展A:[3400-4DBF]
    r'\u4e00-\u9fff'           # CJK基本:[4E00-9FFF]
    r'\uf900-\ufaff'           # CJK兼容:[F900-FAFF]
    r'\U00020000-\U0002A6DF'   # CJK扩展B:[20000-2A6DF]
    r'\U0002A703-\U0002B73F'   # CJK扩展C:[2A700-2B73F]
    r'\U0002B740-\U0002B81D'   # CJK扩展D:[2B740-2B81D]
    r'\U0002B825-\U0002BF6E'
    r'\U0002C029-\U0002CE93'
    r'\U0002D016'
    r'\U0002F80A-\U0002FA1F'   # CJK兼容扩展:[2F800-2FA1F]
    r'\U00030EDD'
    r'\U00030EDE'
    r'])+$'
) class DefaultConverter(Converter):
    def __init__(self, **kwargs):
        pass

    def convert(self, words, style, heteronym, errors, strict, **kwargs):
        """根据参数把汉字转成相应风格的拼音结果。

        :param words: 汉字字符串
        :type words: unicode
        :param style: 拼音风格
        :param heteronym: 是否启用多音字
        :type heteronym: bool
        :param errors: 如果处理没有拼音的字符
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :type strict: bool
        :return: 按风格转换后的拼音结果
        :rtype: list

        """
        pys = []
        # 初步过滤没有拼音的字符
        if RE_HANS.match(words):
            pys = self._phrase_pinyin(words, style=style, heteronym=heteronym,
                                      errors=errors, strict=strict)
            return _remove_dup_and_empty(pys)

        py = self.handle_nopinyin(words, style=style, errors=errors,
                                  heteronym=heteronym, strict=strict)
        if py:
            pys.extend(py)
        return _remove_dup_and_empty(pys)

    def pre_convert_style(self, han, orig_pinyin, style, strict, **kwargs):
        """在把原始带声调的拼音按拼音风格转换前会调用 ``pre_convert_style`` 方法。

        如果返回值不为 ``None`` 会使用返回的结果代替 ``orig_pinyin``
        来进行后面的风格转换。

        :param han: 要处理的汉字
        :param orig_pinyin: 汉字对应的原始带声调拼音
        :param style: 要转换的拼音风格
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: ``None`` 或代替 ``orig_pinyin`` 参与拼音风格转换的拼音字符串。

        """
        pass

    def convert_style(self, han, orig_pinyin, style, strict, **kwargs):
        """按 ``style`` 的值对 ``orig_pinyin`` 进行处理，返回处理后的拼音

        转换风格前会调用 ``pre_convert_style`` 方法，
        转换后会调用 ``post_convert_style`` 方法。

        :param han: 要处理的汉字
        :param orig_pinyin: 汉字对应的原始带声调拼音
        :param style: 拼音风格
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: 按拼音风格转换处理后的拼音

        """
        pre_data = self.pre_convert_style(
            han, orig_pinyin, style=style, strict=strict)
        if pre_data is not None:
            pinyin = pre_data
        else:
            pinyin = orig_pinyin

        converted_pinyin = self._convert_style(
            han, pinyin, style=style, strict=strict, default=pinyin)

        post_data = self.post_convert_style(
            han, pinyin, converted_pinyin, style=style, strict=strict)
        if post_data is None:
            post_data = converted_pinyin

        return post_data

    def post_convert_style(self, han, orig_pinyin, converted_pinyin,
                           style, strict, **kwargs):
        """在把原始带声调的拼音按拼音风格转换前会调用 ``pre_convert_style`` 方法。

        如果返回值不为 ``None`` 会使用返回的结果代替 ``converted_pinyin``
        作为拼音风格转换后的最终拼音结果。

        :param han: 要处理的汉字
        :param orig_pinyin: 汉字对应的原始带声调拼音
        :param converted_pinyin: 按拼音风格转换处理后的拼音
        :param style: 要转换的拼音风格
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: ``None`` 或代替 ``converted_pinyin`` 作为拼音风格转换后的拼音结果。

        """
        pass

    def pre_handle_nopinyin(self, chars, style, heteronym, errors,
                            strict, **kwargs):
        """处理没有拼音的字符串前会调用 ``pre_handle_nopinyin`` 方法。

        如果返回值不为 ``None`` 会使用返回的结果作为处理没有拼音字符串的结果，
        不再使用内置方法进行处理。

        :param chars: 待处理的没有拼音的字符串
        :param errors: 如何处理
        :param heteronym: 是否需要处理多音字
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: ``None`` 或代替 ``chars`` 参与拼音风格转换的拼音字符串
                  或拼音结果 list。

        """
        pass

    def handle_nopinyin(self, chars, style, heteronym, errors,
                        strict, **kwargs):
        """处理没有拼音的字符串。

        处理前会调用 ``pre_handle_nopinyin`` 方法，
        处理后会调用 ``post_handle_nopinyin`` 方法。

        :param chars: 待处理的没有拼音的字符串
        :param style: 拼音风格
        :param errors: 如何处理
        :param heteronym: 是否需要处理多音字
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :return: 处理后的拼音结果，如果为 ``None`` 或空 list 表示忽略这个字符串.
        :rtype: list
        """
        pre_data = self.pre_handle_nopinyin(
            chars, style, errors=errors, heteronym=heteronym, strict=strict)

        if pre_data is not None:
            py = pre_data
        else:
            pre_data = chars
            py = self._convert_nopinyin_chars(
                pre_data, style, errors=errors,
                heteronym=heteronym, strict=strict)

        post_data = self.post_handle_nopinyin(
            chars, style, errors=errors, heteronym=heteronym, strict=strict,
            pinyin=py)
        if post_data is not None:
            py = post_data

        if not py:
            return []
        if isinstance(py, list):
            # 包含多音字信息
            if isinstance(py[0], list):
                if heteronym:
                    return py
                # [[a, b], [c, d]]
                # [[a], [c]]
                return [[x[0]] for x in py]

            return [[i] for i in py]
        else:
            return [[py]]

    def post_handle_nopinyin(self, chars, style, heteronym,
                             errors, strict,
                             pinyin, **kwargs):
        """处理完没有拼音的字符串后会调用 ``post_handle_nopinyin`` 方法。

        如果返回值不为 ``None`` 会使用返回的结果作为处理没有拼音的字符串的结果。

        :param chars: 待处理的没有拼音的字符串
        :param errors: 如何处理
        :param heteronym: 是否需要处理多音字
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :param pinyin: 处理后的拼音信息，值为空 list 或包含拼音信息的 list
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: ``None`` 或代替 ``pinyin`` 做为处理结果。

        """
        pass

    def post_pinyin(self, han, heteronym, pinyin, **kwargs):
        """找到汉字对应的拼音后，会调用 ``post_pinyin`` 方法。

        如果返回值不为 ``None`` 会使用返回的结果作为 han 的拼音数据。

        :param han: 单个汉字或者词语
        :param heteronym: 是否需要处理多音字
        :param pinyin: 单个汉字的拼音数据或词语的拼音数据 list
        :type pinyin: list
        :param kwargs: 其他关键字参数，暂时无用，用于以后扩展新的参数。
        :return: ``None`` 或代替 ``pinyin`` 作为 han 的拼音 list。

        """
        pass

    def _phrase_pinyin(self, phrase, style, heteronym, errors, strict):
        """词语拼音转换.

        :param phrase: 词语
        :param errors: 指定如何处理没有拼音的字符
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :return: 拼音列表
        :rtype: list
        """
        pinyin_list = []
        if phrase in PHRASES_DICT:
            pinyin_list = deepcopy(PHRASES_DICT[phrase])
        else:
            for han in phrase:
                py = self._single_pinyin(han, style, heteronym, errors, strict)
                pinyin_list.extend(py)

        post_data = self.post_pinyin(phrase, heteronym, pinyin_list)
        if post_data is not None:
            pinyin_list = post_data

        for idx, item in enumerate(pinyin_list):
            han = phrase[idx]
            if heteronym:
                pinyin_list[idx] = [
                        self.convert_style(
                            han, orig_pinyin=x, style=style, strict=strict)
                        for x in item
                    ]
            else:
                orig_pinyin = item[0]
                pinyin_list[idx] = [
                    self.convert_style(
                        han, orig_pinyin=orig_pinyin, style=style,
                        strict=strict)]

        return pinyin_list

    def _single_pinyin(self, han, style, heteronym, errors, strict):
        """单字拼音转换.

        :param han: 单个汉字
        :param errors: 指定如何处理没有拼音的字符，详情请参考
                       :py:func:`~pypinyin.pinyin`
        :param strict: 只获取声母或只获取韵母相关拼音风格的返回结果
                       是否严格遵照《汉语拼音方案》来处理声母和韵母，
                       详见 :ref:`strict`
        :return: 返回拼音列表，多音字会有多个拼音项
        :rtype: list
        """
        num = ord(han)
        # 处理没有拼音的字符
        if num not in PINYIN_DICT:
            return self.handle_nopinyin(
                han, style=style, errors=errors,
                heteronym=heteronym, strict=strict)

        pys = PINYIN_DICT[num].split(',')  # 字的拼音列表
        return [pys]

    def _convert_style(self, han, pinyin, style, strict, default,
                       **kwargs):
        return convert_style(pinyin, style, strict, default=default, **kwargs)

    def _convert_nopinyin_chars(self, chars, style, heteronym, errors, strict):
        """转换没有拼音的字符。

        """
        if callable_check(errors):
            return errors(chars)

        if errors == 'default':
            return chars
        elif errors == 'ignore':
            return None
        elif errors == 'replace':
            if len(chars) > 1:
                return ''.join(text_type('%x' % ord(x)) for x in chars)
            else:
                return text_type('%x' % ord(chars)) class UltimateConverter(DefaultConverter):
    def __init__(self, v_to_u=False, neutral_tone_with_five=False,
                 tone_sandhi=False, **kwargs):
        super(UltimateConverter, self).__init__(**kwargs)
        self._v_to_u = v_to_u
        self._neutral_tone_with_five = neutral_tone_with_five
        self._tone_sandhi = tone_sandhi

    def post_convert_style(self, han, orig_pinyin, converted_pinyin,
                           style, strict, **kwargs):
        post_data = super(UltimateConverter, self).post_convert_style(
            han, orig_pinyin, converted_pinyin, style, strict, **kwargs)
        if post_data is not None:
            converted_pinyin = post_data

        if self._v_to_u:
            post_data = _v2UConverter().post_convert_style(
                han, orig_pinyin, converted_pinyin, style, strict, **kwargs)
            if post_data is not None:
                converted_pinyin = post_data

        if self._neutral_tone_with_five:
            post_data = _neutralToneWith5Converter().post_convert_style(
                han, orig_pinyin, converted_pinyin, style, strict, **kwargs)
            if post_data is not None:
                converted_pinyin = post_data

        return converted_pinyin

    def post_pinyin(self, han, heteronym, pinyin, **kwargs):
        post_data = super(UltimateConverter, self).post_pinyin(
            han, heteronym, pinyin, **kwargs)
        if post_data is not None:
            pinyin = post_data

        if self._tone_sandhi:
            post_data = _toneSandhiConverter().post_pinyin(
                han, heteronym, pinyin, **kwargs)
            if post_data is not None:
                pinyin = post_data

        return pinyin class ToneSandhiMixin(object):
    """

    按普通话变调规则处理拼音：

    * https://en.wikipedia.org/wiki/Standard_Chinese_phonology#Tone_sandhi
    * https://studycli.org/zh-CN/learn-chinese/tone-changes-in-mandarin/

    """  # noqa

    def post_pinyin(self, han, heteronym, pinyin_list, **kwargs):
        ret = super(ToneSandhiMixin, self).post_pinyin(
            han, heteronym, pinyin_list, **kwargs)
        if ret is not None:
            pinyin_list = ret

        pinyin_list = self._third_tone(han, pinyin_list)
        pinyin_list = self._bu(han, pinyin_list)
        pinyin_list = self._yi(han, pinyin_list)

        return pinyin_list

    def _third_tone(self, han, pinyin_list):
        """

        Third tone sandhi:

        The principal rule of third tone sandhi is:

            When there are two consecutive third-tone syllables, the first of them is pronounced with second tone.

        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 

        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:

            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̯˨˩˦].
            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˨˩˦].

        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".

        """  # noqa
        tone2_pinyin_list = [tone_to_tone2(x[0]) for x in pinyin_list]
        if '3' not in ''.join(tone2_pinyin_list):
            return pinyin_list

        changed = False
        third_num = 0
        for pinyin in tone2_pinyin_list:
            if '3' in pinyin:
                third_num += 1
            else:
                third_num = 0

        if third_num == 2:
            for i, v in enumerate(tone2_pinyin_list):
                if '3' in v:
                    tone2_pinyin_list[i] = v.replace('3', '2')
                    changed = True
                    break

        elif third_num > 2:
            n = 1
            for i, v in enumerate(tone2_pinyin_list):
                if '3' in v:
                    if n == third_num:
                        break
                    tone2_pinyin_list[i] = v.replace('3', '2')
                    changed = True
                    n += 1

        if changed:
            return [[tone2_to_tone(x)] for x in tone2_pinyin_list]

        return pinyin_list

    def _bu(self, han, pinyin_list):
        """

        For 不 bù:

            不 is pronounced with second tone when followed by a fourth tone syllable.

                Example: 不是 (bù+shì, "to not be") becomes búshì [pu˧˥ʂɻ̩˥˩]

            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ì).

        """  # noqa
        if '不' not in han:
            return pinyin_list

        tone2_pinyin_list = [tone_to_tone2(x[0]) for x in pinyin_list]
        changed = False

        for i, h in enumerate(han):
            current_pinyin = tone2_pinyin_list[i]
            if h == '不' and i < len(han) - 1:
                next_pinyin = tone2_pinyin_list[i+1]
                if '4' in next_pinyin:
                    tone2_pinyin_list[i] = current_pinyin.replace('4', '2')
                    changed = True
                else:
                    tone2_pinyin_list[i] = _re_num.sub('4', current_pinyin)
                    changed = True
            elif h == '不':
                tone2_pinyin_list[i] = _re_num.sub('4', current_pinyin)
                changed = True

        if changed:
            return [[tone2_to_tone(x)] for x in tone2_pinyin_list]

        return pinyin_list

    def _yi(self, han, pinyin_list):
        """

        For 一 yī:

            一 is pronounced with second tone when followed by a fourth tone syllable.

                Example: 一定 (yī+dìng, "must") becomes yídìng [i˧˥tiŋ˥˩]

            Before a first, second or third tone syllable, 一 is pronounced with fourth tone.

                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˨˩˦].

            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).
            When 一 is used between two reduplicated words, it may become neutral in tone (e.g. 看一看 kànyikàn ("to take a look of")).

        """  # noqa
        if '一' not in han:
            return pinyin_list

        tone2_pinyin_list = [tone_to_tone2(x[0]) for x in pinyin_list]
        changed = False

        for i, h in enumerate(han):
            current_pinyin = tone2_pinyin_list[i]
            if h == '一' and i < len(han) - 1:
                next_pinyin = tone2_pinyin_list[i + 1]
                if '4' in next_pinyin:
                    tone2_pinyin_list[i] = current_pinyin.replace('4', '2')
                    changed = True
                else:
                    tone2_pinyin_list[i] = _re_num.sub('4', current_pinyin)
                    changed = True
            elif h == '一':
                tone2_pinyin_list[i] = _re_num.sub('1', current_pinyin)
                changed = True

        if changed:
            return [[tone2_to_tone(x)] for x in tone2_pinyin_list]

        return pinyin_list class Seg(object):
class PrefixSet(object):
    def __init__(self, prefix_set, no_non_phrases=False):
    def cut(self, text):
    def train(self, words):
    def __init__(self):
    def train(self, word_s):
    def __contains__(self, key):
def retrain(seg_instance): def seg(hans):
    hans = simple_seg(hans)
    ret = []
    for x in hans:
        if not RE_HANS.match(x):   # 没有拼音的字符，不再参与二次分词
            ret.append(x)
        elif PHRASES_DICT:
            ret.extend(list(mmseg.seg.cut(x)))
        else:   # 禁用了词语库，不分词
            ret.append(x)
    return ret
from itertools import chain
from pypinyin.compat import text_type
from pypinyin.constants import (
    PHRASES_DICT, PINYIN_DICT, Style, RE_HANS
)
from pypinyin.converter import DefaultConverter, UltimateConverter
from pypinyin.contrib.tone_sandhi import ToneSandhiMixin
from pypinyin.contrib.tone_convert import tone2_to_tone
from pypinyin.seg import mmseg
from pypinyin.seg.simpleseg import seg
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import unicode_literals




def load_single_dict(pinyin_dict, style='default'):
    """载入用户自定义的单字拼音库

    :param pinyin_dict: 单字拼音库。比如： ``{0x963F: u"ā,ē"}``
    :param style: pinyin_dict 参数值的拼音库风格. 支持 'default', 'tone2'
    :type pinyin_dict: dict
    """
    if style == 'tone2':
        for k, v in pinyin_dict.items():
            v = tone2_to_tone(v)
            PINYIN_DICT[k] = v
    else:
        PINYIN_DICT.update(pinyin_dict)


    mmseg.retrain(mmseg.seg)