Program Tip

SQLite, Python, 유니 코드 및 비 UTF 데이터

programtip 2020. 11. 18. 09:41
반응형

SQLite, Python, 유니 코드 및 비 UTF 데이터


파이썬을 사용하여 sqlite에 문자열을 저장하려고 시도하고 메시지를 받았습니다.

sqlite3.ProgrammingError : 8 비트 바이트 열을 해석 할 수있는 text_factory (예 : text_factory = str)를 사용하지 않는 한 8 비트 바이트 열을 사용해서는 안됩니다. 대신 응용 프로그램을 유니 코드 문자열로 전환하는 것이 좋습니다.

좋아, 유니 코드 문자열로 전환했습니다. 그런 다음 메시지를 받기 시작했습니다.

sqlite3.OperationalError : 'Sigur Rós'텍스트가있는 UTF-8 열 'tag_artist'로 디코딩 할 수 없습니다.

db에서 데이터를 검색하려고 할 때. 더 많은 연구를 통해 utf8로 인코딩하기 시작했지만 'Sigur Rós'는 'Sigur Rós'처럼 보이기 시작합니다.

참고 : 내 콘솔은 @John Machin이 지적한대로 'latin_1'로 표시되도록 설정되었습니다.

무엇을 제공합니까? 이것을 읽은 후 내가있는 것과 똑같은 상황을 설명하면 다른 조언을 무시하고 결국 8 비트 바이트 문자열을 사용하는 것 같습니다.

이 프로세스를 시작하기 전에는 유니 코드와 utf에 대해 많이 알지 못했습니다. 지난 몇 시간 동안 꽤 많이 배웠지 만 'ó'를 latin-1에서 utf-8로 올바르게 변환하고 그것을 엉망으로 만드는 방법이 있는지 여전히 무지합니다. 없는 경우 왜 sqlite가 내 응용 프로그램을 유니 코드 문자열로 전환 할 것을 '적극 권장'합니까?


이 질문을 지난 24 시간 동안 제가 배운 모든 것에 대한 요약과 몇 가지 예제 코드로 업데이트하여 내 입장에있는 누군가가 쉬운 가이드를 얻을 수 있도록 할 것입니다. 내가 게시 한 정보가 잘못되었거나 어떤 식 으로든 오해의 소지가있는 경우 알려 주시면 업데이트하겠습니다. 그렇지 않으면 상급자 중 한 명이 업데이트 할 수 있습니다.


답변 요약

이해하는대로 먼저 목표를 설명하겠습니다. 다양한 인코딩 처리의 목표는 소스 인코딩이 무엇인지 이해 한 다음 해당 소스 인코딩을 사용하여 유니 코드로 변환 한 다음 원하는 인코딩으로 변환하는 것입니다. 유니 코드는 기본이고 인코딩은 해당 기본의 하위 집합 매핑입니다. utf_8에는 유니 코드의 모든 문자를위한 공간이 있지만, 예를 들어 latin_1과 같은 위치에 있지 않기 때문에 utf_8로 인코딩되고 latin_1 콘솔로 전송 된 문자열은 예상대로 보이지 않습니다. 파이썬에서 유니 코드와 다른 인코딩을 얻는 과정은 다음과 같습니다.

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

또는 str이 이미 유니 코드에있는 경우

str.encode('desired_encoding')

sqlite의 경우 실제로 다시 인코딩하고 싶지 않았으며 디코딩하고 유니 코드 형식으로 남겨두고 싶었습니다. 파이썬에서 유니 코드 및 인코딩 작업을 할 때 알아야 할 네 가지 사항이 있습니다.

  1. 작업 할 문자열의 인코딩 및 가져 오려는 인코딩입니다.
  2. 시스템 인코딩입니다.
  3. 콘솔 인코딩.
  4. 소스 파일의 인코딩

동화:

(1) 소스에서 문자열을 읽을 때 latin_1 또는 utf_8과 같은 인코딩이 있어야합니다. 제 경우에는 파일 이름에서 문자열을 얻으므로 불행히도 모든 종류의 인코딩을 얻을 수 있습니다. Windows XP는 UCS-2 (유니 코드 시스템)를 기본 문자열 유형으로 사용합니다. 다행스럽게도 대부분의 파일 이름에있는 문자는 하나 이상의 소스 인코딩 유형으로 구성되지 않을 것이며, 내 모든 것은 완전히 latin_1, 완전히 utf_8 또는 단순한 ASCII (둘 다의 하위 집합)라고 생각합니다. 그). 그래서 저는 그것들을 읽고 마치 latin_1 또는 utf_8에있는 것처럼 디코딩했습니다. 그러나 Windows에서 latin_1 및 utf_8 및 다른 모든 문자가 파일 이름에 함께 혼합되어있을 수 있습니다. 때로는 그 캐릭터가 상자로 나타날 수 있고, 어떤 때는 그냥 엉망인 것처럼 보일 수도 있습니다. 다른 경우에는 정확 해 보입니다 (강조 문자 및 기타). 계속해.

