065:文字列を最大nバイトに切り詰める
最初に思いつく方法は、文字列の先頭から n バイト切り出す方法です。しかし、日本語が対象となっている時は、切れ目が文字の先頭とは限らないので問題が起こります。
うまくいかない例
>>> li='あいうえお' >>> li[0] '\x82' >>> li[:1] '\x82' >>> li[:2] 'あ' >>> li[:3] 'あ\x82' >>> li[:4] 'あい'
2.5 以降ならば codecs モジュールの iterencode, getincrementaldecoder らを使用して、この問題を簡単に解決できます。次の関数 take_nbytes_str, take_nbytes_unicode を使用してください。
# coding: utf-8
from codecs import getincrementaldecoder, iterencode
def _iter_mbchar(iterator, encoding, errors='strict', **kwargs):
decoder = getincrementaldecoder(encoding)(errors, **kwargs)
mbchar = []
for input in iterator:
mbchar.append(input)
output = decoder.decode(input)
if output:
yield ''.join(mbchar)
del mbchar[:]
output = decoder.decode("", True)
if output:
yield ''.join(mbchar)
def take_nbytes_str(str_, n, encoding, errors='strict', **kwargs):
u"""
文字列 str_ を最大 n バイトに切り詰める
str_: 8 ビット文字列
n: 最大のバイト数。これより長い場合、切り詰める
encoding: str_ の文字コード
errors: コーデックが用いるエラー処理方法
**kwargs: コーデックに渡すオプション
"""
length = 0
temp = []
for mbchar in _iter_mbchar(str_, encoding, errors, **kwargs):
length += len(mbchar)
if length > n:
break
temp.append(mbchar)
return ''.join(temp)
def take_nbytes_unicode(unicode_, n, encoding, errors='strict', **kwargs):
u"""
ユニコード文字列 unicode_ をエンコードする、ただし最大 n バイトまで
unicode_: ユニコード文字列
n: 最大のバイト数。出力結果がこれより長くなることはない
encoding: 文字コード
errors: コーデックが用いるエラー処理方法
**kwargs: コーデックに渡すオプション
"""
length = 0
temp = []
for mbchar in iterencode(unicode_, encoding, errors, **kwargs):
length += len(mbchar)
if length > n:
break
temp.append(mbchar)
return ''.join(temp)
def _test():
def _write(f, code):
s = u'あいうえおかきくけこさしすせそたちつてと'.encode(code)
for i in range(1, 40):
f.write(''.join(take_nbytes_str(s, i, code)))
f.write('\n')
u = u'あいうえおかきくけこさしすせそたちつてと'
for i in range(1, 40):
f.write(''.join(take_nbytes_unicode(u, i, code)))
f.write('\n')
f_sjis = open('_sjis.txt', 'w')
try:
_write(f_sjis, 'shift_jis')
finally:
f_sjis.close()
f_euc = open('_euc.txt', 'w')
try:
_write(f_euc, 'euc_jp')
finally:
f_euc.close()
f_utf8 = open('_utf8.txt', 'w')
try:
_write(f_utf8, 'utf_8')
finally:
f_utf8.close()
if __name__ == '__main__':
_test()
以下は codecs の新機能の存在を知らずに作成してしまった関数 take_nbytes1 です。恥ずかしながら残しておきます。 Python 2.4 以下では役に立つかもしれません。
# coding: utf-8
def _shift_jis(c):
if c <= '\x7f' or '\xa1' <= c <= '\xdf':
return 1
elif '\x81' <= c <= '\x9f' or '\xe0' <= c <= '\xfc':
return 2
raise ValueError(repr(c))
def _euc_jp(c):
if c <= '\x7f':
return 1
elif c == '\x8e' or '\xa1' <= c <= '\xfe':
return 2
elif c == '\x8f':
return 3
raise ValueError(repr(c))
def _utf_8(c):
if c <= '\x7f':
return 1
elif '\xc0' <= c <= '\xdf':
return 2
elif '\xe0' <= c <= '\xef':
return 3
elif '\xf0' <= c <= '\xf7':
return 4
elif '\xf8' <= c <= '\xfb':
return 5
elif '\xfc' <= c <= '\xfd':
return 6
raise ValueError(repr(c))
_code = {
'shift_jis': _shift_jis,
'euc_jp': _euc_jp,
'utf_8': _utf_8,
}
def take_nbytes1(str_, n, code):
u"""文字列 str_ を最大 n バイトに切り詰める
str_: 8 ビット文字列
n: 最大のバイト数。これより長い場合、切り詰める
code: code: str_ の文字コード ['shift_jis', 'euc_jp', 'utf_8']
"""
length = 0
buf = []
f_code = _code[code]
i = iter(str_)
try:
while True:
c = i.next()
width = f_code(c)
length += width
if length > n:
break
buf.append(c)
for _ in range(width - 1):
buf.append(i.next())
except StopIteration:
pass
return ''.join(buf)
def _test():
def _write(f, code):
s = u'あいうえおかきくけこさしすせそたちつてと'.encode(code)
for i in range(1, 40):
f.write(take_nbytes1(s, i, code))
f.write('\n')
f_sjis = open('_sjis.txt', 'w')
try:
_write(f_sjis, 'shift_jis')
finally:
f_sjis.close()
f_euc = open('_euc.txt', 'w')
try:
_write(f_euc, 'euc_jp')
finally:
f_euc.close()
f_utf8 = open('_utf8.txt', 'w')
try:
_write(f_utf8, 'utf_8')
finally:
f_utf8.close()
if __name__ == '__main__':
_test()