Каков надежный способ преобразования некоторой строки (utf-8 или еще) в простую строку ASCII в python

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


person olamundo    schedule 24.11.2009    source источник


Ответы (5)


Если вам нужна строка ASCII, однозначно представляющая то, что у вас есть, без потери информации, ответ прост:

Не возитесь с кодированием/декодированием, используйте функцию repr() (Python 2.X) или функцию ascii() (Python 3.x).

person John Machin    schedule 25.11.2009

Вы говорите, что «кодировка варьируется». Я предполагаю, что под «этим» вы подразумеваете «строку» Python 2.x, которая на самом деле представляет собой последовательность байтов.

Ответ на первую часть: если вы не знаете кодировку этой закодированной строки, то нет, с ней вообще невозможно что-либо сделать*. Если вы знаете кодировку, то первым шагом будет преобразование str в unicode:

encoded_string = i_have_no_control()
the_encoding = 'utf-8' # for the sake of example
text = unicode(encoded_string, the_encoding)

Затем вы можете перекодировать свой объект unicode как ASCII, если хотите.

ascii_garbage = text.encode('ascii', 'replace')

* Существуют эвристические методы угадывания кодировок, но они медленные и ненадежные. Вот одна отличная попытка в Python.

person Jonathan Feinberg    schedule 24.11.2009
comment
нет, с ним вообще невозможно сделать что-либо осмысленное -- почти каждый используемый сегодня набор символов наследует свои младшие символы из ASCII. В этом случае есть кое-что важное, что вы можете сделать: отбросить все символы, отличные от ASCII. Этого хочет спрашивающий. Исключения (UTF-16 и UTF-32) нельзя спутать ни с какими другими наборами символов, поэтому я считаю, что их можно игнорировать. - person intgr; 24.11.2009
comment
Вы, кажется, придерживаетесь мнения, что единственные кодировки символов в мире определяются Unicode, но это не так. Есть десятки более часто используемых, таких как shift-jis, windows-1252 и т. д. Более того, преобразование в ascii обычно означает нормализацию символов, например преобразование ä в a, что вы, конечно, не можете сделать, предполагая, что ваша кодировка один байт на символ и маскирование байтов, отличных от ascii, как вы предлагаете! - person Jonathan Feinberg; 24.11.2009
comment
И Shift-JIS, и Windows-1252 наследуют младшие кодовые точки ASCII от ASCII. Таким образом, удаление всех символов с установленным старшим битом (что и делает мой ответ) работает в общем случае. Это не идеально, но во многих случаях достаточно. Если вы просто не знаете кодировку, то очевидно вы не сможете ее нормализовать. Что касается автоопределения, некоторые наборы символов в серии ISO-8859-* имеют так много совпадений и неоднозначностей, что их практически невозможно различить. - person intgr; 25.11.2009

Я бы попытался нормализовать строку, а затем закодировать ее. Что о :

import unicodedata
s = u"éèêàùçÇ"
print unicodedata.normalize('NFKD',s).encode('ascii','ignore')

Это работает, только если у вас есть юникод в качестве входных данных. Поэтому вы должны знать, что может кодировать функция, и декодировать ее. Если вы этого не сделаете, существуют эвристики обнаружения кодирования, но для коротких строк они ненадежны.

Конечно, вам может повезти, и выходные данные функции зависят от различных неизвестных кодировок, но с использованием ascii в качестве базы кода, поэтому они будут выделять одно и то же значение для байтов от 0 до 127 (например, utf-8 ).

В этом случае вы можете просто избавиться от нежелательных символов, отфильтровав их с помощью OrderedSets:

import string.printable # asccii chars
print "".join(OrderedSet(string.printable) & OrderedSet(s))

Или, если вы хотите вместо этого пробелы:

print("".join(((char if char in  string.printable else " ") for char in s )))

«Перевести» может помочь вам сделать то же самое.

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

person e-satis    schedule 24.11.2009

Под «защитой от дурака» понимается то, что функция не дает сбоев даже при самых неясных, невозможных входных данных — это означает, что вы можете передать функции случайные двоичные данные, и ОНА НИКОГДА НЕ ОТКАЖЕТСЯ, НЕЗАВИСИМО ЧТО. Вот что значит "защита от дурака".

Затем функция должна сделать все возможное, чтобы преобразовать кодировку в целевую. Если ему приходится выбрасывать весь мусор, который он не понимает, то это совершенно нормально и на самом деле является наиболее желательным результатом. Зачем пытаться спасти весь хлам? Просто выбросьте хлам. Скажите пользователю, что он не просто идиот из-за того, что использует что-то Microsoft, но нестандартный идиот из-за того, что использует что-то нестандартное из Microsoft... или за попытку отправить двоичные данные!

