Unicode (UTF-8) для читання та запису у файли на Python


329

У мене є певна мозкова недостатність у розумінні читання та запису тексту у файл (Python 2.4).

# The string, which has an a-acute in it.
ss = u'Capit\xe1n'
ss8 = ss.encode('utf8')
repr(ss), repr(ss8)

("u'Capit \ xe1n '", "' Capit \ xc3 \ xa1n '")

print ss, ss8
print >> open('f1','w'), ss8

>>> file('f1').read()
'Capit\xc3\xa1n\n'

Тож я набираю Capit\xc3\xa1nсвого улюбленого редактора, у файл f2.

Тоді:

>>> open('f1').read()
'Capit\xc3\xa1n\n'
>>> open('f2').read()
'Capit\\xc3\\xa1n\n'
>>> open('f1').read().decode('utf8')
u'Capit\xe1n\n'
>>> open('f2').read().decode('utf8')
u'Capit\\xc3\\xa1n\n'

Чого я тут не розумію? Очевидно, що я маю трохи життєвої магії (або доброго глузду). Що вводить один текст у текстові файли для отримання належних перетворень?

Що я справді не в змозі зробити тут, то в чому полягає суть представлення UTF-8, якщо ви насправді не можете змусити Python розпізнати його, коли воно надходить ззовні. Можливо, я повинен просто скинути рядок JSON і використовувати це натомість, оскільки це є передбачуване представлення! Більше того, чи існує представлення ASCII цього об’єкта Unicode, який Python розпізнає та декодує, приходячи з файлу? Якщо так, то як мені це отримати?

>>> print simplejson.dumps(ss)
'"Capit\u00e1n"'
>>> print >> file('f3','w'), simplejson.dumps(ss)
>>> simplejson.load(open('f3'))
u'Capit\xe1n'

Відповіді:


110

У позначеннях

u'Capit\xe1n\n'

"\ xe1" являє собою лише один байт. "\ x" повідомляє вам, що "e1" знаходиться в шістнадцятці. Коли пишеш

Capit\xc3\xa1n

у вашому файлі у вас є "\ xc3". Це 4 байти, і у вашому коді ви їх читаєте всі. Це можна побачити, коли ви їх відображаєте:

>>> open('f2').read()
'Capit\\xc3\\xa1n\n'

Ви можете бачити, що нахил косою рисою повертається. Отже, у вашому рядку є чотири байти: "\", "x", "c" та "3".

Редагувати:

Як зазначали інші у своїх відповідях, вам слід просто ввести символи в редакторі, а потім ваш редактор повинен обробити перетворення в UTF-8 і зберегти його.

Якщо у вас фактично є рядок у такому форматі, ви можете використовувати string_escapeкодек, щоб розшифрувати його у звичайний рядок:

In [15]: print 'Capit\\xc3\\xa1n\n'.decode('string_escape')
Capitán

Результат - рядок, кодований в UTF-8, де символ наголосу представлений двома байтами, які були записані \\xc3\\xa1в початковому рядку. Якщо ви хочете мати рядок Unicode, вам доведеться знову декодувати за допомогою UTF-8.

До вашої редакції: у вас немає файлу UTF-8. Щоб насправді побачити, як це виглядатиме:

s = u'Capit\xe1n\n'
sutf8 = s.encode('UTF-8')
open('utf-8.out', 'w').write(sutf8)

Порівняйте вміст файлу utf-8.outзі змістом файлу, який ви зберегли разом із редактором.


Отже, який сенс закодованого формату utf-8, якщо python може читати у файлах, використовуючи його? Іншими словами, чи є представлення ascii, яке python прочитає в \ xc3 як 1 байт?
Грегг Лінд

4
Відповідь на ваше питання "Отже, у чому сенс ..." - "Му". (оскільки Python може читати файли, закодовані в UTF-8). Що стосується вашого другого питання: \ xc3 не є частиною набору ASCII. Можливо, ви маєте на увазі "8-бітове кодування". Вас плутає Unicode та кодування; це добре, багато хто.
tzot

8
Спробуйте прочитати це як буквар: joelonsoftware.com/articles/Unicode.html
tzot

Примітка: u'\xe1'це одна кодова точка Unicode, U+00e1яка може бути представлена ​​за допомогою 1 або більше байтів залежно від кодування символів (це 2 байти у utf-8). b'\xe1'це один байт (число 225), яка буква, якщо вона може бути представлена, залежить від кодування символів, використовуваного для її розшифровки, наприклад, це б( U+0431) в cp1251, с( U+0441) в cp866 і т.д.
jfs

