Как правильно кодировать escape-символы в Python 2, не убивая Unicode? - PullRequest
4 голосов
/ 20 марта 2012

Я думаю, что схожу с ума от Unicode-строк Python. Я пытаюсь кодировать escape-символы в строке 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?

PS: Как указано ниже, я на самом деле пытаюсь избежать управляющих персонажей. Нужно ли мне знать больше, чем это нужно.

Ответы [ 4 ]

3 голосов
/ 20 марта 2012

Сначала давайте исправим терминологию.То, что вы пытаетесь сделать, это заменить «управляющие символы» эквивалентной «escape-последовательностью».

Мне не удалось найти какой-либо встроенный метод для этого, и никто еще не опубликовал его.,К счастью, это не сложная функция для записи.

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])
2 голосов
/ 09 августа 2013

Обратная косая черта, экранирующая управляющие символы ascii в середине данных Юникода, безусловно, полезная вещь для достижения. Но это не просто экранирование, а правильное удаление, когда вы хотите вернуть фактические символьные данные.

Должен быть способ сделать это в python stdlib, но его нет. Я подал отчет об ошибке: 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_escape мог справиться с повторной сборкой всего правильно.

UPDATE

Так что у меня был случай, когда мне нужно было это работать как в 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')
1 голос
/ 20 марта 2012

Метод .encode возвращает байтовую строку (тип str в Python 2), поэтому он не может возвращать символы Юникода.

Но так как существует всего несколько \ - последовательностей, вы легко можете .replace их вручную. См. http://docs.python.org/reference/lexical_analysis.html#string-literals для получения полного списка.

0 голосов
/ 20 марта 2012

.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']'
...