Zapisujesz tekst Unicode do pliku tekstowego?

225

Wyciągam dane z dokumentu Google, przetwarzam je i zapisuję w pliku (który ostatecznie wkleję na stronie Wordpress).

Ma kilka symboli spoza ASCII. Jak przekonwertować je bezpiecznie na symbole, których można używać w źródle HTML?

Obecnie po drodze konwertuję wszystko na Unicode, łączę to wszystko w łańcuch Python, a następnie:

import codecs
f = codecs.open('out.txt', mode="w", encoding="iso-8859-1")
f.write(all_html.encode("iso-8859-1", "replace"))

W ostatnim wierszu występuje błąd kodowania:

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

Częściowe rozwiązanie:

Ten Python działa bez błędu:

row = [unicode(x.strip()) if x is not None else u'' for x in row]
all_html = row[0] + "<br/>" + row[1]
f = open('out.txt', 'w')
f.write(all_html.encode("utf-8"))

Ale jeśli otworzę właściwy plik tekstowy, widzę wiele symboli, takich jak:

Qur’an 

Może muszę pisać do czegoś innego niż plik tekstowy?

Szymon
źródło
1
Program, którego używasz do jego otwierania, nie interpretuje poprawnie tekstu UTF-8. Powinien mieć opcję otwarcia pliku jako UTF-8.
Thomas K

Odpowiedzi:

322

Zajmuj się wyłącznie obiektami Unicode w jak największym stopniu, dekodując je do obiektów Unicode, gdy je dostaniesz, i kodując je w razie potrzeby po wyjściu.

Jeśli Twój ciąg znaków jest w rzeczywistości obiektem Unicode, musisz go przekonwertować na obiekt łańcucha zakodowany w Unicode przed zapisaniem go w pliku:

foo = u'Δ, Й, ק, ‎ م, ๗, あ, 叶, 葉, and 말.'
f = open('test', 'w')
f.write(foo.encode('utf8'))
f.close()

Po ponownym odczytaniu tego pliku otrzymasz ciąg kodowany w Unicode, który możesz zdekodować do obiektu Unicode:

f = file('test', 'r')
print f.read().decode('utf8')
quasistoiczny
źródło
Dzięki. Działa to bezbłędnie, ale jeśli otworzę plik tekstowy, zobaczę kilka dziwnych symboli :) Muszę skopiować i wkleić tekst na stronie Wordpress (nie pytaj). Czy jest jakiś sposób, aby rzeczywiście wydrukować znajdujące się tam symbole? Chyba nie do pliku txt, prawda, ale może do czegoś innego?
simon
1
Czego używasz do otwarcia pliku tekstowego? Zgaduję, że masz system Windows i otwierasz go w Notatniku, który nie jest zbyt inteligentny w kodowaniu. Co się stanie, gdy otworzysz go w Wordpadzie?
quasistoik
@ quasistoic, skąd pochodzi metoda pliku ?
Omar Cusma Fait
Musiałem włączyć tryb binarny, tj. F = open ('test', 'wb'), jak opisano w stackoverflow.com/a/5513856/6580199 - w przeciwnym razie dostałbym argument "TypeError: write () musi być str, nie bajtów ”
Benji
72

W Pythonie 2.6+ możesz użyćio.open() domyślnego ( wbudowanegoopen() ) w Python 3:

import io

with io.open(filename, 'w', encoding=character_encoding) as file:
    file.write(unicode_text)

Może być wygodniejsze, jeśli trzeba pisać tekst przyrostowo (nie trzeba dzwonić unicode_text.encode(character_encoding)wiele razy). W przeciwieństwie do codecsmodułu, iomoduł ma odpowiednią uniwersalną obsługę nowego wiersza.

jfs
źródło
1
Człowieku, spędziłem tyle czasu, aby to znaleźć! Dziękuję Ci!
Georgy Gobozov
2
Działa to również w Pythonie 3 (oczywiste, ale nadal warte podkreślenia).
Hippo
37

