web-dev-qa-db-ja.com

SQLite、Python、Unicode、および非UTFデータ

私はPythonを使用してsqliteに文字列を保存しようとすることから始めて、メッセージを受け取りました:

sqlite3.ProgrammingError:8ビットバイト文字列を解釈できるtext_factory(text_factory = strなど)を使用しない限り、8ビットバイト文字列を使用しないでください。代わりに、単にアプリケーションをUnicode文字列に切り替えることを強くお勧めします。

OK、Unicode文字列に切り替えました。それから私はメッセージを受け取り始めました:

sqlite3.OperationalError:テキスト「SigurRós」のUTF-8列「tag_artist」にデコードできませんでした

データベースからデータを取得しようとしたとき。さらに研究を重ね、utf8でエンコードを開始しましたが、「SigurRós」は「SigurRós」のように見え始めました。

note:@John Machinが指摘したように、私のコンソールは 'latin_1'に表示するように設定されていました。

何が得られますか? this を読んだ後、私とまったく同じ状況を説明すると、アドバイスは他のアドバイスを無視し、結局8ビットのバイト文字列を使用するように思えます。

このプロセスを開始する前は、Unicodeとutfについてあまり知りませんでした。過去数時間でかなりのことを学びましたが、「ó」をlatin-1からutf-8に正しく変換し、マングル処理しない方法があるかどうかについては、まだ無知です。存在しない場合、なぜアプリケーションをユニコード文字列に切り替えるのですか?


この質問を、過去24時間に学んだすべての要約とサンプルコードで更新して、私の靴の誰かが簡単なガイドを用意できるようにします。私が投稿した情報が間違っていたり、何らかの形で誤解を招いたりする場合は、お知らせください。更新します。または、上級者が更新できます。


回答の概要

最初に目標を説明します。さまざまなエンコーディングを処理する際の目標は、ソースエンコーディングを理解し、ソースエンコーディングを使用してUnicodeに変換し、目的のエンコーディングに変換することです。 Unicodeはベースであり、エンコーディングはそのベースのサブセットのマッピングです。 utf_8にはUnicodeのすべての文字のためのスペースがありますが、たとえばlatin_1と同じ場所にないため、utf_8でエンコードされてlatin_1コンソールに送信される文字列は期待どおりに見えません。 pythonでは、Unicodeから別のエンコーディングに移行するプロセスは次のようになります。

str.decode('source_encoding').encode('desired_encoding')

またはstrがすでにUnicodeになっている場合

str.encode('desired_encoding')

Sqliteの場合、私は実際に再度エンコードしたくありませんでした。デコードしてユニコード形式のままにしておきたいと思いました。 PythonでUnicodeとエンコーディングを使用しようとするときに注意する必要がある4つのことを以下に示します。

  1. 使用したい文字列のエンコーディングと、取得したいエンコーディング。
  2. システムのエンコード。
  3. コンソールのエンコード。
  4. ソースファイルのエンコーディング

精緻化:

(1)ソースから文字列を読み取るときは、latin_1やutf_8などのエンコードが必要です。私の場合、ファイル名から文字列を取得しているため、残念ながら、あらゆる種類のエンコーディングを取得できます。 Windows XPは、UCS-2(Unicodeシステム)をネイティブの文字列タイプとして使用します。これは私にとってはごまかしのようです。幸いなことに、ほとんどのファイル名の文字は、複数のソースエンコーディングタイプがあり、すべてが完全にlatin_1、完全にutf_8、または単なるascii(これらの両方のサブセット)のいずれかであったと思います。 latin_1またはutf_8。ただし、Windowsのファイル名にlatin_1とutf_8およびその他の文字を混在させることができます。これらの文字はボックスとして表示される場合があります。 (アクセント文字とその他)。

(2)Pythonには、pythonが開始され、実行中に変更できない場合に設定されるデフォルトのシステムエンコーディングがあります。 here を参照してください。 =詳細についてダーティサマリー...ここに追加したファイルを示します。

\# sitecustomize.py  
\# this file can be anywhere in your Python path,  
\# but it usually goes in ${pythondir}/lib/site-packages/  
import sys  
sys.setdefaultencoding('utf_8')  

このシステムエンコーディングは、他のエンコーディングパラメータなしでunicode( "str")関数を使用するときに使用されるものです。別の言い方をすれば、pythonは、デフォルトのシステムエンコーディングに基づいて「str」をUnicodeにデコードしようとします。

