在py2.7的項目中用了__future__模塊中的 unicode_literals 來爲兼容py3.x做準備,今天遇到一個UnicodeEncodeError的錯誤,跟了下,發現這個小坑值得注意。是怎麼樣的一個坑呢?跟着代碼看看。順便深究一下原理。
1. 未引入unicode_literals版本
#coding:utf-8
from datetime import datetime
now = datetime.now()
print now.strftime('%m月%d日 %H:%M')
這段代碼可以正常執行輸出: 03月12日 21:53
2. 引入unicode_literals
#coding:utf-8
from __future__ import unicode_literals
from datetime import datetime
now = datetime.now()
print now.strftime('%m月%d日 %H:%M')
拋出如下錯誤:
Traceback (most recent call last):
File "unicode_error_demo2.py", line 7, in <module>
print now.strftime('%m月%d日 %H:%M')
UnicodeEncodeError: 'ascii' codec can't encode character u'\u6708' in position 2: ordinal not in range(128)
3. 解決方案一:設置運行時編碼爲utf-8
#coding:utf-8
from __future__ import unicode_literals
import sys
from datetime import datetime
reload(sys)
sys.setdefaultencoding('utf-8')
now = datetime.now()
print now.strftime('%m月%d日 %H:%M')
正常執行
4. 解決方案二: 使用byte string
#coding:utf-8
from __future__ import unicode_literals
from datetime import datetime
now = datetime.now()
print now.strftime(b'%m月%d日 %H:%M') # 指明爲bytearray字符串
# 或者這樣也行
t = bytearray('%m月 %d %H:%M', 'utf-8')
print now.strftime(str(t))
5. 總結
這裏主要涉及到python中的編碼問題,也是很多人在剛接觸Python時感到頭疼的問題。更多基礎的東西,可以到下面的參考鏈接裏看,這裏就分析下我的這幾段代碼。
先來看 第一段代碼 ,第一段能成功執行是正常的,因爲datetime的strftime函數,接受的參數就是string(注意:string表示字節,unicode表示字符串,見參考1),因此是正常的,strftime接收到string,然後格式化最後返回。
第二段例子 我們引入了來自__future__的unicode_literals,這個模塊的作用就是把你當前模塊所以的字符串(string literals)轉爲unicode。基於這個認識來看代碼,雖然我們給 now.strftime 傳遞的還是一樣的參數,但本質已經不同——一個是string(字節)一個是unicode(字符)。而 strftime 能夠接收的參數應該是string類型的,那咱們傳了一個unicode進去,它必然要轉換一下,這一轉換就出錯了——UnicodeEncodeError。
這個地方應該詳細說下,咱們給定了一個unicode字符"月",要被轉爲string,怎麼轉呢?這時就得想到ASCII了,這是Python2.7運行時默認的編碼環境。所謂"編碼"就是用來編碼的嘛,於是python就通過ASCII來把unicode轉爲string,遂,拋錯了。
錯誤的原因在Traceback中詳細指明瞭——咱們傳進去的u'u6708' (也就是"月"字)ascii解釋不了。這個符號不在ascii的128個字符表當中,因此就拋錯了。關於字符編碼方面的內容可以查看參考5。
再來說 第三段代碼 ,我們重載了系統的編碼環境爲utf-8,於是上面的那個問題消失了,簡單來說就是utf-8可以表示更多的字符。
最後來看 第四段代碼 ,我們通過把字符串定義爲byte類型同樣解決了那個錯誤。原理也很簡單,就是先把unicode轉換爲bytes,然後再轉爲string。這段代碼裏提供了兩種方法,一個是在字符串前加 b 來聲明一個bytes(而不是unicode);第二個是對生成的unicode對象通過utf-8進行編碼爲bytearray,然後轉爲string。這個問題可以查看參考4和參考6。
上面都是the5fire自己根據資料總結出來的結論,如果有問題歡迎指出。
PS: 同樣的問題對於python built-in的getattr方法也適用。
參考資料:
- 黃聰:解決python中文處理亂碼,先要弄懂“字符”和“字節”的差別
- http://docs.python.org/2/library/datetime.html#datetime.date.strftime
- http://docs.python.org/2.7/library/functions.html#getattr
- http://docs.python.org/2/whatsnew/2.6.html?highlight=bytestring#pep-3112-byte-literals
- http://www.cnblogs.com/huxi/articles/1897271.html
- http://stackoverflow.com/questions/6269765/what-does-the-b-character-do-in-front-of-a-string-literal