(2) Python에는 Python이 시작될 때 설정되고 런타임 중에 변경할 수없는 기본 시스템 인코딩이 있습니다. 자세한 내용은 여기참조 하십시오 . 더러운 요약 ... 여기에 내가 추가 한 파일이 있습니다.

\# 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") 함수를 사용할 때 사용되는 인코딩입니다. 즉, 파이썬은 기본 시스템 인코딩을 기반으로 "str"을 유니 코드로 디코딩하려고합니다.

(3) IDLE 또는 명령 줄 파이썬을 사용하는 경우 콘솔이 기본 시스템 인코딩에 따라 표시 될 것이라고 생각합니다. 어떤 이유로 이클립스와 함께 pydev를 사용하고 있으므로 프로젝트 설정으로 이동하여 테스트 스크립트의 시작 구성 속성을 편집하고 공통 탭으로 이동 한 다음 콘솔을 latin-1에서 utf-8로 변경해야했습니다. 내가하고있는 일이 제대로 작동하는지 시각적으로 확인할 수있었습니다.

(4) 예를 들어 테스트 문자열을 갖고 싶다면

test_str = "ó"

소스 코드에서, 그 파일에서 어떤 종류의 인코딩을 사용하고 있는지 파이썬에게 알려야합니다. (참고 : 인코딩을 잘못 입력했을 때 파일을 읽을 수 없게 되었기 때문에 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

프로그램이 올바르게 작동하거나 출력을보기 위해 파이썬 콘솔이나 다른 콘솔을 사용하지 않는 경우 목록에서 # 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

여기에서 모든 것이 지난번과 동일하게 작동하지만 콘솔이 출력을 올바르게 표시 할 수 없습니다. Etc. 아래의 기능은 또한 이것에 대한 더 많은 정보를 표시하며 누군가가 이해의 차이가 어디인지 알아내는 데 도움이되기를 바랍니다. 나는이 모든 정보가 다른 곳에 있고 더 철저히 다뤄진다는 것을 알고 있지만, 이것이 파이썬 및 / 또는 sqlite로 코딩을 시도하는 사람에게 좋은 시작점이되기를 바랍니다. 아이디어는 훌륭하지만 때로는 소스 코드가 어떤 기능이 무엇을하는지 파악하는 데 하루나 이틀을 절약 할 수 있습니다.

면책 조항 : 저는 인코딩 전문가가 아닙니다. 제 이해를 돕기 위해 이것을 모았습니다. 중복 코드를 피하기 위해 함수를 인수로 전달하기 시작 했어야했을 때이를 계속 구축했습니다. 가능하다면 더 간결하게 만들겠습니다. 또한, utf_8과 latin_1은 결코 유일한 인코딩 체계가 아닙니다. 그들은 제가 필요한 모든 것을 처리한다고 생각하기 때문에 제가 가지고 놀던 두 가지입니다. 데모 기능에 고유 한 인코딩 체계를 추가하고 자신의 입력을 테스트하십시오.

한 가지 더 : 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에게 감사드립니다.


나는 'ó'를 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'
>>>

latin1 용으로 설정된 터미널에 oacute_utf8을 보내면 A- 물결표 뒤에 위 첨자 -3이 표시됩니다.

유니 코드 문자열로 전환했습니다.

유니 코드 문자열을 무엇이라고 부르나요? UTF-16?

무엇을 제공합니까? 이 글을 읽은 후, 내가 처한 상황을 정확히 설명하면 다른 조언을 무시하고 결국 8 비트 바이트 문자열을 사용하는 것이 조언 인 것처럼 보입니다.

나는 그것이 당신에게 어떻게 그렇게 보이는지 상상할 수 없습니다. 전달 된 이야기는 Python의 유니 코드 객체와 데이터베이스의 UTF-8 인코딩이 갈 길 이었다는 것입니다. 그러나 Martin은 원래 질문에 대답하여 OP가 latin1을 사용할 수있는 방법 ( "텍스트 팩토리")을 제공했습니다. 이것은 권장 사항이 아닙니다!

Update in response to these further questions raised in a comment:

I didn't understand that the unicode characters still contained an implicit encoding. Am I saying that right?

No. An encoding is a mapping between Unicode and something else, and vice versa. A Unicode character doesn't have an encoding, implicit or otherwise.

It looks to me like unicode("\xF3") and "\xF3".decode('latin1') are the same when evaluated with repr().

Say what? It doesn't look like it to me:

>>> 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'
>>>

Perhaps you meant: u'\xf3' == '\xF3'.decode('latin1') ... this is certainly true.

It is also true that unicode(str_object, encoding) does the same as str_object.decode(encoding) ... including blowing up when an inappropriate encoding is supplied.

Is that a happy circumstance

That the first 256 characters in Unicode are the same, code for code, as the 256 characters in latin1 is a good idea. Because all 256 possible latin1 characters are mapped to Unicode, it means that ANY 8-bit byte, ANY Python str object can be decoded into unicode without an exception being raised. This is as it should be.

However there exist certain persons who confuse two quite separate concepts: "my script runs to completion without any exceptions being raised" and "my script is error-free". To them, latin1 is "a snare and a delusion".

In other words, if you have a file that's actually encoded in cp1252 or gbk or koi8-u or whatever and you decode it using latin1, the resulting Unicode will be utter rubbish and Python (or any other language) will not flag an error -- it has no way of knowing that you have commited a silliness.

or is unicode("str") going to always return the correct decoding?

Just like that, with the default encoding being ascii, it will return the correct unicode if the file is actually encoded in ASCII. Otherwise, it'll blow up.

Similarly, if you specify the correct encoding, or one that's a superset of the correct encoding, you'll get the correct result. Otherwise you'll get gibberish or an exception.

In short: the answer is no.

If not, when I receive a python str that has any possible character set in it, how do I know how to decode it?

If the str object is a valid XML document, it will be specified up front. Default is UTF-8. If it's a properly constructed web page, it should be specified up front (look for "charset"). Unfortunately many writers of web pages lie through their teeth (ISO-8859-1 aka latin1, should be Windows-1252 aka cp1252; don't waste resources trying to decode gb2312, use gbk instead). You can get clues from the nationality/language of the website.