(3)IDLEまたはコマンドラインpythonを使用している場合、コンソールはデフォルトのシステムエンコーディングに従って表示されると思います。何らかの理由でpydevをEclipseで使用しているため、プロジェクト設定に移動し、テストスクリプトの起動構成プロパティを編集し、[共通]タブに移動して、コンソールをlatin-1からutf-8に変更する必要がありました。私がやっていることが働いていたことを視覚的に確認できました。

(4)テスト文字列が必要な場合、例えば

test_str = "ó"

あなたのソースコードでは、そのファイルで使用しているエンコーディングの種類をpythonに伝える必要があります。(参考:エンコーディングを間違って入力したとき、ファイルがctrl-Zになったため、これは、ソースコードファイルの先頭に次のような行を追加することで簡単に実現できます。

# -*- coding: utf_8 -*-

この情報がない場合、pythonはデフォルトでコードをasciiとして解析しようとします。

SyntaxError: Non-ASCII character '\xf3' in file _redacted_ on line 81, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

プログラムが正常に動作するか、出力を見るためにpythonのコンソールまたは他のコンソールを使用していない場合、おそらくリストの#1のみを気にするでしょう。出力を確認する必要がある場合や、string.decode()関数の代わりに組み込みのunicode()関数(エンコーディングパラメータなし)を使用する場合を除き、システムのデフォルトとコンソールエンコーディングはそれほど重要ではありません。この巨大な混乱の一番下に貼り付けるデモ関数を作成しました。これをリスト内の項目が正しく示されることを願っています。以下は、デモ関数を介してキャラクター「ó」を実行したときの出力の一部であり、さまざまなメソッドが入力としてキャラクターにどのように反応するかを示しています。この実行では、システムエンコーディングとコンソール出力の両方がutf_8に設定されています。

'�' = original char <type 'str'> repr(char)='\xf3'
'?' = unicode(char) ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data
'ó' = char.decode('latin_1') <type 'unicode'> repr(char.decode('latin_1'))=u'\xf3'
'?' = char.decode('utf_8')  ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data

次に、システムとコンソールのエンコードをlatin_1に変更し、同じ入力に対してこの出力を取得します。

'ó' = original char <type 'str'> repr(char)='\xf3'
'ó' = unicode(char) <type 'unicode'> repr(unicode(char))=u'\xf3'
'ó' = char.decode('latin_1') <type 'unicode'> repr(char.decode('latin_1'))=u'\xf3'
'?' = char.decode('utf_8')  ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data

「元の」文字が正しく表示され、組み込みのunicode()関数が動作することに注意してください。

次に、コンソール出力をutf_8に戻します。

'�' = original char <type 'str'> repr(char)='\xf3'
'�' = unicode(char) <type 'unicode'> repr(unicode(char))=u'\xf3'
'�' = char.decode('latin_1') <type 'unicode'> repr(char.decode('latin_1'))=u'\xf3'
'?' = char.decode('utf_8')  ERROR: 'utf8' codec can't decode byte 0xf3 in position 0: unexpected end of data

ここではすべてが前回と同じように機能しますが、コンソールは出力を正しく表示できません。以下の関数は、これがより多くの情報も表示し、理解のギャップがどこにあるかを誰かが理解するのに役立つことを願っています。この情報はすべて他の場所にあり、より徹底的に対処されていることは知っていますが、これがpythonおよび/またはsqliteでコーディングしようとしている人にとって良い出発点になることを願っています。素晴らしいですが、時にはソースコードは、どの関数が何をするのかを理解しようとするのに1日か2日節約できます。

免責事項:私はエンコーディングの専門家ではありません。自分で理解できるようにこれをまとめました。あまり冗長なコードを避けるために、おそらく引数として関数を渡し始めるべきだったので、私はそれを構築し続けたので、できればもっと簡潔にします。また、utf_8とlatin_1は決して唯一のエンコーディングスキームではなく、必要なすべてを処理できると思うので、私が遊んでいた2つだけです。独自のエンコードスキームをデモ関数に追加し、独自の入力をテストします。

もう一つ: 明らかにクレイジーなアプリケーション開発者 Windowsでの生活を難しくしています。

#!/usr/bin/env python
# -*- coding: utf_8 -*-

import os
import sys

def encodingDemo(str):
    validStrings = ()
    try:        
        print "str =",str,"{0} repr(str) = {1}".format(type(str), repr(str))
        validStrings += ((str,""),)
    except UnicodeEncodeError as ude:
        print "Couldn't print the str itself because the console is set to an encoding that doesn't understand some character in the string.  See error:\n\t",
        print ude
    try:
        x = unicode(str)
        print "unicode(str) = ",x
        validStrings+= ((x, " decoded into unicode by the default system encoding"),)
    except UnicodeDecodeError as ude:
        print "ERROR.  unicode(str) couldn't decode the string because the system encoding is set to an encoding that doesn't understand some character in the string."
        print "\tThe system encoding is set to {0}.  See error:\n\t".format(sys.getdefaultencoding()),  
        print ude
    except UnicodeEncodeError as uee:
        print "ERROR.  Couldn't print the unicode(str) because the console is set to an encoding that doesn't understand some character in the string.  See error:\n\t",
        print uee
    try:
        x = str.decode('latin_1')
        print "str.decode('latin_1') =",x
        validStrings+= ((x, " decoded with latin_1 into unicode"),)
        try:        
            print "str.decode('latin_1').encode('utf_8') =",str.decode('latin_1').encode('utf_8')
            validStrings+= ((x, " decoded with latin_1 into unicode and encoded into utf_8"),)
        except UnicodeDecodeError as ude:
            print "The string was decoded into unicode using the latin_1 encoding, but couldn't be encoded into utf_8.  See error:\n\t",
            print ude
    except UnicodeDecodeError as ude:
        print "Something didn't work, probably because the string wasn't latin_1 encoded.  See error:\n\t",
        print ude
    except UnicodeEncodeError as uee:
        print "ERROR.  Couldn't print the str.decode('latin_1') because the console is set to an encoding that doesn't understand some character in the string.  See error:\n\t",
        print uee
    try:
        x = str.decode('utf_8')
        print "str.decode('utf_8') =",x
        validStrings+= ((x, " decoded with utf_8 into unicode"),)
        try:        
            print "str.decode('utf_8').encode('latin_1') =",str.decode('utf_8').encode('latin_1')
        except UnicodeDecodeError as ude:
            print "str.decode('utf_8').encode('latin_1') didn't work.  The string was decoded into unicode using the utf_8 encoding, but couldn't be encoded into latin_1.  See error:\n\t",
            validStrings+= ((x, " decoded with utf_8 into unicode and encoded into latin_1"),)
            print ude
    except UnicodeDecodeError as ude:
        print "str.decode('utf_8') didn't work, probably because the string wasn't utf_8 encoded.  See error:\n\t",
        print ude
    except UnicodeEncodeError as uee:
        print "ERROR.  Couldn't print the str.decode('utf_8') because the console is set to an encoding that doesn't understand some character in the string.  See error:\n\t",uee

    print
    print "Printing information about each character in the original string."
    for char in str:
        try:
            print "\t'" + char + "' = original char {0} repr(char)={1}".format(type(char), repr(char))
        except UnicodeDecodeError as ude:
            print "\t'?' = original char  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(char), repr(char), ude)
        except UnicodeEncodeError as uee:
            print "\t'?' = original char  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(char), repr(char), uee)
            print uee    

        try:
            x = unicode(char)        
            print "\t'" + x + "' = unicode(char) {1} repr(unicode(char))={2}".format(x, type(x), repr(x))
        except UnicodeDecodeError as ude:
            print "\t'?' = unicode(char) ERROR: {0}".format(ude)
        except UnicodeEncodeError as uee:
            print "\t'?' = unicode(char)  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(x), repr(x), uee)

        try:
            x = char.decode('latin_1')
            print "\t'" + x + "' = char.decode('latin_1') {1} repr(char.decode('latin_1'))={2}".format(x, type(x), repr(x))
        except UnicodeDecodeError as ude:
            print "\t'?' = char.decode('latin_1')  ERROR: {0}".format(ude)
        except UnicodeEncodeError as uee:
            print "\t'?' = char.decode('latin_1')  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(x), repr(x), uee)

        try:
            x = char.decode('utf_8')
            print "\t'" + x + "' = char.decode('utf_8') {1} repr(char.decode('utf_8'))={2}".format(x, type(x), repr(x))
        except UnicodeDecodeError as ude:
            print "\t'?' = char.decode('utf_8')  ERROR: {0}".format(ude)
        except UnicodeEncodeError as uee:
            print "\t'?' = char.decode('utf_8')  {0} repr(char)={1} ERROR PRINTING: {2}".format(type(x), repr(x), uee)

        print

x = 'ó'
encodingDemo(x)

以下の回答、特に徹底的に回答してくれた@John Machinに感謝します。

65
Nathan Spears

私はまだ「ó」をlatin-1からutf-8に正しく変換し、それをマングルしない方法があるかどうかは知らない

このような問題のデバッグに関しては、repr()とunicodedata.name()は友達です。

_>>> oacute_latin1 = "\xF3"
>>> oacute_unicode = oacute_latin1.decode('latin1')
>>> oacute_utf8 = oacute_unicode.encode('utf8')
>>> print repr(oacute_latin1)
'\xf3'
>>> print repr(oacute_unicode)
u'\xf3'
>>> import unicodedata
>>> unicodedata.name(oacute_unicode)
'LATIN SMALL LETTER O WITH ACUTE'
>>> print repr(oacute_utf8)
'\xc3\xb3'
>>>
_

Oacute_utf8をlatin1用に設定された端末に送信すると、Aチルダの後に上付き文字3が表示されます。

Unicode文字列に切り替えました。

Unicode文字列を何と呼んでいますか? UTF-16?

何が得られますか?これを読んで、私がいるのとまったく同じ状況を説明すると、アドバイスは他のアドバイスを無視し、結局8ビットのバイト文字列を使用するように思えます。

どう思われるか想像できません。伝えられた話は、PythonのUnicodeオブジェクトとデータベース内のUTF-8エンコーディングが道であるということでした。しかし、Martinはメソッド( "text factory" )OPがlatin1を使用できるようにするため-これは推奨事項ではありませんでした!

Updateコメントで提起されたこれらのさらなる質問に応じて:

ユニコード文字に暗黙のエンコーディングがまだ含まれていることを理解していませんでした。私はそう言っていますか?

いいえ。エンコードは、Unicodeと他の何かとの間のマッピングであり、その逆も同様です。ユニコード文字には、暗黙的またはその他のエンコーディングはありません。

私にはunicode( "\ xF3")と "\ xF3" .decode( 'latin1')はrepr()で評価されたとき同じであるように見えます。

何だって?私にはそれのように見えません:

_>>> unicode("\xF3")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xf3 in position 0: ordinal
not in range(128)
>>> "\xF3".decode('latin1')
u'\xf3'
>>>
_

おそらくあなたは:u'\xf3' == '\xF3'.decode('latin1') ...これは確かに本当です。

unicode(str_object, encoding)str_object.decode(encoding)と同じことを行うことも事実です...不適切なエンコードが指定された場合の爆破を含みます。

それは幸せな状況ですか

Unicodeの最初の256文字は同じコードであるため、latin1の256文字は良い考えです。 256個のすべてのlatin1文字がUnicodeにマッピングされるため、8ビットバイトのANY Python strオブジェクトは例外を発生させることなくUnicodeにデコードできることを意味します。 。

ただし、「例外が発生することなくスクリプトが最後まで実行される」と「エラーが発生しない」という2つのまったく異なる概念を混同する人が存在します。彼らにとって、latin1は「わなと妄想」です。

言い換えると、実際にcp1252またはgbkまたはkoi8-uなどでエンコードされたファイルがあり、latin1を使用してデコードする場合、結果のUnicodeは完全にゴミになり、Python(またはany他の言語)はエラーにフラグを立てません-愚かさを犯したことを知る方法がありません。

または、unicode( "str")は常に正しいデコードを返しますか?

そのように、デフォルトのエンコードがasciiの場合、ファイルが実際にASCIIでエンコードされている場合、正しいUnicodeを返します。そうでなければ、爆破します。

同様に、正しいエンコーディング、または正しいエンコーディングのスーパーセットであるエンコーディングを指定すると、正しい結果が得られます。そうしないと、意味がわからなくなったり、例外が発生します。

要するに、答えはノーです。

そうでない場合、可能な文字セットが含まれるpython strを受け取ったときに、それをデコードする方法を知るにはどうすればよいですか?

Strオブジェクトが有効なXMLドキュメントである場合、事前に指定されます。デフォルトはUTF-8です。適切に構築されたWebページの場合は、事前に指定する必要があります(「charset」を探してください)。残念ながら、Webページの多くの作家は歯をたてています(ISO-8859-1、別名latin1、Windows-1252、別名cp1252、gb2312をデコードしようとするリソースを無駄にしないでください)。ウェブサイトの国籍/言語から手がかりを得ることができます。

UTF-8は常に試してみる価値があります。データがasciiの場合、asciiはutf8のサブセットであるため、正常に機能します。非ASCII文字を使用して記述され、utf8以外のエンコードでエンコードされたテキスト文字列は、utf8としてデコードしようとすると、例外を除いてほぼ間違いなく失敗します。

上記のすべてのヒューリスティックおよびその他の多くの統計は、任意のファイルのエンコーディングを推測するモジュールである chardet にカプセル化されています。通常はうまく機能します。ただし、ソフトウェアをバカにすることはできません。たとえば、一部をエンコードAで、一部をエンコードBで記述したデータファイルを連結し、結果をchardetにフィードすると、答えはおそらくCをエンコードすることになります信頼性が低下します。 0.8。回答の信頼部分を常に確認してください

他のすべてが失敗した場合:

(1)データの先頭からの小さなサンプルでここで尋ねてみてください... print repr(your_data[:400]) ...そして、あなたが持っている出所についてのあらゆる付随情報。

(2)ロシアの最近の研究 忘れられたパスワードを回復する技術 は、未知のエンコーディングを推測するのに非常に適用できるようです。

Update 2ところで、それはあなたが別の質問を開いた時についてですか?-)

