Python 处理 UCS-2 (UTF-16?) 的笨拙方法转为 ASCII
我对这个问题有点无从下手,所以请原谅我用词不当。
我在Windows XP上用Python 2.7来运行这个程序。
我找到了一段Python代码,它可以读取一个日志文件,做一些处理,然后显示结果。
觉得这信息不够详细?好吧,我给你一个简化版:
#!/usr/bin/python
import re
import sys
class NotSupportedTOCError(Exception):
pass
def filter_toc_entries(lines):
while True:
line = lines.next()
if re.match(r""" \s*
.+\s+ \| (?#track)
\s+.+\s+ \| (?#start)
\s+.+\s+ \| (?#length)
\s+.+\s+ \| (?#start sec)
\s+.+\s*$ (?#end sec)
""", line, re.X):
lines.next()
break
while True:
line = lines.next()
m = re.match(r"""
^\s*
(?P<num>\d+)
\s*\|\s*
(?P<start_time>[0-9:.]+)
\s*\|\s*
(?P<length_time>[0-9:.]+)
\s*\|\s*
(?P<start_sector>\d+)
\s*\|\s*
(?P<end_sector>\d+)
\s*$
""", line, re.X)
if not m:
break
yield m.groupdict()
def calculate_mb_toc_numbers(eac_entries):
eac = list(eac_entries)
num_tracks = len(eac)
tracknums = [int(e['num']) for e in eac]
if range(1,num_tracks+1) != tracknums:
raise NotSupportedTOCError("Non-standard track number sequence: %s", tracknums)
leadout_offset = int(eac[-1]['end_sector']) + 150 + 1
offsets = [(int(x['start_sector']) + 150) for x in eac]
return [1, num_tracks, leadout_offset] + offsets
f = open(sys.argv[1])
mb_toc_urlpart = "%20".join(str(x) for x in calculate_mb_toc_numbers(filter_toc_entries(f)))
print mb_toc_urlpart
这段代码在处理“简单”的文本日志文件时运行得很好(我想说是ASCII格式,虽然这可能不太准确,比如Notepad++显示的是ANSI格式)。
但是,这个脚本在某些日志文件上就不工作了(在这些情况下,Notepad++显示的是“UCS-2 Little Endian”)。
我遇到了以下错误:
Traceback (most recent call last):
File "simple.py", line 55, in <module>
mb_toc_urlpart = "%20".join(str(x) for x in calculate_mb_toc_numbers(filter_
toc_entries(f)))
File "simple.py", line 49, in calculate_mb_toc_numbers
leadout_offset = int(eac[-1]['end_sector']) + 150 + 1
IndexError: list index out of range
这个日志 可以正常工作
这个日志 就不行了
我觉得是编码问题导致脚本出错,因为如果我在命令提示符下这样做:
type ascii.log > scrubbed.log
然后在scrubbed.log上运行脚本,脚本就能正常工作(这对我来说没问题,因为没有丢失重要信息,而且我不需要写回文件,只是打印到控制台)。
一种解决办法是在把日志文件传给Python之前先“清理”一下(比如用上面提到的类型管道技巧生成一个临时文件,然后让脚本在那个文件上运行),但我希望Python能“忽略”编码问题,如果可能的话。我也不太确定怎么检测脚本正在读取什么类型的日志文件,以便采取相应的措施。
3 个回答
Python 2.x 期望普通字符串是 ASCII 编码(或者至少是一个字节)。你可以试试这个:
在你的 Python 源文件的最顶部加上这段代码:
from __future__ import unicode_literals
然后把所有的 str
改成 unicode
。
[编辑]
正如 Ignacio Vazquez-Abrams 所说,可以试试用 codecs.open()
来打开输入文件。
codecs.open()
这个函数可以让你用特定的编码方式打开一个文件,并且它会生成 unicode
字符串。你可以尝试几种编码方式,从最可能用到的到最不可能用到的(或者这个工具可能总是生成 UTF-16LE,但这可不太可能哦,哈哈)。
works.log
这个文件看起来是用 ASCII 编码的:
>>> data = open('works.log', 'rb').read()
>>> all(d < '\x80' for d in data)
True
breaks.log
这个文件看起来是用 UTF-16LE 编码的——它的开头有两个字节 '\xff\xfe'
。在 breaks.log
中,没有任何字符超出了 ASCII 的范围:
>>> data = open('breaks.log', 'rb').read()
>>> data[:2]
'\xff\xfe'
>>> udata = data.decode('utf16')
>>> all(d < u'\x80' for d in udata)
True
如果这就是唯一的两种可能性,你可以用以下的方法来解决问题。把你的主代码从:
f = open(sys.argv[1])
mb_toc_urlpart = "%20".join(
str(x) for x in calculate_mb_toc_numbers(filter_toc_entries(f)))
print mb_toc_urlpart
改成这样:
f = open(sys.argv[1], 'rb')
data = f.read()
f.close()
if data[:2] == '\xff\xfe':
data = data.decode('utf16').encode('ascii')
# ilines is a generator which produces newline-terminated strings
ilines = (line + '\n' for line in data.splitlines())
mb_toc_urlpart = "%20".join(
str(x) for x in calculate_mb_toc_numbers(filter_toc_entries(ilines)) )
print mb_toc_urlpart