Obsługa ciągów znaków Unicode jest już ustandaryzowana w Pythonie 3.

  1. Znaki są już zapisane w Unicode (32-bit) w pamięci
  2. Wystarczy otworzyć plik w utf-8
    (32-bitowa konwersja utf-8 w Unicode na zmienną długość bajtu jest wykonywana automatycznie z pamięci do pliku).

    out1 = "(嘉南大圳 ㄐㄧㄚ ㄋㄢˊ ㄉㄚˋ ㄗㄨㄣˋ )"
    fobj = open("t1.txt", "w", encoding="utf-8")
    fobj.write(out1)
    fobj.close()
    
David M Lee
źródło
Ale to nie działa w Pythonie 2, prawda? (Powinienem powiedzieć, że w tym kodzie Python 3 wygląda tak zwięźle i rozsądnie)
Liwen Zhao
nie powinno działać na Python 2. Pozostajemy na Python 3. 3 jest o wiele lepszy.
david m lee
18

Plik otwierany przez codecs.opento plik, który pobiera unicodedane, koduje je iso-8859-1i zapisuje w pliku. Jednak to, co próbujesz napisać, nie jest unicode; bierzesz unicodei kodujesz w iso-8859-1 sobie . Tak właśnie działa unicode.encodemetoda, a wynikiem kodowania łańcucha znaków Unicode jest testowanie ( strtyp).

Powinieneś albo użyć normalnego open()i samodzielnie kodować Unicode, albo (zazwyczaj lepszy pomysł) użyć codecs.open()i nie kodować danych samodzielnie.

Thomas Wouters
źródło
17

Przedmowa: czy Twój widz będzie działał?

Upewnij się, że twoja przeglądarka / edytor / terminal (jakkolwiek wchodzisz w interakcję z plikiem zakodowanym w utf-8) może go odczytać. Jest to często problem w systemie Windows , na przykład w Notatniku.

Zapisujesz tekst Unicode do pliku tekstowego?

W Python 2 użyj openz iomodułu (jest to to samo, co wbudowane openw Python 3):

import io

Najlepsze praktyki, ogólnie rzecz biorąc, używaj UTF-8do zapisywania do plików (nie musimy nawet martwić się kolejnością bajtów z utf-8).

encoding = 'utf-8'

utf-8 jest najnowocześniejszym i powszechnie używanym kodowaniem - działa we wszystkich przeglądarkach internetowych, w większości edytorów tekstu (sprawdź ustawienia, jeśli masz problemy) i na większości terminali / powłok.

W systemie Windows możesz spróbować, utf-16lejeśli jesteś ograniczony do wyświetlania wyników w Notatniku (lub innej ograniczonej przeglądarce).

encoding = 'utf-16le' # sorry, Windows users... :(

I po prostu otwórz go za pomocą menedżera kontekstu i wypisz swoje znaki Unicode:

with io.open(filename, 'w', encoding=encoding) as f:
    f.write(unicode_object)

Przykład użycia wielu znaków Unicode

Oto przykład, który próbuje zmapować każdy możliwy znak o szerokości do trzech bitów (4 to maksimum, ale byłoby to nieco daleko) od cyfrowej reprezentacji (w liczbach całkowitych) do zakodowanego wydruku, wraz z jego nazwą, jeśli możliwe (wstaw to do pliku o nazwie uni.py):

from __future__ import print_function
import io
from unicodedata import name, category
from curses.ascii import controlnames
from collections import Counter

try: # use these if Python 2
    unicode_chr, range = unichr, xrange
except NameError: # Python 3
    unicode_chr = chr

exclude_categories = set(('Co', 'Cn'))
counts = Counter()
control_names = dict(enumerate(controlnames))
with io.open('unidata', 'w', encoding='utf-8') as f:
    for x in range((2**8)**3): 
        try:
            char = unicode_chr(x)
        except ValueError:
            continue # can't map to unicode, try next x
        cat = category(char)
        counts.update((cat,))
        if cat in exclude_categories:
            continue # get rid of noise & greatly shorten result file
        try:
            uname = name(char)
        except ValueError: # probably control character, don't use actual
            uname = control_names.get(x, '')
            f.write(u'{0:>6x} {1}    {2}\n'.format(x, cat, uname))
        else:
            f.write(u'{0:>6x} {1}  {2}  {3}\n'.format(x, cat, char, uname))