もう1つ:明らかに、Windowsが特定の文字のUnicodeとして使用する文字は、その文字の正しいUnicodeではないため、他のプログラムでそれらを使用する場合は、それらの文字を正しい文字にマッピングする必要がありますそれらのキャラクターが正しい場所にあることを期待しています。

それを行っているのはWindowsではありません。クレイジーなアプリケーション開発者の集まりです。あなたはより理解しやすいように言い換えなかったかもしれませんが、あなたが言及したeffbotの記事の冒頭の段落を引用しました:

一部のアプリケーションでは、ISO 8859-1(Latin 1)またはその他のエンコーディングとしてマークアップされたドキュメントにCP1252(Windows、西ヨーロッパ)文字を追加します。これらの文字は有効なISO-8859-1文字ではなく、アプリケーションの処理と表示であらゆる種類の問題を引き起こす可能性があります。

バックグラウンド:

U + 0000からU + 001Fまでの範囲はUnicodeで「C0制御文字」として指定されます。これらはASCIIとlatin1にも同じ意味で存在します。これらには、キャリッジリターン、ラインフィード、ベル、バックスペース、タブなど、めったに使用されないものが含まれます。

U + 0080からU + 009Fまでの範囲はUnicodeで「C1制御文字」として指定されています。これらはlatin1にも存在し、unicode.orgの外部では誰も使用できないと思われる32文字が含まれています。

