本篇讨论PyQt4中的中文处理问题。
Qt中的QString与QByteArray之间的关系,近似等同于Python 2.x中的unicode与str的关系,以及Python 3.x中的str与bytes之间的关系。PyQt提供了Qt类型(包括C/C++类型)与Python原始类型之间的隐式转换。而PyQt4、PyQt5、Python 2.x、Python 3.x的隐式转换方法都不尽相同,由此产生了4种排列组合的情况。本文讨论其中的两种:PyQt4 + Python 2.x 以及 PyQt4 + Python 3.x
1. PyQt4 + Python 2.x
1.1 QByteArray与Python类型互转
QByteArray这个类型其实和中文等国际字符没有什么太大的关系。和Qt一样,PyQt4中的QByteArray也是保存8位的原始码流。通常情况下我们会从一个IO设备中获得这样的码流。
当我们得到一个QByteArray后,它可能有以下几种去处:自己发生一些简单的运算(例如,取长度、截断等)
提供给一个需要QByteArray的场合
提供给一个需要QString的场合
提供给一个需要str的场合
提供给一个需要unicode的场合
对于情况1和2,在此不必多说。
对于情况3,这属于Qt部分的内容,可参阅QTextCodec这个类。简单的说,你必须知道这个QByteArray实例的字符编码方式,以便于拿到正确的解码。
对于情况4,由于PyQt4在QByteArray内部实现了__str__方法,所以str()作用于QByteArray是没有问题的。
from PyQt4.QtCore import *
f = QFile('test_in.txt')
if f.open(QIODevice.ReadOnly):
hello_text = f.readAll()
f.close()
with open('text_out.txt','w') as f2:
hello_text_2 = hello_text + " end"
hello_text_3 = "before" + hello_text
print type(hello_text_2)
print type(hello_text_3)
f2.write(hello_text_3)
f2.write(hello_text_2)
f2.write(hello_text.join(['1','2'])) #error
f2.write(str(hello_text).join(['1','2']))为什么hello_text_2和hello_text_3都是QByteArray类型而不是Python的str类型?这是因为PyQt4的QByteArray实现了__add__和__radd__。
对于情况5,同样需要知道QByteArray类型变量的编码方式,才能用Python的codecs模块解码。
from PyQt4.QtCore import *
import codecs
f = QFile('test_in.txt')
if f.open(QIODevice.ReadOnly):
hello_text = f.readAll()
f.close()
with codecs.open('text_out.txt',encoding='utf-8',mode='w') as f2:
f2.write(codecs.decode(hello_text,'gb2312'))
代码中,f2.write()需要一个Python的unicode类型。使用codecs.decode函数对hello_text进行解码,其中存在QByteArray到str的隐式转换。最终会得到一个以UTF-8编码的『你好』文件。
反过来,当一个PyQt的函数需要QByteArray的时候,如何将一个Python类型赋予它呢?规则是这样的:对于str类型和只含有ASCII的unicode类型,直接使用或通过QByteArray()构造函数
对于unicode类型,要使用codecs进行编码。
# -*- coding: utf-8 -*-
from PyQt4.QtCore import *
import codecs
f = QFile('test_out.txt')
text1 = u'hello'
text2 = u'你好'
if f.open(QIODevice.WriteOnly):
f.write(text1)
f.write(text2) #error
f.write(codecs.encode(text2,'utf-8'))
f.close()
这段代码需要保存为无BOM的UTF-8。实际上,从QByteArray到Python环境,首先是要做str()的强制转换的,而str()强制转换并不改变QByteArray内部的数据。
1.2 QString与Python类型互转
QString类型与Python的str类型并不直接对应,而是对应于类型unicode。所以,当把一个Python类型传送给需要QString类型的函数时,首先应该考虑该类型是否能够顺利的转为unicode类型。
对于只含有ASCII字符的str类型实例,其与unicode类型的转换是自由的。所以对于不涉及国际字符的情况,大可放心的在PyQt下使用Python的str。一旦涉及国际字符,则需要知道该字符的编码,并使用codecs模块进行解码。
例如,有一个文本文件保存了GB2312编码的『你好』,我们用Python的IO来读取这个文件,并使用MessageBox显示出来。
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import codecs
import sys
app = QApplication(sys.argv)
with open('test_in.txt') as f:
hello_text = f.read()
f.close()
QMessageBox.information(
None,codecs.decode(hello_text,'gb2312'),
codecs.decode(hello_text,'gb2312')
)
exit(app.exec_())看起来好麻烦的样子。能不能让解码过程一次就设好呢?答案是肯定的。我们只需要利用Qt的QTextCodec进行一次全局的设置,并将Python的str类型直接赋给需要QString作为参数的函数。
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
QTextCodec.setCodecForCStrings(QTextCodec.codecForName("GB2312"))
app = QApplication(sys.argv)
with open('test_in.txt') as f:
hello_text = f.read()
f.close()
QMessageBox.information(
None,hello_text,
hello_text
)
exit(app.exec_())反之,从Qt拿到的QString如何赋给Python环境?用unicode()转换一下就行了。当然,如果你清楚地知道QString变量里只含有ASCII码,那么str()也是可以的。当然,你可以借用QTextCodec.fromUnicode把QString转换成QByteArray,然后再从QByteArray转换成Python所需要的类型。
总结:在PyQt4+Python 2.x中,QByteArray和str是一致的,都是原始的8位码流;QString和unicode是一致的,都是可以存储国际字符的变量。可以利用Qt的QTextCodec或者Python的codecs进行国际字符的编解码。我们可以用一张图来表示:
2. PyQt4 + Python 3.x
2.1 QByteArray与Python类型互转
在Python 3.x中,QByteArray与bytes类型一一对应。从Qt读文件再用Python写入的例子变成了这样:
from PyQt4.QtCore import *
f = QFile('test_in.txt')
if f.open(QIODevice.ReadOnly):
hello_text = f.readAll()
f.close()
with open('text_out.txt','w') as f2:
f2.write(bytes(hello_text).decode("gb2312"))
Python的codecs模块依然有效,故可以书写Python 2.x/3.x兼容的代码。
from PyQt4.QtCore import *
import codecs
f = QFile('test_in.txt')
if f.open(QIODevice.ReadOnly):
hello_text = f.readAll()
f.close()
with codecs.open('text_out.txt',encoding='utf-8',mode='w') as f2:
f2.write(codecs.decode(hello_text,'gb2312'))
这是因为codecs.decode在Python 2.x和Python 3.x对『原始数据』(即第一个参数)做了合理的处理。
而Qt中需要QByteArray的场合,可以用bytes类型予以代入。
# -*- coding: utf-8 -*-
from PyQt4.QtCore import *
f = QFile('test_out.txt')
text1 = 'hello'
text2 = '你好'
if f.open(QIODevice.WriteOnly):
f.write(text1)
f.write(text2) #error
f.write(text2.encode('utf-8'))
f.close()
这段代码需要保存为无BOM的UTF-8。当然,利用codecs也可以写出Python 2.x/3.x兼容的代码。
2.2 QString与Python类型互转
在Python 3.x中,QString类型『消失』了。声明一个QString类型的变量将引发QString类无法找到的错误。
凡需要QString的Qt函数,可以直接赋以str,而返回QString的Qt函数均返回类型str。
最后,由于只含有ASCII的bytes类型与str类型有自动编解码的关系,在这种情况下bytes类型和QString类型也是互通的。
总结图如下:
3. 自由选择2.x和3.x风格的QString
Python 3.x 中去掉了QString,带来了很多方便。我们希望在Python 2.x 中也能用到这么方便的隐式转换;但Python 3.x 的QString带来代码的向下兼容问题,而且QString自身丰富的处理函数也消失了,故有时候我们想在Python 3.x 中能用到显式的QString。
使用PyQt4自带的sip模块,能够任意的选择2.x和3.x风格的QString实现。命令是
import sip
sip.setapi('QString',1) # 1 = Python 2.x; 2 = Python 3.x
from PyQt4.QtCore import *
...
setapi('QString',x)只能运行一次,由于import PyQt4的过程中会尝试重设sip.setapi,故setapi必须在import PyQt4之前进行。
如果在Python 2.x 环境下选择了 sip.setapi('QString',2),那么QString类会消失,取而代之的是Python自带的unicode类;如果在Python 3.x 环境下选择了 sip.setapi('QString',1),那么QString类会出现,并与str类形成等价关系。
4.其他字符串的表示法
在PyQt4中:凡以char *,unsigned char *表示的'\0'结尾字符串,均与QByteArray对待的形式相同。
凡以char,unsigned char表示的字符,视同长度为1的QByteArray
凡以QChar表示的字符,如果QString存在,视同长度为1的QString;如果QString不存在,那么QChar也不存在。
(未完待续)