python --- 字符編碼學習小結


       上半年的KPI,是用python做一個測試樁系統,現在系統框架基本也差不多定下來了。里面有用到新學的工廠設計模式以及以及常用的大牛寫框架的業務邏輯和python小技巧。發現之前自己寫的代碼還是面向過程思想的多,基本沒有面向對象的思想,近半年看的代碼給了很大的觸動,我需要升級我的技能了,於是也花了挺多時間在這個KPI學習上,現在先總結下在做這個系統時我所面臨到的python的字符編碼問題。

  字符編碼問題,如果處理有問題,可能直接就報錯了;如果處理不得當,中文就會顯示亂碼。這是最初接觸字符編碼遇到問題最簡潔的表達。一般都會遇到以下幾個問題(逐漸升級的頭疼問題):

  1. 使用編輯器或者Python IDE直接打印中文,報錯或者亂碼;

  2. 前台傳輸過來包含中文的字符串在后台打印,報錯或者亂碼;

  3. DB交互,從DB查詢或者insert的中文,操作時報錯或者亂碼;

  4. 文件操作,從文件中讀取或者寫文件時,報錯或者亂碼;

  除了第四個問題,暫時我沒遇到過,前面3個問題,我遇到好幾次了;遇到字符編碼問題,如果想着就解決當前的問題,隨便在百度上,狂搜各種方法亂試,可能還真能解決這個問題,但是耗時太長了,下次再遇到一樣會頭炸開,我覺得學習解決這個問題需要經過以下幾個過程:

  1. 簡單的了解計算機字符編碼的發展史,ASCII編碼是啥? EASCII是啥?GBK是啥?unicode是啥?UTF-8是啥?UTF-16是啥?如果這些最初級的基本概念不了解,后面學習會很困難。

  2. 理解python的字符串的數據類型,str和unicode,兩者之間是如何轉換的?

  3. mysql支持哪些字符?mysql的環境變量跟字符集相關的有七,八個,都是神馬意思?最簡單的要怎么使用?

      第一:字符編碼的前世今生

      1989年,荷蘭人Guido van Rossum發明python語言,第一個公開發行版發行於1991年,當時在那個時代,是不關心編碼問題的而且英文字符個數本身也是有限的,26個字母,10個數字,標點符號,鍵盤上加起來能輸入的字符就一百多個,用一個字節來存儲已經夠了,8個比特位能存256個字符。於是美國人制定了一套字符編碼標准ASCII。最開始的ASCII只定義了128個字符,包括96個字符和32個控制符,因此 ASCII 只使用了一個字節的后7位,最高位都為0。

  隨着時代的進步,計算機開始普及到千家萬戶,計算機進入中國面臨的一個問題就是字符編碼,中國的漢字是人類使用頻率最多的文字,常見的漢字就有成千上萬,大大超出了 ASCII 編碼所能表示的字符范圍了,於是中國人自己弄了一套編碼叫 GB2312,GB2312 編碼共收錄了6763個漢字,同時他還兼容 ASCII,GB 2312的出現,基本滿足了漢字的計算機處理需要,它所收錄的漢字已經覆蓋中國大陸99.75%的使用頻率,不過 GB2312 還是不能100%滿足中國漢字的需求,對一些罕見的字和繁體字 GB2312 沒法處理,后來就在GB2312的基礎上創建了一種叫 GBK 的編碼,GBK 不僅收錄了27484個漢字,同時還收錄了藏文、蒙文、維吾爾文等主要的少數民族文字。同樣 GBK 也是兼容 ASCII 編碼的,對於英文字符用1個字節來表示,漢字用兩個字節來標識。

       世界語言種類有多少,計算機的字符編碼相應就會增加多少。於是統一聯盟國際組織提出了Unicode編碼,Unicode的學名是”Universal Multiple-Octet Coded Character Set”,簡稱為UCS。Unicode有兩種格式:UCS-2和UCS-4。UCS-2就是用兩個字節編碼,一共16個比特位,這樣理論上最多可以表示65536個字符,不過要表示全世界所有的字符顯示65536個數字還遠遠不過,因為光漢字就有近10萬個,因此Unicode4.0規范定義了一組附加的字符編碼,UCS-4就是用4個字節(實際上只用了31位,最高位必須為0)。世界上任何一個字符都可以用一個Unicode編碼來表示,一旦字符的Unicode編碼確定下來后,就不會再改變了。但是Unicode有一定的局限性,一個Unicode字符在網絡上傳輸或者最終存儲起來的時候,並不見得每個字符都需要兩個字節,比如一字符“A“,用一個字節就可以表示的字符,卻使用兩個字節,太浪費空間了。UTF-8(8-bit Unicode Transformation Format)就出現了,UTF-8是一種針對Unicode的可變長度字符編碼,又稱萬國碼。UTF-8用1到6個字節編碼Unicode字符。

 

    第二:python 字符串類型

  python2.X 系統的默認編碼是ASCII,3.X系統就是unicode,所以在2.X系列遇到的編碼問題會更多。

