標籤:
寫這篇文章,起源於要寫一個指令碼批量把CSV檔案(檔案採用GBK或utf-8編碼)寫入到sqlite資料庫裡。
Python版本:2.7.9
sqlite3模組提供了con = sqlite3.connect("D:\\text_factory.db3") 這樣的方法來建立資料庫(當檔案不存在時,建立庫),資料庫預設編碼為UTF-8,支援使用特殊sql語句設定編碼
PRAGMA encoding = "UTF-8";
PRAGMA encoding = "UTF-16";
PRAGMA encoding = "UTF-16le";
PRAGMA encoding = "UTF-16be";
但設定編碼必須在main庫之前,否則無法更改。 https://www.sqlite.org/pragma.html#pragma_encoding
認識text_factory屬性,大家應該都是通過以下錯誤知曉的:
sqlite3.ProgrammingError: You must not use 8-bit bytestrings unless you use a text_factory that can interpret 8-bit bytestrings (like text_factory = str). It is highly recommended that you instead just switch your application to Unicode strings.
大意是推薦你把字串入庫之前轉成unicode string,你要用bytestring位元組型字串(如ascii ,gbk,utf-8),需要加一條語句text_factory = str。
Python擁有兩種字串類型。標準字串是單位元組字元序列,允許包含位元據和嵌入的null字元。 Unicode 字串是雙位元組字元序列,一個字元使用兩個位元組來儲存,因此可以有最多65536種不同的unicode字元。儘管最新的Unicode標準支援最多100萬個不同的字元,Python現在尚未支援這個最新的標準。
預設text_factory = unicode,原以為這unicode、str是函數指標,但貌似不是,是<type ‘unicode‘>和<type ‘str‘>
下面寫了一段測實驗證代碼:
1 # -*- coding: utf-8 -*- 2 import sqlite3 3 ‘‘‘ 4 GBK UNIC UTF-8 5 B8A3 798F E7 A6 8F 福 6 D6DD 5DDE E5 B7 9E 州 7 ‘‘‘ 8 9 con = sqlite3.connect(":memory:")10 # con = sqlite3.connect("D:\\text_factory1.db3")11 # con.executescript(‘PRAGMA encoding = "UTF-16";‘)12 cur = con.cursor()13 14 a_text = "Fu Zhou"15 gb_text = "\xB8\xA3\xD6\xDD"16 utf8_text = "\xE7\xA6\x8F\xE5\xB7\x9E"17 unicode_text= u"\u798F\u5DDE"18 19 print ‘Part 1: con.text_factory=str‘20 con.text_factory = str21 print type(con.text_factory)22 cur.execute("CREATE TABLE table1 (city);")23 cur.execute("INSERT INTO table1 (city) VALUES (?);",(a_text,))24 cur.execute("INSERT INTO table1 (city) VALUES (?);",(gb_text,))25 cur.execute("INSERT INTO table1 (city) VALUES (?);",(utf8_text,))26 cur.execute("INSERT INTO table1 (city) VALUES (?);",(unicode_text,))27 cur.execute("select city from table1")28 res = cur.fetchall()29 print "-- result: %s"%(res)30 31 print ‘Part 2: con.text_factory=unicode‘32 con.text_factory = unicode33 print type(con.text_factory)34 cur.execute("CREATE TABLE table2 (city);")35 cur.execute("INSERT INTO table2 (city) VALUES (?);",(a_text,))36 # cur.execute("INSERT INTO table2 (city) VALUES (?);",(gb_text,))37 # cur.execute("INSERT INTO table2 (city) VALUES (?);",(utf8_text,))38 cur.execute("INSERT INTO table2 (city) VALUES (?);",(unicode_text,))39 cur.execute("select city from table2")40 res = cur.fetchall()41 print "-- result: %s"%(res)42 43 print ‘Part 3: OptimizedUnicode‘44 con.text_factory = str45 cur.execute("CREATE TABLE table3 (city);")46 cur.execute("INSERT INTO table3 (city) VALUES (?);",(a_text,))47 #cur.execute("INSERT INTO table3 (city) VALUES (?);",(gb_text,))48 cur.execute("INSERT INTO table3 (city) VALUES (?);",(utf8_text,))49 cur.execute("INSERT INTO table3 (city) VALUES (?);",(unicode_text,))50 con.text_factory = sqlite3.OptimizedUnicode51 print type(con.text_factory)52 cur.execute("select city from table3")53 res = cur.fetchall()54 print "-- result: %s"%(res)55 56 print ‘Part 4: custom fuction‘57 con.text_factory = lambda x: unicode(x, "gbk", "ignore")58 print type(con.text_factory)59 cur.execute("CREATE TABLE table4 (city);")60 cur.execute("INSERT INTO table4 (city) VALUES (?);",(a_text,))61 cur.execute("INSERT INTO table4 (city) VALUES (?);",(gb_text,))62 cur.execute("INSERT INTO table4 (city) VALUES (?);",(utf8_text,))63 cur.execute("INSERT INTO table4 (city) VALUES (?);",(unicode_text,))64 cur.execute("select city from table4")65 res = cur.fetchall()66 print "-- result: %s"%(res)
列印結果:
Part 1: con.text_factory=str<type ‘type‘>-- result: [(‘Fu Zhou‘,), (‘\xb8\xa3\xd6\xdd‘,), (‘\xe7\xa6\x8f\xe5\xb7\x9e‘,), (‘\xe7\xa6\x8f\xe5\xb7\x9e‘,)]Part 2: con.text_factory=unicode<type ‘type‘>-- result: [(u‘Fu Zhou‘,), (u‘\u798f\u5dde‘,)]Part 3: OptimizedUnicode<type ‘type‘>-- result: [(‘Fu Zhou‘,), (u‘\u798f\u5dde‘,), (u‘\u798f\u5dde‘,)]Part 4: custom fuction<type ‘function‘>-- result: [(u‘Fu Zhou‘,), (u‘\u798f\u5dde‘,), (u‘\u7ec2\u5fd3\u7a9e‘,), (u‘\u7ec2\u5fd3\u7a9e‘,)]
Part 1:unicode被轉換成了utf-8,utf-8和GBK被透傳,寫入資料庫,GBK字串被取出顯示時,需要用類似‘gbk chars‘.decode("cp936").encode("utf_8")的語句進行解析print
Part 2:預設設定,注釋的掉都會產生以上的經典錯誤,輸入範圍被限定在unicode對象或純ascii碼
Part 3:自動最佳化,ascii為str對象,非ascii轉為unicode對象
Part 4:GBK被正確轉換,utf-8和unicode在存入資料庫時,都被轉為了預設編碼utf-8儲存,既‘\xe7\xa6\x8f\xe5\xb7\x9e‘,
In[16]: unicode(‘\xe7\xa6\x8f\xe5\xb7\x9e‘,‘gbk‘)
Out[16]: u‘\u7ec2\u5fd3\u7a9e‘
就得到了以上結果。
接著,用軟體查看資料庫裡是如何存放的。
分別用官方的sqlite3.exe和SqliteSpy查看,sqlite3.exe因為用命令列介面,命令列用的是GBK顯示;SqliteSpy則是用UTF顯示,所以GBK顯示亂碼。這就再次印證了GBK被允許存放入資料庫的時候,存放的是raw資料,並不會強制轉為資料庫的預設編碼utf-8儲存。
Connection.text_factory使用此屬性來控制我們可以從TEXT類型得到什麼對象(我:這也印證寫入資料庫的時候,需要自己編碼,不能依靠這個)。預設情況下,這個屬性被設定為Unicode,sqlite3模組將會為TEXT返回Unicode對象。若你想返回bytestring對象,可以將它設定為str。
因為效率的原因,還有一個只針對非ASCII資料,返回Unicode對象,其它資料則全部返回bytestring對象的方法。要啟用它,將此屬性設定為sqlite3.OptimizedUnicode。
你也可以將它設定為任意的其它callabel,接收一個bytestirng類型的參數,並返回結果對象。《摘自http://www.360doc.com/content/11/1102/10/4910_161017252.shtml》
以上一段話是官方文檔的中文版關於text_factory描述的節選。
綜上,我談談我的看法*和使用建議:
1)sqlite3模組執行insert時,寫入的是raw資料,寫入前會根據text_factory屬性進行類型判斷,預設判斷寫入的是否為unicode對象;
2)使用fetchall()從資料庫讀出時,會根據text_factory屬性進行轉化。
3)輸入字串是GBK編碼的bytestring,decode轉為unicode寫入;或加text_factory=str直接寫入,讀出時仍為GBK,前提需要資料庫編碼為utf-8,注意用sqlitespy查看是亂碼。
4)輸入字串是Utf-8編碼的bytestring,可以設定text_factory=str直接寫入直接讀出,sqlitespy查看正常顯示。
5)如果不是什麼高效能情境,入庫前轉成unicode,效能開銷也很小,測試資料找不到了,像我這樣話一整天研究這一行代碼,不如讓機器每次多跑零點幾秒。。
*(因為沒有查看sqlite3模組的原始碼,所以只是猜測)
另外,附上資料庫設定為UTF-16編碼時,產生的結果,更亂,不推薦。
Part 1: con.text_factory=str<type ‘type‘>-- result: [(‘Fu Zhou‘,), (‘\xc2\xb8\xc2\xa3\xef\xbf\xbd\xef\xbf\xbd‘,), (‘\xe7\xa6\x8f\xe5\xb7\x9e‘,), (‘\xe7\xa6\x8f\xe5\xb7\x9e‘,)]Part 2: con.text_factory=unicode<type ‘type‘>-- result: [(u‘Fu Zhou‘,), (u‘\u798f\u5dde‘,)]Part 3: OptimizedUnicode<type ‘type‘>-- result: [(‘Fu Zhou‘,), (u‘\u798f\u5dde‘,), (u‘\u798f\u5dde‘,)]Part 4: custom fuction<type ‘function‘>-- result: [(u‘Fu Zhou‘,), (u‘\u8d42\u62e2\u951f\u65a4\u62f7‘,), (u‘\u7ec2\u5fd3\u7a9e‘,), (u‘\u7ec2\u5fd3\u7a9e‘,)]
Python sqlite3模組的text_factory屬性的使用方法研究