Python 2.7.1, кодеки и MySQLdb; почему установка кодировки файла не нужна

У меня есть небольшая программа на Python, которая читает операторы SQL из файла и запускает их в базе данных MySQL. Файл закодирован в UTF-8, и база данных также использует UTF-8.

Если я не устанавливаю кодировку базы данных, я получаю обычную ошибку, которую все спрашивают о «кодеке 'latin-1' не может закодировать символ ...». Итак, я установил базу данных и кодировку файла, используя

con.set_character_set('utf8')
fh = codecs.open(fname,'r','utf8')

Теперь он работает, но он также работает, когда я не устанавливаю кодировку файла (или просто использую встроенный open), только в базе данных. Под «работает» я имею в виду, что результирующие записи базы данных правильно отображаются в WordPress, который принимает UTF-8.

Если бы мне нужна была магия, я бы написал код на Ruby. Что делает Python в этом случае и почему не было необходимости указывать ему кодировку файла?

Излишне говорить, что я много искал по этому поводу, и мой Google-foo обычно довольно хорош. Здесь и в блогах есть масса сообщений о том, почему необходимо устанавливать кодировку и как это делать, но я не нашел ни одной о том, почему это иногда просто работает.

Изменить: я провел простой тест, используя файл, содержащий «Спасибо».

file
  E2 80 9C 54 68 61 6E 6B 20 79 6F 75 2E E2 80 9D
codecs utf8
  201C 54 68 61 6E 6B 20 79 6F 75 2E 201D

Попытка прочитать его с помощью codecs.open (myfile, 'r', 'ascii') вернула "UnicodeDecodeError: кодек 'ascii' не может декодировать байт 0xe2"

При чтении из файла получилась байтовая строка, поэтому кажется, что волшебство происходит при вставке в базу данных.


person Peter Wooster    schedule 16.01.2013    source источник
comment
вы делаете что-нибудь с содержимым файла, кроме передачи его в MySQL? Python может нормально читать в UTF8 с обычным старым открытым. по моему опыту, когда вы пытаетесь записать его обратно, обычно возникает обычная ошибка «кодек latin-1 не может кодировать».   -  person Anov    schedule 17.01.2013
comment
Я передаю итоговую базу данных WordPress, которая предполагает ее UTF8. Когда он работает правильно, текст отображается правильно, когда он не работает, текст показывает много странных символов. Он прекрасно читает это с обычным старым open, что меня смутило, так как я думал, что кодировка по умолчанию была ISO 8859-1.   -  person Peter Wooster    schedule 17.01.2013
comment
@anov, спасибо, в вопрос добавил определение работ.   -  person Peter Wooster    schedule 17.01.2013


Ответы (2)


Когда вы используете

fh = codecs.open(fname,'r','utf8')

fh.read() возвращает юникод. Если вы возьмете этот юникод и используете драйвер своей базы данных (например, mysql-python) для вставки данных в свою базу данных, тогда драйвер отвечает за преобразование юникода в байты. Драйвер использует кодировку, установленную

con.set_character_set('utf8')

Если вы используете

fh = open(fname, 'r')

тогда fh.read() возвращает строку байтов. Вы находитесь во власти тех байтов, которые оказались в fname. К счастью, согласно вашему сообщению, файл закодирован в UTF-8. Поскольку данные уже являются строкой байтов, драйвер не выполняет никакого кодирования, а просто передает строку байтов в базу данных.

В любом случае одна и та же строка байтов в кодировке UTF-8 вставляется в базу данных.


Давайте посмотрим на исходный код, определяющий codecs.open:

def open(filename, mode='rb', encoding=None, errors='strict', buffering=1):

    if encoding is not None:
        if 'U' in mode:
            # No automatic conversion of '\n' is done on reading and writing
            mode = mode.strip().replace('U', '')
            if mode[:1] not in set('rwa'):
                mode = 'r' + mode
        if 'b' not in mode:
            # Force opening of the file in binary mode
            mode = mode + 'b'
    file = __builtin__.open(filename, mode, buffering)
    if encoding is None:
        return file
    info = lookup(encoding)
    srw = StreamReaderWriter(file, info.streamreader, info.streamwriter, errors)
    # Add attributes to simplify introspection
    srw.encoding = encoding
    return srw

Обратите внимание, в частности, на то, что происходит, если encoding не установлен:

file = __builtin__.open(filename, mode, buffering)
if encoding is None:
     return file

Таким образом, codecs.open по сути то же самое, что и встроенный open, когда кодировка не установлена. Встроенная функция open возвращает файловый объект, read метод которого возвращает объект str. Он вообще не выполняет декодирование.

Напротив, когда вы указываете кодировку, codecs.open возвращает StreamReaderWriter с srw.encoding, установленным на encoding. Теперь, когда вы вызываете read метод StreamReaderWriter, возвращается объект unicode - обычно. Сначала необходимо декодировать объект str с использованием указанной кодировки.

В вашем примере объект str

In [19]: content
Out[19]: '\xe2\x80\x9cThank you.\xe2\x80\x9d'