1
2
3
4
5
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
 
 

  現在會遇到第一類問題,在python源代碼文件中如果不顯示地指定編碼的話,將出現語法錯誤:

    這個提示很明顯,非ASCII碼在源代碼中出現了。單純的出現這類問題,可以采用以下方法解決:

   方法一:在 文件前指定編碼格式

#!/usr/bin/env python
#coding:UTF-8

 方法二:設置整個系統的字符編碼才能解決問題:(結合方法一一起使用)

default_encoding = 'utf-8'
if sys.getdefaultencoding() != default_encoding:
reload(sys)
sys.setdefaultencoding(default_encoding)

   方法一和方法二都嘗試了還有問題,可能就是你的編輯器的顯示問題了,我使用的是pycharm,在file-setting里可以這樣的設置:

   調試IDE編碼和project編碼后,終於能打印中文了:

之前,剛學習python的時候,總結的一段:

  1. 不設置源文件編碼格式,輸入中文,后直接打印,會提示存在‘non-ascii’,編譯不通過

  2. 設置源文件編碼格式為gbk,輸入中文后,打印亂碼

  3. 設置源文件編碼格式為gbk,輸入中文s1 = u'測試'后,打印正常

  4. 設置源文件編碼格式為gbk,輸入中文后,先將字符串解碼decode或者unicode方法,后打印正常

 5. 設置源文件編碼格式為utf-8,輸入中文后直接輸出正常

  6. 設置工具和工程的默認編碼為gbk,輸入中文后,打印正常。

     從python2.0開始,就有一種新的數據類型 Unicode Strings,但是在python3的到來,這個概念已經被弱化了。python2.*的默認編碼格式是ASCII碼,而python3.*的默認編碼格式已經換成了Unicode。在python2中和字符串相關的數據類型,分別是strunicode兩種,他們都是basestring的子類,可見str與unicode是兩種不同類型的字符串對象。區分一個

變量是字符還是unicode,可以使用type方法:

>>> a=''
>>> type(a)
<type 'str'>
>>> a
'\xe5\xa5\xbd'

>>> b=u''
>>> type(b)
<type 'unicode'>
>>> b
u
'\u597d'

Python中str和unicode之間是如何轉換的呢?這兩種類型的字符串類型之間的轉換就是靠這兩個方法decodeencode

 

      這2個函數的具體使用,就不舉例了。網上這類文章挺多的。這時就有可能遇到第二個問題,前台傳入的中文在后台亂碼,無法處理。一般出現這類問題,都是前后台編碼格式不一致導致的;

     方法一:統一前后台編碼格式;

     方法二:如果無法統一,那取數據的時候就需要進行轉碼處理,之前我遇到一個問題,前台傳入的是GBK格式的中文,我是做后台處理的,后台全系統都是用utf8編碼的,接收到GBK的http請求后,顯示的中文是亂碼的,導致解析那段GBK的xml都報異常。后面做了調整,再接收到前台的GBK字符串后,首先decode(gbk)再encode(utf8)就成功了。

  第三:操作DB,需要了解的mysql字符集。

      使用python對DB的操作,隨時都有可能出現亂碼。網上搜以下2個方法偶爾也能解決問題:

    方法一:conn = MySQLdb.connect(self.host,self.username,self.password,self.database,charset='gbk')

    方法二:

      dbSqlCursor.execute('SET NAMES gbk;')        
      dbSqlCursor.execute('SET CHARACTER SET gbk;')        
      dbSqlCursor.execute('SET character_set_connection=gbk;')
    有時候就不能了,如果不了解原因,估計想撞牆了。於是我鼓足了勇氣,查閱了下mysql官方文檔的這部分的小內容:https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html;看完我知道自己下一部深入學習的方向了。這里總結下我粗學的一些皮毛:
       字符集和校驗規則變量涉及到客戶端與服務器的交互。
       數據從客戶端到服務器端的解析入DB的時候,數據字符集是怎么變化的呢?客戶端和服務器建立連接后,客戶端以什么字符編碼發送數據?服務器收到客戶端發送的數據后,會將語句翻譯成什么樣的字符集呢?服務器處理后,在將結果集或錯誤消息返回給客戶端之前,服務器應該翻譯什么字符集?千萬不要自以為一個mysql環境變量就能完成這些事情,沒那么簡單。