У меня точно такая же потребность (хотя моя потребность в PHP), и у меня также есть пользователи, которые по меньшей мере такие же дебилы, как и я, а иногда и более; однако они определенно приятнее и, без сомнения, более терпеливы.

Лучшее, что я нашел до сих пор (в PHP 5.3):

$fixed_string = iconv('ISO-8859-1', 'UTF-8//ИГНОРИРОВАТЬ//ПЕРЕВОД', $in_string);

Это пытается перевести все, что может, и просто отбрасывает весь мусор, в результате чего выводится допустимая строка UTF-8. Я также не смог сломать его или вызвать его сбой или отклонить какой-либо входящий текст или данные, даже загрузив его двоичными ненужными данными.

Найти iconv() и заставить его работать несложно; Что так сводит с ума и расточительно, так это чтение всего этого мусора и идиотизма, который, кажется, поддерживает так много программистов, имея дело с этим фиаско кодирования. Что стало с завидным (и респектабельным) менталитетом старой школы программирования «Сожгите идиотов»? Вернемся к основам. Используйте iconv() и выбросьте их мусор, и не будьте застенчивы, говоря им, что вы выбросили их мусор - короче говоря, не упустите возможность поколотить придурков, которые кормят вас мусором. И вы можете сказать им, что я сказал вам об этом.

person FYA    schedule 12.04.2011

Если все, что вы хотите сделать, это сохранить ASCII-совместимые символы и отбросить остальные, то в большинстве кодировок это сводится к удалению всех символов с установленным старшим битом, т. е. символов со значением выше 127. Это работает, потому что почти все наборы символов являются расширениями 7-битного ASCII.

Если это нормальная строка (т. е. не unicode), вам нужно декодировать ее в произвольном наборе символов (например, iso-8859-1, потому что она принимает любые байтовые значения), а затем закодировать в ascii, используя опцию ignore или replace для ошибок:

>>> orig = '1ä2äö3öü4ü'
>>> orig.decode('iso-8859-1').encode('ascii', 'ignore')
'1234'
>>> orig.decode('iso-8859-1').encode('ascii', 'replace')
'1??2????3????4??'

Шаг декодирования необходим, поскольку для использования кодирования вам нужна строка Unicode. Если у вас уже есть строка Unicode, это проще:

>>> orig = u'1ä2äö3öü4ü'
>>> orig.encode('ascii', 'ignore')
'1234'
>>> orig.encode('ascii', 'replace')
'1??2????3????4??'
person intgr    schedule 24.11.2009
comment
Также возможен прямой переход к ascii (как к объекту unicode): '1ä2äö3öü4ü'.decode("ascii", "ignore"). Тот факт, что вы используете упрощенный набор символов, не делает тип Unicode плохим выбором для текстовых строк IMO. - person u0b34a0f6ae; 24.11.2009
comment
Если ваша кодировка по умолчанию не является iso-8859-1, то ваша самая первая строка взорвется, когда вы попытаетесь декодировать эту исходную строку как iso-8859-1. - person Jonathan Feinberg; 24.11.2009
comment
@Jonathan Feinberg: Декодирование из ISO-8859-1 никогда не дает сбоев, потому что любая последовательность символов имеет определенное значение и является допустимой в ISO-8559-1. При чем здесь кодировка по умолчанию? Кодировки везде указываю явно. - person intgr; 24.11.2009
comment
@kaizer.se: он работает с 'ignore', но когда вы используете 'replace', он даст вам строку Unicode с: u'1\ufffd\ufffd2\ufffd\ufffd\ufffd\ufffd3\ufffd\ufffd\ufffd\ufffd4\ufffd\ufffd' - person intgr; 24.11.2009
comment
Выбрасывание символов, отличных от ASCII, часто похоже на выплескивание ребенка вместе с водой из ванны. Например. На типичном китайском веб-сайте (charset=gb2312, но не верьте этому, следует читать charset=some-superset-of-gb2312, вместо этого попробуйте кодек gbk) символы, совместимые с ASCII, в основном представляют собой синтаксис HTML; контент в основном китайский и испорчен всеми вашими трансформациями. Так же русский. Обратите внимание, что с koi8_r (но не cp1251) есть встроенный трюк: ucity = u"\u041c\u043e\u0441\u043a\u0432\u0430"; ''.join(chr(ord(c) & 0x7f) for c in ucity.encode('koi8_r')) производит 'mOSKWA'. - person John Machin; 26.11.2009