и если вы укажете кодировку как 'ascii', тогда StreamReaderWriter попытается декодировать content, используя кодировку 'ascii':

In [20]: content.decode('ascii')

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)

Это неудивительно, поскольку кодировка ascii может декодировать только байты в диапазоне 0–127, а '\xe2', первый байт в content, имеет порядковое значение за пределами этого диапазона.


Для конкретности: Если вы не указываете кодировку:

In [13]: with codecs.open(filename, 'r') as f:
   ....:     content = f.read() 

In [14]: content
Out[14]: '\xe2\x80\x9cThank you.\xe2\x80\x9d'

content is a str.

Если вы укажете допустимую кодировку:

In [22]: with codecs.open(filename, 'r', encoding = 'utf-8') as f:
   ....:     content = f.read()


In [23]: content
Out[23]: u'\u201cThank you.\u201d'

content is a unicode.

Если вы указали недопустимую кодировку:

In [25]: with codecs.open(filename, 'r', 'ascii') as f:
   ....:     content = f.read()
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)

Вы получите UnicodeDecodeError.

person unutbu    schedule 19.01.2013
comment
Значит ли это, что я всегда должен использовать codecs.open, если я знаю кодировку? - person Peter Wooster; 20.01.2013
comment
В самом деле, лучше быть откровенным! - person unutbu; 20.01.2013
comment
Я обновил свой вопрос. Похоже, что кодирование не выполняется, если ничего не указано. - person Peter Wooster; 25.01.2013
comment
Это объясняет, что происходит с чтением файла. Спасибо. - person Peter Wooster; 25.01.2013

В этом руководстве по Unicode в Python , в 4-м абзаце написано, чем, описывая codecs.open(filename, mode, [encoding]) функцию, которую вы используете:

encoding - строка, дающая кодировку для использования; если оставить значение None, возвращается обычный файловый объект Python, принимающий 8-битные строки.

Кроме того, в ссылке на объект File сказано что

(file.encoding) также может быть None, и в этом случае файл использует кодировку системы по умолчанию для преобразования строк Unicode.

Вызывая codecs.open() без параметра кодировки, объект File возвращается с атрибутом кодировки None (проверено), таким образом, используется системное значение по умолчанию для Unicode, которым в вашем случае должен был быть UTF-8. Это объясняет, почему он работает так аккуратно, когда вы не говорите откровенно.

person matehat    schedule 19.01.2013
comment
как определить кодировку системы по умолчанию? использует открытый эквивалент использования codecs.open без указания кодировки? - person Peter Wooster; 20.01.2013
comment
как это работает, когда я использую встроенную функцию open ()? Я отредактировал вопрос, чтобы добавить это. - person Peter Wooster; 20.01.2013
comment
Да, использование open() возвращает объект File с атрибутом кодирования None, который совпадает с codecs.open без параметра кодирования. Вы можете узнать кодировку вашей системы по умолчанию, выполнив sys.getdefaultencoding(). Чтобы изменить его, см. stackoverflow.com/questions/2276200/ - person matehat; 21.01.2013
comment
Спасибо, у меня Mac, и sys.getdefaultencoding () возвращает ascii. Так что не совсем понятно, почему это работает. - person Peter Wooster; 21.01.2013
comment
Понятно. Можете ли вы предоставить немного больше из своего кода, например, строки, которые отправляют содержимое fh в базу данных? - person matehat; 22.01.2013
comment
Вы можете найти весь код на github.com/PeterWooster/SQL-Tools. /blob/master/SQLRunner.py - person Peter Wooster; 22.01.2013
comment
Используйте 1_. sys.getdefaultencoding() - это кодировка по умолчанию при преобразовании из Unicode в байтовые строки, обычно это ascii в Python 2.X и utf-8 в Python 3.X. - person Mark Tolonen; 22.01.2013
comment
@marktolonen Спасибо, кодировка файловой системы - utf8, что объясняет проблему. Если бы вы могли опубликовать ответ, который подробно описывает этот комментарий, в том числе о том, как он настроен и т. Д., Награда ваша. - person Peter Wooster; 24.01.2013
comment
Я не уверен, что это так, в документации указано, что он возвращает имя кодировки, используемой для преобразования имен файлов Unicode в имена системных файлов, или None, если используется системная кодировка по умолчанию. Так что он используется для имен файлов. Теоретически он не может использоваться для декодирования потоков Unicode. - person matehat; 24.01.2013
comment
Я обновил свой вопрос, чтобы добавить результат простого теста. ни кодировка по умолчанию, ни кодировка файловой системы не используются. Он просто читает строку байтов без указания кодировки. - person Peter Wooster; 25.01.2013
comment
Ага, вообще-то я неправильно понял. Как говорит @matehat, getfilesystemencoding() используется для имен файлов. Когда вы читаете какие-либо данные, будь то файл, сокет и т. Д., Вы должны знать, в какой они кодировке, чтобы преобразовать их в текст Unicode. - person Mark Tolonen; 25.01.2013