pinyinToneMarks = {'a': '\u0101\xe1\u01ce\xe0', 'e':
    '\u0113\xe9\u011b\xe8', 'i': '\u012b\xed\u01d0\xec', 'o':
    '\u014d\xf3\u01d2\xf2', 'u': '\u016b\xfa\u01d4\xf9', '\xfc':
    '\u01d6\u01d8\u01da\u01dc', 'A': '\u0100\xc1\u01cd\xc0', 'E':
    '\u0112\xc9\u011a\xc8', 'I': '\u012a\xcd\u01cf\xcc', 'O':
    '\u014c\xd3\u01d1\xd2', 'U': '\u016a\xda\u01d3\xd9', '\xdc':
    '\u01d5\u01d7\u01d9\u01db'}

def convertPinyinCallback(m):
    tone = int(m.group(3)) % 5
r = m.group(1).replace('v', '\xfc').replace('V', '\xdc')
pos = 0
if len(r) > 1 and not r[0] in 'aeoAEO':
    pos = 1
if tone != 0:
    r = r[0:pos] + pinyinToneMarks[r[pos]][tone - 1] + r[pos + 1:]
return r + m.group(2)

def convertPinyin(s):
    return re.sub('([aeiou\xfcv\xdc]{1,3})(n?g?r?)([012345])',
        convertPinyinCallback, s, flags=re.IGNORECASE)
print(convertPinyin('Ni3 hao3 ma0?'))
Преобразование пронумерованного пиньинь в пиньинь с тональными знаками