Как убедиться, что все строковые литералы являются unicode в python

У меня есть довольно большая база кода Python, которую нужно пройти. У него есть проблема, когда некоторые строковые литералы являются строками, а другие - юникодом. И это вызывает баги. Я пытаюсь преобразовать все в юникод. Мне было интересно, есть ли инструмент, который может конвертировать все литералы в юникод. т.е. если он нашел что-то вроде этого:

print "result code %d" % result['code']

to:

print u"result code %d" % result[u'code']

Если это поможет, я использую PyCharm (в случае, если есть расширение, которое делает это), однако я был бы рад использовать и подобную команду. Надеюсь, такой инструмент существует.


person mmopy    schedule 16.03.2013    source источник
comment
Почему не u"result code %d" тоже?   -  person unutbu    schedule 16.03.2013
comment
вы всегда можете использовать Python 3 :)   -  person MattDMo    schedule 16.03.2013
comment
@unutbu Вы абсолютно правы. Я отредактировал вопрос, чтобы включить это. Я такой глупый.   -  person mmopy    schedule 16.03.2013
comment
from future import unicode_literals? Но вполне возможно, что проблема не в строковых литералах, а в других источниках байтовых строк (например, неправильный выбор API или отсутствие вызовов encode/decode).   -  person    schedule 16.03.2013
comment
@MattDMo, к сожалению, мы используем некоторые сторонние библиотеки, которые поддерживаются только для Python 2.   -  person mmopy    schedule 16.03.2013
comment
@delnan Это сработало отлично. Я хотел бы запустить сценарий над исходным кодом и сделать все согласованным, а затем сказать людям всегда использовать строки Unicode. Но это, кажется, исправляет некоторые места, которые я нашел как ошибки.   -  person mmopy    schedule 16.03.2013


Ответы (2)


Вы можете использовать tokenize.generate_tokens, чтобы разбить строковое представление кода Python на токены. tokenize также классифицирует токены для вас. Таким образом, вы можете идентифицировать строковые литералы в коде Python.

Затем нетрудно манипулировать токенами, добавляя 'u', где это необходимо:


import tokenize
import token
import io
import collections

class Token(collections.namedtuple('Token', 'num val start end line')):
    @property
    def name(self):
        return token.tok_name[self.num]

def change_str_to_unicode(text):    
    result = text.splitlines()
    # Insert a dummy line into result so indexing result
    # matches tokenize's 1-based indexing
    result.insert(0, '')
    changes = []
    for tok in tokenize.generate_tokens(io.BytesIO(text).readline):
        tok = Token(*tok)
        if tok.name == 'STRING' and not tok.val.startswith('u'):
            changes.append(tok.start)

    for linenum, s in reversed(changes):
        line = result[linenum]
        result[linenum] = line[:s] + 'u' + line[s:]
    return '\n'.join(result[1:])

text = '''print "result code %d" % result['code']
# doesn't touch 'strings' in comments
'handles multilines' + \
'okay'
u'Unicode is not touched'
'''

print(change_str_to_unicode(text))

урожаи

print u"result code %d" % result[u'code']
# doesn't touch 'strings' in comments
u'handles multilines' + u'okay'
u'Unicode is not touched'
person unutbu    schedule 16.03.2013
comment
Это отлично сработало. Большое спасибо за то, что собрали этот ответ. - person mmopy; 17.03.2013
comment
Вы гениальны, сэр. - person bgusach; 28.01.2014

Попробуйте это (использует регулярное выражение), и оно короче, чем решение @unutbu.
Но есть лазейка, строки, содержащие #, не будут работать с этим.

import re
scode = '''
print "'Hello World'" # prints 'Hello World'
u'Unicode is unchanged'"""
# so are "comments"'''
x1 = re.compile('''(?P<unicode>u?)(?P<c>'|")(?P<data>.*?)(?P=c)''')

def repl(m):
    return "u%(c)s%(data)s%(c)s" % m.groupdict()

fcode = '\n'.join(
      [re.sub(x1,repl,i)
       if not '#' in i
       else re.sub(x1,repl,i[:i.find('#')])+i[i.find('#'):]
       for i in scode.splitlines()])
print fcode

Выходы:

print u"'Hello World'" # prints 'Hello World'
u'Unicode is unchanged'
# so are "comments"

Для # у меня есть это (и оно длиннее, чем решение @unutbu :|)

import re
scode = '''print "'Hello World'"  # prints 'Hello World'
u'Unicode is unchanged'
# so are "comments"
'#### Hi' # 'Hi' '''

x1 = re.compile('''(?P<unicode>u?)(?P<c>'|")(?P<data>.*?)(?P=c)''')

def in_string(text,index):
    curr,in_l,in_str,level = '',0,False,[]

    for c in text[:index+1]:
        if c == '"' or c == "'":
            if in_str and curr == c:
                instr = False
                curr = ''
                in_l -= 1
            else:
                instr = True
                curr = c
                in_l += 1
        level.append(in_l)
    return bool(level[index])

def repl(m):
    return "u%(c)s%(data)s%(c)s" % m.groupdict()

def handle_hashes(i):
    if i.count('#') == 1:
        n = i.find('#')
    else:
        n = get_hash_out_of_string(i)
    return re.sub(x1,repl,i[:n]) + i[n:]

def get_hash_out_of_string(i):
    n = i.find('#')
    curr = i[:]
    last = (len(i)-1)-''.join(list(reversed(i))).find('#')
    while in_string(curr,n) and n < last:
        curr = curr[:n]+' '+curr[n+1:]
        n = curr.find('#')
    return n

fcode = '\n'.join(
    [re.sub(x1,repl,i)
     if not '#' in i
     else handle_hashes(i)
     for i in scode.splitlines()])

print fcode

Выход:

print u"'Hello World'"  # prints 'Hello World'
u'Unicode is unchanged'
# so are "comments"
u'#### Hi' # 'Hi' 
person pradyunsg    schedule 17.03.2013
comment
Я не одобряю использование регулярных выражений для разбора/манипулирования нестандартными языками, такими как Python, тем более, что в стандартную библиотеку языка включен превосходный парсер Python. Отсюда -1. - person David Foerster; 29.01.2019