# may as well describe the types we logged.
for cat, count in counts.items():
    print('{0} chars of category, {1}'.format(count, cat))

Powinno to działać w kolejności około minuty i można wyświetlić plik danych, a jeśli przeglądarka plików może wyświetlać Unicode, zobaczysz go. Informacje o kategoriach można znaleźć tutaj . Na podstawie obliczeń możemy prawdopodobnie poprawić nasze wyniki, wykluczając kategorie Cn i Co, które nie mają z nimi żadnych symboli.

$ python uni.py

Wyświetli odwzorowanie szesnastkowe, kategorię , symbol (chyba że nie można uzyskać nazwy, więc prawdopodobnie znak kontrolny) i nazwę symbolu. na przykład

Polecam lessna Unixie lub Cygwinie (nie drukuj / cat całego pliku na wyjście):

$ less unidata

np. wyświetli podobne do następujących wierszy, z których próbkowałem z niego przy użyciu Pythona 2 (Unicode 5.2):

     0 Cc NUL
    20 Zs     SPACE
    21 Po  !  EXCLAMATION MARK
    b6 So    PILCROW SIGN
    d0 Lu  Ð  LATIN CAPITAL LETTER ETH
   e59 Nd    THAI DIGIT NINE
  2887 So    BRAILLE PATTERN DOTS-1238
  bc13 Lo    HANGUL SYLLABLE MIH
  ffeb Sm    HALFWIDTH RIGHTWARDS ARROW

Mój Python 3.5 z Anacondy ma Unicode 8.0, przypuszczam, że większość 3.

Aaron Hall
źródło
3

Jak wydrukować znaki Unicode do pliku:

Zapisz to w pliku: foo.py:

#!/usr/bin/python -tt
# -*- coding: utf-8 -*-
import codecs
import sys 
UTF8Writer = codecs.getwriter('utf8')
sys.stdout = UTF8Writer(sys.stdout)
print(u'e with obfuscation: é')

Uruchom i potokuj dane wyjściowe do pliku:

python foo.py > tmp.txt

Otwórz tmp.txt i zajrzyj do środka, zobaczysz to:

el@apollo:~$ cat tmp.txt 
e with obfuscation: é

W ten sposób zapisałeś Unicode e ze znacznikiem zaciemnienia w pliku.

Eric Leschinski
źródło
2
Byłem bardzo podekscytowany tą odpowiedzią, ale daje błąd na moim komputerze. Kiedy kopiuję / wklejam twój kod, pojawia się błąd: „TypeError: must be str, not bytes”
Richard Rast
1

Ten błąd pojawia się, gdy próbujesz zakodować ciąg znaków inny niż Unicode: próbuje go zdekodować, zakładając, że jest to zwykły ASCII. Istnieją dwie możliwości:

  1. Kodujesz go do bajtowania, ale ponieważ użyłeś codecs.open, metoda write oczekuje obiektu Unicode. Więc go kodujesz, a on próbuje go dekodować ponownie. Spróbuj: f.write(all_html)zamiast tego.
  2. all_html nie jest w rzeczywistości obiektem Unicode. Kiedy to zrobisz .encode(...), najpierw próbuje go odkodować.
Thomas K.
źródło
0

W przypadku pisania w python3

>>> a = u'bats\u00E0'
>>> print a
batsà
>>> f = open("/tmp/test", "w")
>>> f.write(a)
>>> f.close()
>>> data = open("/tmp/test").read()
>>> data
'batsà'

W przypadku pisania w python2:

>>> a = u'bats\u00E0'
>>> f = open("/tmp/test", "w")
>>> f.write(a)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe0' in position 4: ordinal not in range(128)

Aby uniknąć tego błędu, należy zakodować go w bajtach za pomocą kodeków „utf-8” w następujący sposób:

>>> f.write(a.encode("utf-8"))
>>> f.close()

i dekoduj dane podczas odczytu przy użyciu kodeków „utf-8”:

>>> data = open("/tmp/test").read()
>>> data.decode("utf-8")
u'bats\xe0'

A także, jeśli spróbujesz wykonać print na tym ciągu, automatycznie dekoduje przy użyciu takich kodeków „utf-8” jak ten

>>> print a
batsà
ashish14
źródło