在Python3中将unicode序列转换为字符串,但允许在字符串中使用路径

在Python3中将unicode序列转换为字符串,但允许在字符串中使用路径,python,python-3.x,unicode,Python,Python 3.x,Unicode,在尝试解码unicode序列时,至少有一种方法被证明是有用的 我正在对很多不同体裁的文本进行预处理。有些是经济的,有些是技术的,等等。其中一个注意事项是转换unicode序列: 'Korado's output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojt\u0115ch \u010camek. 此类字符串需要转换为实际字符: 'Korado's output has go

在尝试解码unicode序列时,至少有一种方法被证明是有用的

我正在对很多不同体裁的文本进行预处理。有些是经济的,有些是技术的,等等。其中一个注意事项是转换unicode序列:

'Korado's output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojt\u0115ch \u010camek.
此类字符串需要转换为实际字符:

'Korado's output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojtĕch Čamek.
可以这样做:

s = "'Korado's output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojt\u0115ch \u010camek."
s = s.encode('utf-8').decode('unicode-escape')
(至少当
s
是取自
utf-8
编码文本文件的输入行时,这一点才起作用。我似乎无法在REPL.it这样的在线服务上实现这一点,因为它的输出编码/解码方式不同。)

在大多数情况下,这很好。但是,如果在输入字符串中看到目录结构路径(通常是我的数据集中的技术文档),则会出现
UnicodeDecodeError
s

给定以下数据
unicode.txt

'Korado's output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojt\u0115ch \u010camek, Financial Director and Director of Controlling.
Voor alle bestanden kan de naam met de volledige padnaam (bijvoorbeeld: /u/slick/udfs/math.a (op UNIX), d:\udfs\math.dll (op Windows)).
使用bytestring表示:

b"'Korado's output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojt\\u0115ch \\u010camek, Financial Director and Director of Controlling.\r\nVoor alle bestanden kan de naam met de volledige padnaam (bijvoorbeeld: /u/slick/udfs/math.a (op UNIX), d:\\udfs\\math.dll (op Windows))."
解码输入文件中的第二行时,以下脚本将失败:

with open('unicode.txt', 'r', encoding='utf-8') as fin, open('unicode-out.txt', 'w', encoding='utf-8') as fout:
    lines = ''.join(fin.readlines())
    lines = lines.encode('utf-8').decode('unicode-escape')

    fout.write(lines)
带跟踪:

Traceback (most recent call last):
  File "C:/Python/files/fast_aligning/unicode-encoding.py", line 3, in <module>
    lines = lines.encode('utf-8').decode('unicode-escape')
UnicodeDecodeError: 'unicodeescape' codec can't decode bytes in position 275-278: truncated \uXXXX escape

Process finished with exit code 1

ignore
模式中的
raw\u unicode\u escape
编解码器似乎可以做到这一点。我在这里将输入内联为原始字节longstring,根据我的推理,这应该相当于从二进制文件中读取它

input = br"""
'Korado's output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojt\u0115ch \u010camek, Financial Director and Director of Controlling.
Voor alle bestanden kan de naam met de volledige padnaam (bijvoorbeeld: /u/slick/udfs/math.a (op UNIX), d:\udfs\math.dll (op Windows)).
"""

print(input.decode('raw_unicode_escape', 'ignore'))
输出

财务总监兼控制总监VojtĕchČamek说,Korado的产量已从每年18万台增加到今天的近170万台 所有这些都是最简单的方法(bijvoorbeeld:/u/slick/udfs/math.a(op UNIX),d:s\math.dll(op Windows))

请注意,
d:\udfs
中的
\udf
被损坏,因为编解码器试图开始读取
\uxxx
序列,但在
s
时放弃