UTF-8 is always worth trying. If the data is ascii, it'll work fine, because ascii is a subset of utf8. A string of text that has been written using non-ascii characters and has been encoded in an encoding other than utf8 will almost certainly fail with an exception if you try to decode it as utf8.

All of the above heuristics and more and a lot of statistics are encapsulated in chardet, a module for guessing the encoding of arbitrary files. It usually works well. However you can't make software idiot-proof. For example, if you concatenate data files written some with encoding A and some with encoding B, and feed the result to chardet, the answer is likely to be encoding C with a reduced level of confidence e.g. 0.8. Always check the confidence part of the answer.

If all else fails:

(1) Try asking here, with a small sample from the front of your data ... print repr(your_data[:400]) ... and whatever collateral info about its provenance that you have.

(2) Recent Russian research into techniques for recovering forgotten passwords appears to be quite applicable to deducing unknown encodings.

Update 2 BTW, isn't it about time you opened up another question ?-)

One more thing: there are apparently characters that Windows uses as Unicode for certain characters that aren't the correct Unicode for that character, so you may have to map those characters to the correct ones if you want to use them in other programs that are expecting those characters in the right spot.

It's not Windows that's doing it; it's a bunch of crazy application developers. You might have more understandably not paraphrased but quoted the opening paragraph of the effbot article that you referred to:

Some applications add CP1252 (Windows, Western Europe) characters to documents marked up as ISO 8859-1 (Latin 1) or other encodings. These characters are not valid ISO-8859-1 characters, and may cause all sorts of problems in processing and display applications.

Background:

The range U+0000 to U+001F inclusive is designated in Unicode as "C0 Control Characters". These exist also in ASCII and latin1, with the same meanings. They include such familar things as carriage return, line feed, bell, backspace, tab, and others that are used rarely.

The range U+0080 to U+009F inclusive is designated in Unicode as "C1 Control Characters". These exist also in latin1, and include 32 characters that nobody outside unicode.org can imagine any possible use for.

Consequently, if you run a character frequency count on your unicode or latin1 data, and you find any characters in that range, your data is corrupt. There is no universal solution; it depends on how it became corrupted. The characters may have the same meaning as the cp1252 characters at the same positions, and thus the effbot's solution will work. In another case that I've been looking at recently, the dodgy characters appear to have been caused by concatenating text files encoded in UTF-8 and another encoding which needed to be deduced based on letter frequencies in the (human) language the files were written in.


UTF-8 is the default encoding of SQLite databases. This shows up in situations like "SELECT CAST(x'52C3B373' AS TEXT);". However, the SQLite C library doesn't actually check whether a string inserted into a DB is valid UTF-8.

If you insert a Python unicode object (or str object in 3.x), the Python sqlite3 library will automatically convert it to UTF-8. But if you insert a str object, it will just assume the string is UTF-8, because Python 2.x "str" doesn't know its encoding. This is one reason to prefer Unicode strings.

However, it doesn't help you if your data is broken to begin with.

To fix your data, do

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

for every text column in your database.


I fixed this pysqlite problem by setting:

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

By default text_factory is set to unicode(), which will use the current default encoding (ascii on my machine)


Of course there is. But your data is already broken in the database, so you'll need to fix it:

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

My unicode problems with Python 2.x (Python 2.7.6 to be specific) fixed this:

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

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

It also solved the error you are mentioning right at the beginning of the post:

sqlite3.ProgrammingError: You must not use 8-bit bytestrings unless ...

EDIT

sys.setdefaultencoding is a dirty hack. Yes, it can solve UTF-8 issues, but everything comes with a price. For more details refer to following links:

참고URL : https://stackoverflow.com/questions/2392732/sqlite-python-unicode-and-non-utf-data

반응형