在不破坏Unicode的情况下,Python 2中正确编码转义字符的方法是什么?

6

我觉得Python的Unicode字符串让我快要疯了。我试图在一个Unicode字符串中编码转义字符而不转义实际的Unicode字符。结果我得到了这个:

In [14]: a = u"Example\n"

In [15]: b = u"Пример\n"

In [16]: print a
Example


In [17]: print b
Пример


In [18]: print a.encode('unicode_escape')
Example\n

In [19]: print b.encode('unicode_escape')
\u041f\u0440\u0438\u043c\u0435\u0440\n

虽然我迫切需要(英文示例可以按照我的要求工作,显然):

In [18]: print a.encode('unicode_escape')
Example\n

In [19]: print b.encode('unicode_escape')
Пример\n

除了转移到Python 3,我该怎么做?

附注:正如下面指出的那样,我实际上正在寻求避免控制字符。是否需要更多内容将不得而知。


3
问题在于,您的请求是矛盾的。Python 2 字符串(Python 3 中的 bytes)不包含 Unicode 字符,它们只包含字节。这些字节可能是以特定编码存储的 Unicode 代码点,但它们仍然只是字节。如果您想存储 Unicode,请使用 unicode。如果您想要字节,请使用 bytes - 但这样您就没有 Unicode,只有没有 UTF-* 信息的字节。它可能像一些奇怪的8位代码页一样。另请参阅 http://nedbatchelder.com/text/unipain.html,其中提供了一些见解和一般方法。 - user395760
1
@rassie 你需要定义“特殊”字符。可能你只需要将其编码为utf-8或其他格式,然后使用正则表达式。没有标准的编码可以满足你的要求。 - agf
@agf:Python 可以解释的每个转义序列。 - Nikolai Prokoschenko
你需要这个做什么? - Karl Knechtel
他想要的是从 u"Пример\n" 转换为 u"Пример\n",并且再次转回去。这对我很有用,因为我需要创建一个每行一个记录且字段周围没有引号的 TSV,允许在字段中使用 UTF-8 编码数据(以可读的方式)。因此,我需要转义 \t 和 \n(因此也需要转义 \)。这是一个合理的请求,但我没有一个好的解决方案。 - underrun
显示剩余7条评论
4个回答

4
在Unicode数据中反斜杠转义ASCII控制字符绝对是一个有用的尝试。但不仅仅是对它们进行转义,而是在需要实际字符数据时正确地取消转义它们。
Python标准库应该有一种方法来做到这一点,但实际上没有。我提交了一个错误报告:http://bugs.python.org/issue18679 但在此期间,可以使用translate和hackery来解决问题。
tm = dict((k, repr(chr(k))[1:-1]) for k in range(32))
tm[0] = r'\0'
tm[7] = r'\a'
tm[8] = r'\b'
tm[11] = r'\v'
tm[12] = r'\f'
tm[ord('\\')] = '\\\\'

b = u"Пример\n"
c = b.translate(tm)
print(c) ## results in: Пример\n

所有非反斜杠单字母控制字符都将使用\x##序列进行转义,但如果您需要对其进行其他处理,则可以使用翻译矩阵。尽管这种方法不会丢失任何信息,但它对我很有效。

但是将其取回也很麻烦,因为您不能仅使用translate将字符序列转换回单个字符。

d = c.encode('latin1', 'backslashreplace').decode('unicode_escape')
print(d) ## result in Пример with trailing newline character

您实际上需要使用latin1对映射到字节的字符进行单独编码,同时反斜杠转义latin1不知道的unicode字符,以便unicode_escape编解码器可以正确地重新组装所有内容。

更新:

所以我有一个情况,需要在python2.7和python3.3中都能工作。这是我所做的(埋藏在_compat.py模块中):

if isinstance(b"", str):                                                        
    byte_types = (str, bytes, bytearray)                                        
    text_types = (unicode, )                                                    
    def uton(x): return x.encode('utf-8', 'surrogateescape')                    
    def ntob(x): return x                                                       
    def ntou(x): return x.decode('utf-8', 'surrogateescape')                    
    def bton(x): return x