另一种方法(可能较慢)是使用regexp在解码数据中查找有效的Unicode序列。这假设
.decode()
可以将完整的输入字符串转换为UTF-8。(由于不能对字符串进行编码,只能对字节进行编码,
.encode().decode()
舞蹈是必要的。还可以使用
chr(int(m.group(0)[2:],16))

输出

财务总监兼控制总监VojtĕchČamek说,Korado的产量已从每年18万台增加到今天的近170万台 所有这些都是最简单的方法(bijvoorbeeld:/u/slick/udfs/math.a(op UNIX),d:\udfs\math.dll(op Windows))


由于
\udf
没有4个十六进制字符,因此
d:\udfs
路径在此保留。

当AKX发布他的答案时,我已经编写了此代码。我仍然认为这是适用的

我们的想法是用正则表达式捕获unicode序列候选(并尝试排除路径,例如前面有任何字母和冒号的部分(例如
c:\udfff
)。如果解码失败,我们将返回原始字符串

with open('unicode.txt', 'r', encoding='utf-8') as fin, open('unicode-out.txt', 'w', encoding='utf-8') as fout:
    lines = ''.join(fin.readlines())
    lines = lines.strip()
    lines = unicode_replace(lines)
    fout.write(lines)


def unicode_replace(s):
    # Directory paths in a text are seen as unicode sequences but will fail to decode, e.g. d:\udfs\math.dll
    # In case of such failure, we'll pass on these sentences - we don't try to decode them but leave them
    # as-is. Note that this may leave some unicode sequences alive in your text.
    def repl(match):
        match = match.group()
        try:
            return match.encode('utf-8').decode('unicode-escape')
        except UnicodeDecodeError:
            return match

    return re.sub(r'(?<!\b[a-zA-Z]:)(\\u[0-9A-Fa-f]{4})', repl, s)
以open('unicode.txt','r',encoding='utf-8')作为fin,以open('unicode-out.txt','w',encoding='utf-8')作为fout:
lines=''.join(fin.readlines())
lines=lines.strip()
行=unicode\u替换(行)
四、写(行)
def unicode_更换:
#文本中的目录路径被视为unicode序列,但无法解码,例如d:\udfs\math.dll
#如果出现这种情况,我们会把这些句子传下去——我们不会试图解码它们,而是留下它们
#请注意,这可能会在文本中保留一些unicode序列。
def repl(匹配):
match=match.group()
尝试:
返回match.encode('utf-8')。decode('unicode-escape'))
除UNICEDECODEERROR外:
复赛

返回re.sub(r'(?输入是不明确的。在一般情况下,正确答案不存在。我们可以使用启发式方法,生成在大多数情况下看起来正确的输出,例如,我们可以使用规则,例如“如果
\uxxx
序列(6个字符)是现有路径的一部分,则不要将其解释为Unicode转义”对于
\uxxxxx
(10个字符)序列也是如此,例如,与问题中类似的输入:
b“c:\\U0001f60f\\math.dll”
可以根据磁盘上是否实际存在
c:\U0001f60f\math.dll
文件进行不同的解释:

#!/usr/bin/env python3
import re
from pathlib import Path


def decode_unicode_escape_if_path_doesnt_exist(m):
    path = m.group(0)
    return path if Path(path).exists() else replace_unicode_escapes(path)


def replace_unicode_escapes(text):
    return re.sub(
        fr"{unicode_escape}+",
        lambda m: m.group(0).encode("latin-1").decode("raw-unicode-escape"),
        text,
    )


input_text = Path('broken.txt').read_text(encoding='ascii')
hex = "[0-9a-fA-F]"
unicode_escape = fr"(?:\\u{hex}{{4}}|\\U{hex}{{8}})"
drive_letter = "[a-zA-Z]"
print(
    re.sub(
        fr"{drive_letter}:\S*{unicode_escape}\S*",
        decode_unicode_escape_if_path_doesnt_exist,
        input_text,
    )
)
如果编码文本中有非ascii字符,请在
read_text()
中指定
break.txt
文件的实际编码

用于提取路径的特定正则表达式取决于您获得的输入类型


您可以尝试一次替换一个可能的Unicode序列,从而使代码复杂化(在这种情况下,替换的数量随着候选序列的数量呈指数增长,例如,如果路径中有10个可能的Unicode转义序列,则有
2**10
解码路径可供尝试).

因此,要清楚地说,
unicode.txt
文件本身实际上包含有
\uxxx
转义序列的字符串,而这不仅仅是Python打印的方式?@AKX是的。我认为最头痛的解决方案是使用
try:…逐行解码文件,但UnicodeDecodeError
除外。显然这将更慢,并且可能隐藏实际的编码问题。更有趣的是,程序如何知道
c:\udfs
中的
\udf
是否意味着Unicode字符0xDF的转义或路径名的一部分?我想您总是有可能产生歧义。
c:\other\u00f6
是两个目录路径还是一个直接路径名为
c:\otherö
?由于输入的编码不正确,您必须做出有根据的猜测。这很有效(而且速度相对较快)-但有一个大问题,即特殊字符会引发语法错误。例如,添加
België
(荷兰语表示
比利时
)输入
,它将不起作用。因此,我认为您的
with open('unicode.txt', 'r', encoding='utf-8') as fin, open('unicode-out.txt', 'w', encoding='utf-8') as fout:
    lines = ''.join(fin.readlines())
    lines = lines.strip()
    lines = unicode_replace(lines)
    fout.write(lines)


def unicode_replace(s):
    # Directory paths in a text are seen as unicode sequences but will fail to decode, e.g. d:\udfs\math.dll
    # In case of such failure, we'll pass on these sentences - we don't try to decode them but leave them
    # as-is. Note that this may leave some unicode sequences alive in your text.
    def repl(match):
        match = match.group()
        try:
            return match.encode('utf-8').decode('unicode-escape')
        except UnicodeDecodeError:
            return match

    return re.sub(r'(?<!\b[a-zA-Z]:)(\\u[0-9A-Fa-f]{4})', repl, s)
#!/usr/bin/env python3
import re
from pathlib import Path


def decode_unicode_escape_if_path_doesnt_exist(m):
    path = m.group(0)
    return path if Path(path).exists() else replace_unicode_escapes(path)


def replace_unicode_escapes(text):
    return re.sub(
        fr"{unicode_escape}+",
        lambda m: m.group(0).encode("latin-1").decode("raw-unicode-escape"),
        text,
    )


input_text = Path('broken.txt').read_text(encoding='ascii')
hex = "[0-9a-fA-F]"
unicode_escape = fr"(?:\\u{hex}{{4}}|\\U{hex}{{8}})"
drive_letter = "[a-zA-Z]"
print(
    re.sub(
        fr"{drive_letter}:\S*{unicode_escape}\S*",
        decode_unicode_escape_if_path_doesnt_exist,
        input_text,
    )
)