11
Дивовижно, скільки британських кодерів кажуть "просто використовувати ascii", а потім не усвідомлюють, що знак £ - це не так. Більшість не знають, що ascii! = Локальна кодова сторінка (тобто латинська1).
Danny Staple

712

Замість того, щоб возитися з методами кодування та декодування, мені легше вказати кодування при відкритті файлу. ioМодуль (додано в Python 2.6) забезпечує io.openфункцію, яка має параметр кодування.

Використовуйте відкритий метод з ioмодуля.

>>>import io
>>>f = io.open("test", mode="r", encoding="utf-8")

Потім після виклику функції read () f повертається закодований об'єкт Unicode.

>>>f.read()
u'Capit\xe1l\n\n'

Зауважте, що в Python 3 io.openфункція є псевдонімом вбудованої openфункції. Вбудована відкрита функція підтримує лише аргумент кодування в Python 3, а не Python 2.

Редагувати: Раніше ця відповідь рекомендувала модуль кодеків . Модуль кодеків може спричинити проблеми при змішуванні, read()іreadline() тепер ця відповідь рекомендує натомість модуль io .

Використовуйте відкритий метод з модуля кодеків.

>>>import codecs
>>>f = codecs.open("test", "r", "utf-8")

Потім після виклику функції read () f повертається закодований об'єкт Unicode.

>>>f.read()
u'Capit\xe1l\n\n'

Якщо ви знаєте кодування файлу, використання пакету кодеків стане набагато менш заплутаним.

Див. Http://docs.python.org/library/codecs.html#codecs.open


74
Ідеально підходить для запису файлів, замість того, open(file,'w')щоб codecs.open(file,'w','utf-8')вирішувати
Метт Коннолі

1
Цю відповідь я шукав :)
Джастін

6
Чи також codecs.open(...)метод повністю відповідає with open(...):стилю, де withпіклується про закриття файлу після того, як все зроблено? Здається, все одно працює.
try-catch-нарешті

2
@ try-catch-нарешті Так. Я with codecs.open(...) as f:весь час використовую.
Тім Сваст

6
Я б хотіла, щоб я могла підтримати це сто разів. Протягом декількох днів агонізуючи проблеми кодування, спричинені безліччю неоднозначних даних, і читати про кодування розплющеними очима, ця відповідь нагадує воду в пустелі. Хотілося б, щоб я бачив це раніше.
Майк Жирард

45

Тепер все, що вам потрібно в Python3, - це open(Filename, 'r', encoding='utf-8')

[Редагувати 10.02.2016 для запиту на роз'яснення]

Python3 додав параметр кодування до своєї відкритої функції. Наступна інформація про відкриту функцію збирається звідси: https://docs.python.org/3/library/functions.html#open

open(file, mode='r', buffering=-1, 
      encoding=None, errors=None, newline=None, 
      closefd=True, opener=None)

Кодування - це ім'я кодування, яке використовується для декодування або кодування файлу. Це слід використовувати лише в текстовому режимі. Кодування за замовчуванням залежить від платформи (все, що повертається locale.getpreferredencoding () ), але будь-яке кодування тексту, що підтримується Python, може бути використане. Перегляньте модуль кодеків для списку підтримуваних кодувань.

Таким чином, додаючи encoding='utf-8'як параметр до відкритої функції, читання та запис файлів робиться як utf8 (що також тепер кодує за замовчуванням все, що робиться в Python.)


Чи можете ви детальніше розглянути свою відповідь, додавши трохи більше опису про рішення, яке ви надаєте?
аборисон

2
Схоже, це доступно в python 2 за допомогою модуля кодеків - codecs.open('somefile', encoding='utf-8') stackoverflow.com/a/147756/149428
Тейлор Едмістон,

18

Отже, я знайшов рішення для того, що шукаю, а саме:

print open('f2').read().decode('string-escape').decode("utf-8")

Тут є корисні незвичайні кодеки. Це особливе читання дозволяє приймати представлення UTF-8 зсередини Python, копіювати їх у файл ASCII та зчитувати їх у Unicode. Під декодуванням "string-escape" косої риски не подвоюється.

Це дозволяє здійснити ту подорож, яку я собі уявляв.


1
Хороша відповідь, я перевірений як рішення, так (codecs.open(file,"r","utf-8")і просто, open(file,"r").read().decode("utf-8")і обидва працювали прекрасно.
Орел

Я отримую "TypeError: очікувана str, байти або os.PathLike об'єкт, а не _io.TextIOWrapper", будь-яка ідея чому?
JinSnow

Я думаю, враховуючи кількість оновлених матеріалів, було б чудовою ідеєю прийняти другу відповідь :)
Jacquot

14
# -*- encoding: utf-8 -*-

# converting a unknown formatting file in utf-8

import codecs
import commands

file_location = "jumper.sub"
file_encoding = commands.getoutput('file -b --mime-encoding %s' % file_location)

file_stream = codecs.open(file_location, 'r', file_encoding)
file_output = codecs.open(file_location+"b", 'w', 'utf-8')

for l in file_stream:
    file_output.write(l)

file_stream.close()
file_output.close()

14

Насправді, це працювало для мене для читання файлу з кодуванням UTF-8 на Python 3.2:

import codecs
f = codecs.open('file_name.txt', 'r', 'UTF-8')
for line in f:
    print(line)

6

Щоб прочитати в рядку Unicode і потім надіслати HTML, я зробив це:

fileline.decode("utf-8").encode('ascii', 'xmlcharrefreplace')

Корисно для http-серверів, що працюють на python.


6

Ви натрапили на загальну проблему з кодуваннями: Як я можу сказати, в якому кодуванні знаходиться файл?

Відповідь: Ви не можете, якщо формат файлів не передбачає цього. Наприклад, XML починається з:

<?xml encoding="utf-8"?>

Цей заголовок був ретельно вибраний, щоб його можна було прочитати незалежно від кодування. У вашому випадку такої підказки немає, отже, ні ваш редактор, ні Python не мають уявлення про те, що відбувається. Тому ви повинні використовувати codecsмодуль і використовуватиcodecs.open(path,mode,encoding) який забезпечує відсутній біт у Python.

Що стосується вашого редактора, ви повинні перевірити, чи він пропонує певний спосіб встановити кодування файлу.

Суть UTF-8 полягає в тому, щоб мати можливість кодувати 21-бітові символи (Unicode) як 8-бітний потік даних (адже це єдине, з чим можуть працювати всі комп'ютери у світі). Але оскільки більшість ОС передують ері Unicode, вони не мають відповідних інструментів для прикріплення інформації про кодування до файлів на жорсткому диску.

Наступний випуск - представництво в Python. Це чудово пояснюється в коментарі heikogerlach . Ви повинні розуміти, що на вашій консолі може відображатися лише ASCII. Щоб відобразити Unicode або що-небудь> = charcode 128, він повинен використовувати деякі засоби втечі. У своєму редакторі ви не повинні вводити рядок відображення, що відображається, але що означає рядок (у цьому випадку ви повинні ввести umlaut та зберегти файл).

Це означає, що ви можете використовувати функцію Python eval (), щоб перетворити рядок, що вийшов, у рядок:

>>> x = eval("'Capit\\xc3\\xa1n\\n'")
>>> x
'Capit\xc3\xa1n\n'
>>> x[5]
'\xc3'
>>> len(x[5])
1

Як бачимо, рядок "\ xc3" перетворений в єдиний символ. Зараз це 8-бітна рядок, кодована UTF-8. Щоб отримати Unicode:

>>> x.decode('utf-8')
u'Capit\xe1n\n'

Грегг Лінд запитав: Я думаю, тут відсутні деякі фрагменти: файл f2 містить: hex:

0000000: 4361 7069 745c 7863 335c 7861 316e  Capit\xc3\xa1n

codecs.open('f2','rb', 'utf-8')наприклад, читає їх усі окремими символами (очікується) Чи є спосіб записати у файл в ASCII, який би спрацював?

Відповідь: Це залежить від того, що ви маєте на увазі. ASCII не може представляти символи> 127. Тому вам потрібен якийсь спосіб сказати "наступні кілька символів означають щось особливе", що і робить послідовність "\ x". У ньому сказано: Наступні два символи - це код одного символу. "\ u" робить те ж саме, використовуючи чотири символи для кодування Unicode до 0xFFFF (65535).

Таким чином, ви не можете безпосередньо записати Unicode в ASCII (оскільки ASCII просто не містить однакових символів). Ви можете записати це у вигляді рядків (як у f2); у цьому випадку файл може бути представлений як ASCII. Або ви можете записати його як UTF-8, і в цьому випадку вам потрібен безпечний 8-бітний потік.

Ваше рішення з використанням decode('string-escape')працює, але ви повинні знати, скільки пам'яті ви використовуєте: Втричі більша кількість використання codecs.open().

Пам'ятайте, що файл - це лише послідовність байт з 8 бітами. Ні біти, ні байти не мають значення. Це ти, хто каже "65 означає" А ". Оскільки \xc3\xa1має стати "à", але комп'ютер не має можливості знати, ви повинні сказати це, вказавши кодування, яке було використано під час запису файлу.


Я думаю, тут відсутні деякі фрагменти: файл f2 містить: hex: 0000000: 4361 7069 745c 7863 335c 7861 316e 0a Capit \ xc3 \ xa1n. codecs.open ('f2', 'rb', 'utf-8'), наприклад, читає їх усі окремими символами (очікується) Чи є спосіб записати у файл в ascii, який би спрацював?
Грегг Лінд

6

крім codecs.open(), можна використовувати io.open()для роботи з Python2 або Python3 для читання / запису файлу unicode

приклад

import io

text = u'á'
encoding = 'utf8'

with io.open('data.txt', 'w', encoding=encoding, newline='\n') as fout:
    fout.write(text)

with io.open('data.txt', 'r', encoding=encoding, newline='\n') as fin:
    text2 = fin.read()

assert text == text2


Так, краще використовувати io; Але я написав з твердженням , як це with io.open('data.txt', 'w', 'utf-8') as file:і є помилка: TypeError: an integer is required. Після того, як я змінився, with io.open('data.txt', 'w', encoding='utf-8') as file:і це спрацювало.
Еван Ху

5

Ну, ваш улюблений текстовий редактор не розуміє, що \xc3\xa1вони повинні бути буквами символів, але інтерпретує їх як текст. Ось чому ви отримуєте подвійні риски в останньому рядку - тепер це справжній зворотний нахил + xc3тощо у вашому файлі.

Якщо ви хочете читати і записувати закодовані файли в Python, краще використовувати модуль кодеків .

Вставлення тексту між терміналом і програмами важко, тому що ви не знаєте, яка програма буде інтерпретувати ваш текст, використовуючи кодування. Ви можете спробувати наступне:

>>> s = file("f1").read()
>>> print unicode(s, "Latin-1")
Capitán

Потім вставте цей рядок у свій редактор і переконайтесь, що він зберігає його, використовуючи Latin-1. За припущенням, що буфер обміну не обтягує струну, туди і назад слід працювати.


4

Послідовність \ x .. - те, що є специфічним для Python. Це не універсальна послідовність втечі байтів.

Те, як ви фактично входите в кодований UTF-8 non-ASCII, залежить від вашої ОС та / або вашого редактора. Ось як це робиться в Windows . Для OS X , щоб увійти з гострим наголосом ви можете просто натиснути + , потім , і майже всі текстові редактори в OS X підтримка UTF-8.optionEA


3

Ви також можете покращити оригінальну open()функцію для роботи з файлами Unicode, замінивши її на місці, використовуючи partialфункцію. Краса цього рішення полягає в тому, що вам не потрібно змінювати старий код. Це прозоро.

import codecs
import functools
open = functools.partial(codecs.open, encoding='utf-8')

1

Я намагався розібрати iCal за допомогою Python 2.7.9:

з календаря імпорту icalendar

Але я отримував:

 Traceback (most recent call last):
 File "ical.py", line 92, in parse
    print "{}".format(e[attr])
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe1' in position 7: ordinal not in range(128)

і це було виправлено просто:

print "{}".format(e[attr].encode("utf-8"))

(Тепер він може друкувати liké á böss.)


0

Я знайшов найпростіший підхід, змінивши кодування за замовчуванням усього сценарію на "UTF-8":

import sys
reload(sys)
sys.setdefaultencoding('utf8')

будь-який open, printабо іншу заяву буде просто використовуватиutf8 .

Працює принаймні для Python 2.7.9 .

Thx переходить до https://markhneedham.com/blog/2015/05/21/python-unicodeencodeerror-ascii-codec-cant-encode-character-uxfc-in-position-11-ordinal-not-in-range128/ ( подивитись на кінець).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.