else:                                                                           
    byte_types = (bytes, bytearray)                                             
    text_types = (str, )                                                        
    def uton(x): return x                                                       
    def ntob(x): return x.encode('utf-8', 'surrogateescape')                    
    def ntou(x): return x                                                       
    def bton(x): return x.decode('utf-8', 'surrogateescape')    

escape_tm = dict((k, ntou(repr(chr(k))[1:-1])) for k in range(32))              
escape_tm[0] = u'\0'                                                            
escape_tm[7] = u'\a'                                                            
escape_tm[8] = u'\b'                                                            
escape_tm[11] = u'\v'                                                           
escape_tm[12] = u'\f'                                                           
escape_tm[ord('\\')] = u'\\\\'

def escape_control(s):                                                          
    if isinstance(s, text_types):                                               
        return s.translate(escape_tm)
    else:
        return s.decode('utf-8', 'surrogateescape').translate(escape_tm).encode('utf-8', 'surrogateescape')

def unescape_control(s):                                                        
    if isinstance(s, text_types):                                               
        return s.encode('latin1', 'backslashreplace').decode('unicode_escape')
    else:                                                                       
        return s.decode('utf-8', 'surrogateescape').encode('latin1', 'backslashreplace').decode('unicode_escape').encode('utf-8', 'surrogateescape')

3

首先,让我们纠正术语。您试图做的是用等效的“转义序列”替换“控制字符”。

我还没有找到任何内置的方法来完成这个任务,也没有人发布过这样的方法。幸运的是,这不是一个难写的函数。

control_chars = [unichr(c) for c in range(0x20)] # you may extend this as required

def control_escape(s):
    chars = []
    for c in s:
        if c in control_chars:
            chars.append(c.encode('unicode_escape'))
        else:
            chars.append(c)
    return u''.join(chars)

或者稍微不那么易读的单行版本:

def control_escape2(s):
    return u''.join([c.encode('unicode_escape') if c in control_chars else c for c in s])

哦,是的,“控制字符”,完全忘记了这个术语。 - Nikolai Prokoschenko
现在的问题是如何解码您创建的字符串?当我尝试这样做时,我得到了正确的字节串,但无法倒退。 - underrun
@underrun,如果你真的遇到了这个问题,你应该发布一个新的问题。我没有一个快速的答案。 - Mark Ransom

1

0

.encode('unicode_escape') 返回一个字节字符串。你可能想要直接在 Unicode 字符串中转义控制字符:

# coding: utf8
import re

def esc(m):
    return u'\\x{:02x}'.format(ord(m.group(0)))

s = u'\r\t\b马克\n'

# Match control characters 0-31.
# Use DOTALL option to match end-of-line control characters as well.
print re.sub(ur'(?s)[\x00-\x1f]',esc,s)

输出:

\x0d\x09\x08马克\x0a

请注意,除了0-31之外还有其他Unicode控制字符,因此您可能需要更多的内容,例如:
# coding: utf8
import re
import unicodedata as ud

def esc(m):
    c = m.group(0)
    if ud.category(c).startswith('C'):
        return u'\\u{:04x}'.format(ord(c))
    return c

s = u'\rMark\t\b马克\n'

# Match ALL characters so the replacement function
# can test the category.  Not very efficient if the string is long.
print re.sub(ur'(?s).',esc,s)

输出:

\u000dMark\u0009\u0008马克\u000a

你可能想更精细地控制哪些字符被视为控制字符。有许多类别可供选择。你可以使用正则表达式来匹配特定类型:

import sys
import re
import unicodedata as ud

# Generate a regular expression that matches any Cc category Unicode character.
Cc_CODES = u'(?s)[' + re.escape(u''.join(unichr(n) for n in range(sys.maxunicode+1) if ud.category(unichr(n)) == 'Cc')) + u']'

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