1、 客戶端和服務器建立連接后,客戶端以什么字符編碼發送數據?

   答:服務器以character_set_client系統變量被設置, conn = MySQLdb.connect(self.host,self.username,self.password,self.database,charset='gbk') 只是設置了客戶端發生數據的字符編碼格式

 

2、服務器在收到語句后將其翻譯成什么字符集?

   答:服務器使用character_set_connection和collation_connection系統變量。它將收到的字符集從character_set_client轉到character_set_connection,然后再進行處理。

 

3、服務器處理完后,將結果集或錯誤消息返回給客戶端之前,服務器應該翻譯什么字符集?

    答:character_set_results系統變量指定的服務器返回查詢結果給客戶端的字符。這包括結果數據,如列值,以及結果元數據,如列名和錯誤消息。

    其中,character_set_results這些都是mysql的系統環境變量,要想弄清楚mysql的字符集的系統變量,可以查看官網:https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_collation_connection;里面有詳細介紹這幾種mysql字符集系統變量的信息,部分截圖如下:
 
其中: character_set_client系統變量的詳細介紹:
     這些在這個過程中,有一些mysql的變量比較重要:
1、服務器字符集和整理的character_set_server和collation_server,character_set_connection系統變量的值。
2、默認的數據庫的字符集和整理的character_set_database和collation_database系統變量的值。
3、數據庫返回的字符集,character_set_results。
   基本操作DB時,注意這幾個變量就能解決很問題,官網還有介紹:
   Two statements affect the connection-related character set    variables as a group:
  • SET NAMES 'charset_name'   [COLLATE  'collation_name']

     A SET NAMES 'charset_name'      statement is equivalent to these three statements:         

    SET character_set_client = charset_name;
    SET character_set_results = charset_name;
    SET character_set_connection = charset_name;
  • SET CHARACTER SET   'charset_name'         

     A    SET  CHARACTER SET   charset_name statement     is equivalent to these three statements:         

    SET character_set_client = charset_name;
    SET character_set_results = charset_name;
    SET collation_connection = @@collation_database;

     這時看看百度經常給的解決方法2,嘿嘿,后面2個操作多余了吧。

      在實際操作的過程中,遇到過亂碼的問題,了解原理后,明白是因為客戶端在發送數據給DB時,SET character_set_connection=gbk;實際上我的DB的編碼格式是Latin1的;在連接的時候,修改charset='latin1',就可以了。

     還有遇到DB返回SQL Error: 1366: Incorrect string value: "\xE8\xAF\xA6\xE7\xBB\x86…" for column "address" at row 1 問題

     這時,需要確定數據的字符集,表的字符集,列的字符集;如果列的字符集是指定的,就會直接使用列的字符集,這個優先級最高。后面我修改了列的字符集成utf8,就解決了。

     以上提到的這些問題,其實在mysql的官網這些原理都有詳細的說明。這塊我就沒仔細看了;后面還真可以好好自學下,部分有些問題遇到后,沒來得及截圖,導致現在寫總結沒啥實例,之后會注意下這個問題。學習真的不是一蹴而就的事情,是一件需要持續不停的事情,鵝廠呆了半年,終於差不多可以把氣喘勻了,又可以繼續我的學習之路了。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com 联系我们: