原文:https://www.oschina.net/question/2741863_2199513


问:
在 num<128的时候,使用chr(num).encode('utf-8')得到的是一个字符的ascii十六进制
但是在num>128的时候,使用chr(num).encode('utf-8')得到的是两个字节的ascii十六进制,请问该如何转换,我能够得到一个字节的十六进制??

答:
这是一个很有意思的问题,让我来从头讲起。
首先,我们看看help(chr),可以知道Return a Unicode string of one character with ordinal i; 0 <= i <= 0x10ffff. 所以chr(i)函数实际上返回的是Unicode编码表中的一个字符,参数i指定了这个字符在Unicdoe编码表中的位置。

PS1:Unicode编码表是一个全球统一的标准,我们可以认为它为世界上所有的字符(还有一些glyph、表情之类的)都编了号。而其他一些编码,比如ASCII、GBK、cp1252等编码,则只编码了Unicode编码表中的一部分字符。当然还有utf-8和uft-16等编码则是采用不同的方式编码了所有的Unicode字符。目前Unicode已经发布到了第9版,Python 3内部采用Unicode表示所有字符,即str类型,而编码成其他编码格式后,则是Bytes类型。

ASCII编码中每个字符编码是1个Byte,且只有0-127,而128-255部分的编码属于Extended ASCII。Python默认的ASCII中是没有大于127部分的,如果执行:chr(128).encode('ascii'),会得到异常'ascii' codec can't encode character '\x80' in position 0: ordinal not in range(128)

而utf-8编码虽然支持所有的Unicode字符,但根据字符的不同,会采用1~3个Byte来编码字符。它对于ASCII编码是兼容的,所以0~127的部分和utf-8和ascii的编码是一样的。但是大于128的那部分extended ascii中的字符,则采用了两个字节进行编码,这也是为什么你的代码得到了两个Bytes。

我猜你需要的是一个支持Extended ASCII部分字符,且采用1个Byte固定大小编码的encoding,比如ISO 8859-1,也被称为latin1。
举个例子,比如人民币钱多符号¥在Unicode中的位置是165。

>>> chr(165)
¥
>>> chr(165).encode('latin1')
b'\xa5'

其他的编码比如cp437、cp1252,也可以编码128-255中的部分字符,比如 chr(165).encode('cp437')返回b'\x9d',比如 chr(165).encode('cp1252')返回b'\xa5'。但如果编码chr(128)就会报错。所以,如果你的目的是要能支持chr(i),0 <= i <= 255,那么我还是推荐你用latin1。

PS2:关于这个问题,《Fluent Python》的第4章值得一读,讲得非常清楚。

参考资料:
* 《Fluent Python》, Chapter 4
* http://www.ascii-code.com/
* https://en.wikibooks.org/wiki/Unicode/Character_reference/0000-0FFF
* https://en.wikipedia.org/wiki/Extended_ASCII#ISO_8859_and_proprietary_adaptations
* https://docs.python.org/3.5/library/unicodedata.html


例子:

def replace_char(ch):
    if ch == "a":
        return "ab"
    return ch

s = "你好aaa阿斯蒂芬"
if isinstance(s, str) is True:
    try:
        print(111)
        s = s.encode(encoding="UTF-8")
        t = s.decode(encoding="UTF-8")
        print(t)
    except:
        pass

print(len(s))
print(s)
l = list(s)
print(l)
s =  b"".join([b"\"", b"".join(
                             map(
                                 lambda ch: replace_char(ch).encode("latin1"),
                                 map(lambda o: chr(o), s)
                             )
                         )
             , b"\""])

print(len(s))
print(s)

print(type(s))
s = str(s, "UTF-8")

print(s)