Python:比较带有重音字符的字符串无法正常工作

3
我对Python还比较陌生。我正在尝试从另一个列表中删除出现在一个列表中的文件。这些列表是通过在Mac和Windows上重定向ll -R而生成的(但已经进行了一些处理,如合并、排序等,使用其他Python脚本)。 有些文件名带有重音符号和特殊符号。尽管它们相同(在包含列表的文件中打印和查看时都一样),但这些字符串被发现不相等。
我找到了关于如何比较带有特殊字符的Unicode字符串的线程:Python String Comparison--Problems With Special/Unicode Characters。这与我的问题非常相似。我还阅读了有关编码以及如何更改字符串编码的更多信息。 然而,我尝试了在codecs文档中找到的所有编解码器: https://docs.python.org/2/library/codecs.html 对于所有可能的编解码器组合,这两个字符串都不相等(请参见下面的程序 - 尝试了解码和编码选项)。
当逐个比较两个字符串中的字符时,一个字符串中的重音e显示为一个重音e(一个字符),而在另一个字符串中显示为两个字符(e和可打印为空格)。
任何想法都将不胜感激。
我将这两个文本文件缩小到一个单词的一行(显然带有重音符号)。我把文本文件上传到了dropbox:testfilesindatatestmissingfiles(但还没有尝试从dropbox下载新的副本)。
非常感谢!
PS. 对于混乱的链接,我很抱歉。我没有10的声望...
#!/usr/bin/python3

import sys

codecs = [ 'ascii', 'big5', 'big5hkscs', 'cp037', 'cp424', 'cp437', 'cp500', 'cp720      ', 'cp737   ', 'cp775', 'cp850', 'cp852', 'cp855', 'cp856   ', 'cp857', 'cp858', 'cp860', 'cp861', 'cp862', 'cp863', 'cp864', 'cp865', 'cp866', 'cp869', 'cp874     ', 'cp875   ', 'cp932', 'cp949', 'cp950', 'cp1006   ', 'cp1026', 'cp1140', 'cp1250', 'cp1251', 'cp1252', 'cp1253', 'cp1254', 'cp1255', 'cp1256', 'cp1257', 'cp1258', 'euc_jp', 'euc_jis_2004', 'euc_jisx0213', 'euc_kr', 'gb2312', 'gbk', 'gb18030', 'hz', 'iso2022_jp', 'iso2022_jp_1', 'iso2022_jp_2', 'iso2022_jp_2004', 'iso2022_jp_3', 'iso2022_jp_ext', 'iso2022_kr', 'latin_1', 'iso8859_2', 'iso8859_3', 'iso8859_4', 'iso8859_5', 'iso8859_6', 'iso8859_7', 'iso8859_8', 'iso8859_9', 'iso8859_10', 'iso8859_13', 'iso8859_14', 'iso8859_15', 'iso8859_16', 'johab', 'koi8_r   ', 'koi8_u      ', 'mac_cyrillic', 'mac_greek', 'mac_iceland', 'mac_latin2', 'mac_roman', 'mac_turkish', 'ptcp154', 'shift_jis', 'shift_jis_2004', 'shift_jisx0213', 'utf_32', 'utf_32_be', 'utf_32_le', 'utf_16', 'utf_16_be', 'utf_16_le', 'utf_7', 'utf_8', 'utf_8_sig' ]

file1 = open('testmissingfiles','r')
file2 = open('testfilesindata','r')

list1 = file1.readlines()
list2 = file2.readlines()

word1 = list1[0].rstrip('\n')
word2 = list2[0].rstrip('\n')

for i in range(0,len(codecs)-1):
    for j in range(0,len(codecs)-1):
        try:
            encoded1 = word1.decode(codecs[i])
            encoded2 = word2.decode(codecs[j])

            if encoded1 == encoded2:
                sys.stdout.write('Succeeded with ' + codecs[i] + ' & ' + codecs[j] + '\n')
        except:
            pass

你能给我们举一个比较失败的案例吗? - levi
欢迎来到 Stack Overflow!你真的写双倍行距的程序吗,还是这只是在 Stack Overflow 上的显示效果? - Robᵩ
谢谢你们两位。不知何种原因,stackoverflow的编辑器无法识别换行符,所以我不得不添加双倍行距。比较失败的情况是通过Dropbox文件链接在一起的。 - user3938196
2个回答

6
使用unicodedata.normalize函数将两个字符串规范化为相同的标准形式:
import unicodedata

encoded1 = unicodedata.normalize('NFC', word1.decode('utf8'))
encoded2 = unicodedata.normalize('NFC', word2.decode('utf8'))

将相同操作应用于两个单词后,我得到了以下结果: b'Adhe\xcc\x81sion' b'Adh\xc3\xa9sion' 因此根据这个结果,重音e可以用两种不同的方式书写。 - user3938196
顺便提一下,他们是这样使用 sys.stdout.write(...) 的: Adhésion Adhésion - user3938196

2
您的程序存在一些问题:
您的程序将会生成一个AttributeError异常,并在每次循环中使用pass。 无论是word1还是word2都没有一个名为.decode()的方法。 在Python3中,您可以将字符串编码为字节序列,或者将字节序列解码为字符串。
使用codecs是一个误导。 您的两个输入文件都是UTF-8编码的。 当您从文件中读取它们时,文件中的字节已经被成功解码。
您的字符串看起来相似,但由不同的Unicode代码点组成。 具体来说,“Adhésion”包括两个Unicode代码点0065和0301,“LATIN SMALL LETTER E”和“COMBINING ACUTE ACCENT”。 另一方面,第二个单词“Adhésion”包含单个代码点00E9,“LATIN SMALL LETTER E WITH ACUTE”。 正如Daniel在他的答案中指出的那样,您可以通过先将它们标准化来检查这些不同字符串的语义等价性。
以下是我解决您的问题的方法:
#!/usr/bin/python3

import sys
import unicodedata

with open('testmissingfiles', 'r') as fp:
    list1 = [line.strip() for line in fp]
with open('testfilesindata','r') as fp:
    list2 = [line.strip() for line in fp]

word1 = list1[0]
word2 = list2[0]

if word1 == word2:
    print("%s and %s are identical"%(word1, word2))
elif unicodedata.normalize('NFC', word1) == unicodedata.normalize('NFC', word2):
    print("%s and %s look the same, but use different code poitns"%(word1, word2))
else:
    print("%s and %s are unrelated"%(word1, word2))

是的,你说得对。它可以使用encode()选项。我尝试在添加编码不可能的异常情况后再尝试解码。抱歉。 - user3938196
希望您能理解,这里既不需要使用.encode()也不需要使用.decode(),它们都无法解决您的问题。您上传到Dropbox的文件至少都是UTF-8编码的,不需要进行任何特殊处理。 - Robᵩ
太好了!谢谢你!我太菜了,无法给你点赞。 - user3938196

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接