Zapisz do pliku UTF-8 w Pythonie

204

Jestem naprawdę mylony z codecs.open function. Kiedy robię:

file = codecs.open("temp", "w", "utf-8")
file.write(codecs.BOM_UTF8)
file.close()

Daje mi to błąd

UnicodeDecodeError: Kodek „ascii” nie może dekodować bajtu 0xef w pozycji 0: porządek poza zakresem (128)

Jeśli zrobię:

file = open("temp", "w")
file.write(codecs.BOM_UTF8)
file.close()

To działa dobrze.

Pytanie brzmi, dlaczego pierwsza metoda zawodzi? A jak wstawić BOM?

Jeśli druga metoda jest poprawna, to po co codecs.open(filename, "w", "utf-8")?

John Jiang
źródło
54
Nie używaj BOM w UTF-8. Proszę.
tchrist
7
@tchrist Huh? Dlaczego nie?
Salman von Abbas
8
@SalmanPK BOM nie jest potrzebny w UTF-8 i jedynie zwiększa złożoność (np. Nie można po prostu połączyć plików BOM i uzyskać poprawnego tekstu). Zobacz pytania i odpowiedzi ; nie przegap wielkiego komentarza pod Q
Alois Mahdal

Odpowiedzi:

271

Myślę, że problem polega na tym, że codecs.BOM_UTF8jest to ciąg bajtów, a nie ciąg Unicode. Podejrzewam, że procedura obsługi plików próbuje zgadnąć, co naprawdę masz na myśli na podstawie „Mam pisać Unicode jako tekst zakodowany w UTF-8, ale dałeś mi ciąg bajtów!”

Spróbuj napisać ciąg Unicode dla znaku kolejności bajtów (tj. Unicode U + FEFF), aby plik po prostu kodował go jako UTF-8:

import codecs

file = codecs.open("lol", "w", "utf-8")
file.write(u'\ufeff')
file.close()

(To wydaje się dawać właściwą odpowiedź - plik z bajtami EF BB BF.)

EDYCJA: Sugestia S. Lott, aby użyć „utf-8-sig” jako kodowania, jest lepsza niż bezpośrednie pisanie BOM, ale zostawię tę odpowiedź tutaj, ponieważ wyjaśnia ona, co wcześniej się nie udawało.

Jon Skeet
źródło
Ostrzeżenie: otwieranie i otwieranie to nie to samo. Jeśli wykonasz polecenie „z otwartego importu kodeków”, NIE będzie ono takie samo, jak po prostu wpisz „otwarte”.
Apache
2
zamiast tego możesz również użyć codecs.open ('test.txt', 'w', 'utf-8-sig')
beta-zamknięte
1
Otrzymuję komunikat „TypeError: wymagana jest liczba całkowita (mam typ str)”. Nie rozumiem, co tu robimy. Czy ktoś może pomóc? Muszę dołączyć ciąg (akapit) do pliku tekstowego. Czy muszę najpierw przekonwertować to na liczbę całkowitą?
Mugen
@Mugen: Dokładny kod, który napisałem, działa dobrze, o ile widzę. Proponuję zadać nowe pytanie pokazujące dokładnie, jaki masz kod i gdzie występuje błąd.
Jon Skeet,
@Mugen musisz zadzwonić codecs.openzamiast po prostuopen
northben
179

Przeczytaj następujące: http://docs.python.org/library/codecs.html#module-encodings.utf_8_sig

Zrób to

with codecs.open("test_output", "w", "utf-8-sig") as temp:
    temp.write("hi mom\n")
    temp.write(u"This has ♭")

Plik wynikowy to UTF-8 z oczekiwanym zestawieniem komponentów.

S.Lott
źródło
2
Dzięki. To działało (Windows 7 x64, Python 2.7.5 x64). To rozwiązanie działa dobrze po otwarciu pliku w trybie „a” (dołącz).
Mohamad Fakih
To nie działało dla mnie, Python 3 w systemie Windows. Musiałem to zrobić zamiast tego z open (nazwa_pliku, 'wb') jako plik bomfile: bomfile.write (codecs.BOM_UTF8), a następnie ponownie otwórz plik w celu dołączenia.
Dustin Andrews,
Może dodać temp.close()?
user2905353
2
@ user2905353: nie wymagany; to jest obsługiwane przez zarządzanie kontekstowego z open.
Matheburg
11

@ S-Lott podaje właściwą procedurę, ale rozwijając kwestie związane z Unicode , Python interpreter może zapewnić więcej informacji.

Jon Skeet ma rację (nietypowy) w odniesieniu do codecsmodułu - zawiera ciągi bajtów:

>>> import codecs
>>> codecs.BOM
'\xff\xfe'
>>> codecs.BOM_UTF8
'\xef\xbb\xbf'
>>> 

Wybierając inną nit, BOMma standardową nazwę Unicode i można ją wprowadzić jako:

>>> bom= u"\N{ZERO WIDTH NO-BREAK SPACE}"
>>> bom
u'\ufeff'

Jest również dostępny przez unicodedata:

>>> import unicodedata
>>> unicodedata.lookup('ZERO WIDTH NO-BREAK SPACE')
u'\ufeff'
>>> 
gimel
źródło
8

Używam polecenia file * nix do konwersji nieznanego pliku zestawu znaków w plik utf-8

# -*- 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()
Ricardo
źródło
1
Użyj # coding: utf8zamiast tego # -*- coding: utf-8 -*-jest o wiele łatwiejsze do zapamiętania.
pokaż0k