6.10.9 定义定制编码
由于Python已经提供了大量标准codecs,所以应用一般不太可能需要定义定制的编码器或解码器。不过,如果确实有必要,codecs中的很多基类可以帮助你更容易地定义定制编码第一步是了解编码描述的转换性质。这一节中的例子将使用一个“invertcaps”编码,它把大写字母转换为小写,把小写字母转换为大写。下面是一个编码函数的简单定义,它会对输入字符串完成这个转换。
import string
def invertcaps(text):
"""Return new string with the case of all letters switched.
"""
return ''.join(
c.upper() if c in string.ascii_lowercase
else c.lower() if c in string.ascii_uppercase
else c
for c in text
)
if __name__ == '__main__':
print(invertcaps('ABCdef'))
print(invertcaps('abcDEF'))
在这里,编码器和解码器都是同一个函数(与ROT-13类似)。
运行结果:
abcDEF
ABCdef
尽管很容易理解,但这个实现效率不高,特别是对于非常大的文本串。幸运的是,codecs包含一些辅助函数,可以创建基于字符映射(character map)的codecs,如invertcaps。字符映射编码由两个字典构成。编码映射(encoding map)将输入串的字符值转换为输出中的字节值,解码映射(decoding map)则相反。首先创建解码映射,然后使用make_encoding_map()把它转换为一个编码映射。C函数charmap_encode()和charmap_decode()可以使用这些映射高效地转换输入数据。
# codecs_invertcaps_charmap.py
import codecs
import string
# Map every character to itself.
decoding_map = codecs.make_identity_dict(range(256))
# Make a list of pairs of ordinal values for the
# lowercase and uppercase letters.
pairs = list(zip(
[ord(c) for c in string.ascii_lowercase],
[ord(c) for c in string.ascii_uppercase],
))
# Modify the mapping to convert upper to lower and
# lower to upper.
decoding_map.update({
upper: lower
for (lower,upper)
in pairs
})
decoding_map.update({
lower: upper
for (lower,upper)
in pairs
})
# Create a separate encoding map.
encoding_map = codecs.make_encoding_map(decoding_map)
if __name__ == '__main__':
print(codecs.charmap_encode('abcDEF','strict',encoding_map))
print(codecs.charmap_decode(b'avcDEF','strict',decoding_map))
print(encoding_map == decoding_map)
尽管invertcaps的编码和解码映射是一样的,但并不总是如此。有时会把多个输入字符编码为相同的输出字节,make_encoding_map()会检测这些情况,并把编码值替换为None,以标志编码为未定义。
运行结果:
(b’ABCdef’, 6)
(‘AVCdef’, 6)
True
字符映射编码器和解码器支持前面结束的所有标准错误处理方法,所以不需要做任何额外的工作来支持这部分API。
import codecs
from codecs_invertcaps_charmap import encoding_map
text = 'pi: \u03c0'
for error in ['ignore','replace','strict']:
try:
encoded = codecs.charmap_encode(
text,error,encoding_map)
except UnicodeEncodeError as err:
encoded = str(err)
print('{:7}: {}'.format(error,encoded))
由于π的Unicode码点不再编码映射中,所以采用strict错误处理模式时会产生一个异常。
运行结果:
定义了编码和解码映射之后,还需要建立一些额外的类,另外要注册编码。register()向注册表增加一个搜索函数,使得当前用户希望使用这种编码时,codecs能够找到它。这个搜索函数必须有一个字符串参数,起重工包含编码名,如果它知道这个编码则返回一个CodecInfo对象,否则返回None。
import codecs
import encodings
def search1(encoding):
print('search1: Searching for:',encoding)
return None
def search2(encoding):
print('search2: Searching for:',encoding)
return None
codecs.register(search1)
codecs.register(search2)
utf8 = codecs.lookup('utf-8')
print('UTF-8:',utf8)
try:
unknown = codecs.lookup('no-such-encoding')
except LookupError as err:
print('ERROR:',err)
可以注册多个搜索函数,每个搜索函数将依次调用,直到一个搜索函数返回一个CodecInfo或者所有搜索函数都已经调用。codecs注册的内部搜索函数知道如何加载标准codecs,如encodings的UTF-8,所以这些编码名不会传递到定制搜索函数。
运行结果:
搜索函数返回的CodecInfo实例告诉codecs如何使用所支持的各种不同机制来完成编码和解码,包括:无状态编码、增量式编码和流编码。codecs包括一些基类来帮助建立字符映射编码。下面这个例子集成了所有内容,它会注册一个搜索函数,并返回为invertcaps codec配置的一个CodecInfo实例。
import codecs
from codecs_invertcaps_charmap import encoding_map,decoding_map
class InvertCapsCodec(codecs.Codec):
"Stateless encoder/decoder"
def encode(self,input,errors='strict'):
return codecs.charmap_encode(input,errors,encoding_map)
def decode(self,input,errors='strict'):
return codecs_charmap_decode(input,errors,decoding_map)
class InvertCapsIncrementalEncoder(codecs.IncrementalEncoder):
def encode(self,input,final=False):
data,nbytes = codecs.charmap_encode(input,self.errors,encoding_map)
return data
class InvertCapsIncrementalDecoder(codecs.IncrementalDecoder):
def decode(self,input,final=False):
data,nbytes = codecs.charmap_decode(input,self.errors,decoding_map)
return data
class InvertCapsStreamReader(InvertCapsCodec,codecs.StreamReader):
pass
class InvertCapsStreamWriter(InvertCapsCodec,codecs.StreamWriter):
pass
def find_invertcaps(encoding):
"""Return the codec for 'invertcaps'.
"""
if encoding == 'invertcaps':
return codecs.CodecInfo(
name = 'invertcaps',
encode = InvertCapsCodec().encode,
decode = InvertCapsCodec().decode,
incrementalencoder = InvertCapsIncrementalEncoder,
incrementaldecoder = InvertCapsIncrementalDecoder,
streamreader = InvertCapsStreamReader,
streamwriter = InvertCapsStreamWriter,
)
return None
codecs.register(find_invertcaps)
if __name__ == '__main__':
# Stateless encoder/decode
encoder = codecs.getencoder('invertcaps')
text = 'abcDEF'
encoded_text,consumed = encoder(text)
print('Encoded "{}" to "{}",consuming {} characters'.format(
text,encoded_text,consumed))
# Stream writer
import io
buffer = io.BytesIO()
writer = codecs.getwriter('invertcaps')(buffer)
print('StreamWriter for io buffer: ')
print(' Writing "abcDEF"')
writer.write('abcDEF')
print(' buffer contents: ',buffer.getvalue())
# Incremental decoder
decoder_factory = codecs.getincrementaldecoder('invertcaps')
decoder = decoder_factory()
decoded_text_parts = []
for c in encoded_text:
decoded_text_parts.append(
decoder.decode(bytes([c]),final=False)
)
decoded_text_parts.append(
decoder.decode(b'',final=True))
decoded_text = ''.join(decoded_text_parts)
print('IncrementalDecoder converted {!r} to {!r}'.format(
encoded_text,decoded_text))
运行结果:
无状态编码器/解码器的基类是Codec,要用新实现来覆盖encode()和decode()(在这里分别调用了charmap_encode()和charmap_decode())。这些方法必须分别返回一个元组,其中包含转换的数据和已消费的输入字节或字符数。charmap_encode()和charmap_decode()已经返回了这个信息,所以很方便。
IncrementalEncoder和IncrementalDecoder可以作为增量式编码接口的基类。增量类的encode()和decode()方法被定义为只返回真正的转换数据。缓冲的有关信息都作为内部状态来维护。invertcaps编码不需要缓存数据(它使用一种一对一映射)。如果编码根据所处理的数据会生成不同数量的输出,如压缩算法,那么对于这些编码,BufferedIncrementalEncoder和BufferedIncrementalDecoder将是更合适的基类,因为它们可以管理输入中未处理的部分。StreamReader和StreamWriter也需要encode(0和decode()方法,而且因为它们往往返回与Codec中相应方法同样的值,所以实现时可以使用多重继承。