そのため、Unicodeまたはlatin1データで文字頻度カウントを実行し、その範囲内の文字を見つけた場合、データは破損しています。普遍的な解決策はありません。破損した方法によって異なります。文字mayは、同じ位置にあるcp1252文字と同じ意味を持つため、effbotのソリューションは機能します。最近見た別のケースでは、危険な文字は、UTF-8でエンコードされたテキストファイルと、ファイルがあった(人間の)言語の文字頻度に基づいて推定される必要がある別のエンコードを連結することによって引き起こされたようですで書かれている。

34
John Machin

UTF-8は、SQLiteデータベースのデフォルトのエンコーディングです。これは、「SELECT CAST(x'52C3B373 'AS TEXT);」などの状況で表示されます。ただし、SQLite Cライブラリは、DBに挿入された文字列が有効なUTF-8であるかどうかを実際にチェックしません。

Python unicodeオブジェクト(または3.xのstrオブジェクト)を挿入すると、Python sqlite3ライブラリはそれを自動的にUTF-8に変換します。しかし、strオブジェクトを挿入すると、 assume だけが文字列がUTF-8になります。これは、Python 2.x "str"がエンコードを認識しないためです。これがUnicode文字列を好む理由の1つです。

ただし、そもそもデータが破損している場合は役に立ちません。

データを修正するには、

db.create_function('FIXENCODING', 1, lambda s: str(s).decode('latin-1'))
db.execute("UPDATE TheTable SET TextColumn=FIXENCODING(CAST(TextColumn AS BLOB))")

データベース内のすべてのテキスト列に対して。

21
dan04

このpysqliteの問題を次のように設定して修正しました。

conn.text_factory = lambda x: unicode(x, 'utf-8', 'ignore')

デフォルトでは、text_factoryはunicode()に設定され、現在のデフォルトのエンコーディング(私のマシンではASCII)が使用されます

19
hoju

もちろんあります。ただし、データベース内のデータはすでに破損しているため、修正する必要があります。

>>> print u'Sigur Rós'.encode('latin-1').decode('utf-8')
Sigur Rós

Python 2.x(具体的にはPython 2.7.6))のUnicode問題により、これが修正されました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import unicode_literals
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

また、投稿の冒頭で言及しているエラーも解決しました。

sqlite3.ProgrammingError:8ビットバイト文字列を使用しないでください...

[〜#〜] edit [〜#〜]

sys.setdefaultencodingは、ダーティハックです。はい、UTF-8の問題を解決できますが、すべてに価格が伴います。詳細については、次のリンクを参照してください。

3
Dušan Maďar