terça-feira, 7 de dezembro de 2010

Uso de Python3 em programação desktop usando PyQt4

O python3 é parcialmente incompatível com o Python2.x, e isso é um grande problema principalmente porque existem milhares de bibliotecas prontas e funcionando bem com o python2 , a realidade é que igualmente ao XP o python2.x vai demorar muito tempo para morrer, principalmente nos frameworks web que possuem uma boa gama de módulos prontinhos e funcionando.
No entanto o Python3 trouxe consigo diversas melhorias com relação à sua versão anterior, e a mais notável com certeza é a de uso de strings unicode, agora é possível criar uma variável com o nome "maçã" por exemplo. Em especial, o PyQt4 também sofreu muitas mudanças, na realidade ele ficou mais simples, uma porcão de objetos que antes existiam no PyQt4 para python2.x não existem mais para o PyQt4 para python3, o que causa problemas ao tentar executar programas em PyQt4 escritos para rodarem no Python2.x, existem algumas "gambiarras" como essa abaixo, no entanto esse método possui algumas falhas graves em relação a uma outra opção que eu coloquei no final do post que é o uso da biblioteca sip do python3, nesse exemplo a QString irá funcionar, no entanto os métodos dela estarão ausente causando dores de cabeça, em seguida eu coloquei um exemplo de uma calculadora simples em PyQt feita por uma amiga que está estudando a linguagem e eu resolvi alterar algumas coisas para colocar como exemplo aqui, é um bom começo para estudo, o código principal pode ser salvo com o nome calculadora.py e o da classe de interface obrigatoriamente tem que ser ui_calculadora.py porque ele é importado para a classe principal.

try:
    from PyQt4.QtCore import QString
except ImportError:
    # we are using Python3 so QString is not defined
    QString = type("") 
 
Eis o Exemplo em Python 2.x 
 
#!/usr/bin/python
# -*- coding: utf-8 -*-
#importação dos módulos das bibliotecas Qt
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
import ui_calculadora
#Importação dos módulos personalizados
class Calculadora(QMainWindow, ui_calculadora.Ui_Calculadora):
    
    '''
        Classe Principal que cria uma agenda eletrônica Simples :D
    '''
# método init construtor da classe...
    def __init__(self, parent=None):
        #os dois comandos abaixo inicializa a classe com as duas das quais ela herdou métodos e atributos
        #QMainWindow que é uma classe base do Qt framework e ui_calculadora que é a interface criada
        super(Calculadora, self).__init__(parent)
        self.setupUi(self)
        self.edicao = True
        self.memoria = 0
        #conectando os objetos da interface com as funções
        self.connect(self.btnMemoriaNegativa, SIGNAL("clicked()"), self.memoriaNegativa)
        self.connect(self.btnMemoriaPositiva, SIGNAL("clicked()"), self.memoriaPositiva)
        self.connect(self.btnLimparMemoria, SIGNAL("clicked()"), self.limparMemoria)
        self.connect(self.btnNumeros, SIGNAL("buttonClicked(QAbstractButton*)"), self.botaoClickado)
        self.connect(self.btnIgual, SIGNAL("clicked()"), self.resultado)
    
    def limparMemoria(self):
        self.memoria = 0
        self.lblMensagem.setText("0")
        self.edicao = False
    def memoriaNegativa(self):
        self.memoria -= float(str(self.txtCalculo.text()))
        self.lblMensagem.setText(str(self.memoria))
        self.edicao = False
    def memoriaPositiva(self):
        self.memoria += float(str(self.txtCalculo.text()))
        self.lblMensagem.setText(str(self.memoria))
        self.edicao = False
    
    def botaoClickado(self, botao):
        if self.edicao:
            self.txtCalculo.insert(botao.text().toUpper())
        else:
            self.txtCalculo.setText(botao.text().toUpper())
            self.edicao = True
    
    def resultado(self):
        try:
            resultado = eval(str(self.txtCalculo.text() + "."))
            self.txtCalculo.setText(str(resultado))
            self.edicao = False
        except:
            # o caractere u antes da string indica que a mesma é unicode...
            QMessageBox.information(self, u"Expressão incorreta", u"Você digitou uma expressão incorreta",
                                    buttons=QMessageBox.Ok)
            self.txtCalculo.clear()    

#Função que inicializa o aplicativo
if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    formulario = Calculadora()
    formulario.show()
    app.exec_()


Agora a Versão em Python3


#!/usr/bin/python
# -*- coding: utf-8 -*-
#importação dos módulos das bibliotecas Qt
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
import ui_calculadora
#Importação dos módulos personalizados
class Calculadora(QMainWindow, ui_calculadora.Ui_Calculadora):
    '''
        Classe Principal que cria uma agenda eletrônica Simples :D
    '''
# método init construtor da classe...
    def __init__(self, parent=None):
        #os dois comandos abaixo inicializa a classe com as duas das quais ela herdou métodos e atributos
        #QMainWindow que é uma classe base do Qt framework e ui_calculadora que é a interface criada
        super(Calculadora, self).__init__(parent)
        self.setupUi(self)
        self.edicao = True
        self.memoria = 0
        #conectando os objetos da interface com as funções
        self.connect(self.btnMemoriaNegativa, SIGNAL("clicked()"), self.memoriaNegativa)
        self.connect(self.btnMemoriaPositiva, SIGNAL("clicked()"), self.memoriaPositiva)
        self.connect(self.btnLimparMemoria, SIGNAL("clicked()"), self.limparMemoria)
        self.connect(self.btnNumeros, SIGNAL("buttonClicked(QAbstractButton*)"), self.botaoClickado)
        self.connect(self.btnIgual, SIGNAL("clicked()"), self.resultado)
    
    def limparMemoria(self):
        self.memoria = 0
        self.lblMensagem.setText("0")
        self.edicao = False
    def memoriaNegativa(self):
        self.memoria -= float(str(self.txtCalculo.text()))
        self.lblMensagem.setText(str(self.memoria))
        self.edicao = False
    def memoriaPositiva(self):
        self.memoria += float(str(self.txtCalculo.text()))
        self.lblMensagem.setText(str(self.memoria))
        self.edicao = False
    
    def botaoClickado(self, botao):
        if self.edicao:
            self.txtCalculo.insert(botao.text())
        else:
            self.txtCalculo.setText(botao.text())
            self.edicao = True
    
    def resultado(self):
        try:
            resultado = eval(str(self.txtCalculo.text() + "."))
            self.txtCalculo.setText(str(resultado))
            self.edicao = False
        except:
            # o caractere u antes da string indica que a mesma é unicode...
            QMessageBox.information(self, "Expressão incorreta", "Você digitou uma expressão incorreta",
                                    buttons=QMessageBox.Ok)
            self.txtCalculo.clear()    

#Função que inicializa o aplicativo
if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    formulario = Calculadora()
    formulario.show()
    app.exec_()


Agora o arquivo de interface(ui_calculadora.py) ele funciona normal em python2 ou 3, lembrando que ele foi feito usando o pyuic4 através de um arquivo ui do QtDesigner.


# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'calculadora.ui'
#
# Created: Tue Nov 30 20:07:38 2010
#      by: PyQt4 UI code generator 4.8.1
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class Ui_Calculadora(object):
    def setupUi(self, Calculadora):
        Calculadora.setObjectName(_fromUtf8("Calculadora"))
        Calculadora.resize(419, 344)
        Calculadora.setMinimumSize(QtCore.QSize(419, 290))
        self.centralwidget = QtGui.QWidget(Calculadora)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.gridLayout_3 = QtGui.QGridLayout(self.centralwidget)
        self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3"))
        self.txtCalculo = QtGui.QLineEdit(self.centralwidget)
        self.txtCalculo.setMinimumSize(QtCore.QSize(401, 41))
        self.txtCalculo.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
        self.txtCalculo.setObjectName(_fromUtf8("txtCalculo"))
        self.gridLayout_3.addWidget(self.txtCalculo, 0, 0, 1, 2)
        self.btnLimpar = QtGui.QPushButton(self.centralwidget)
        self.btnLimpar.setMinimumSize(QtCore.QSize(61, 41))
        self.btnLimpar.setMaximumSize(QtCore.QSize(120, 16777215))
        self.btnLimpar.setObjectName(_fromUtf8("btnLimpar"))
        self.gridLayout_3.addWidget(self.btnLimpar, 2, 0, 1, 1)
        self.gridLayout = QtGui.QGridLayout()
        self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
        self.btn7 = QtGui.QPushButton(self.centralwidget)
        self.btn7.setMinimumSize(QtCore.QSize(61, 41))
        self.btn7.setObjectName(_fromUtf8("btn7"))
        self.btnNumeros = QtGui.QButtonGroup(Calculadora)
        self.btnNumeros.setObjectName(_fromUtf8("btnNumeros"))
        self.btnNumeros.addButton(self.btn7)
        self.gridLayout.addWidget(self.btn7, 0, 0, 1, 1)
        self.btn8 = QtGui.QPushButton(self.centralwidget)
        self.btn8.setMinimumSize(QtCore.QSize(61, 41))
        self.btn8.setObjectName(_fromUtf8("btn8"))
        self.btnNumeros.addButton(self.btn8)
        self.gridLayout.addWidget(self.btn8, 0, 1, 1, 1)
        self.btn9 = QtGui.QPushButton(self.centralwidget)
        self.btn9.setMinimumSize(QtCore.QSize(61, 41))
        self.btn9.setObjectName(_fromUtf8("btn9"))
        self.btnNumeros.addButton(self.btn9)
        self.gridLayout.addWidget(self.btn9, 0, 2, 1, 1)
        self.btn4 = QtGui.QPushButton(self.centralwidget)
        self.btn4.setMinimumSize(QtCore.QSize(61, 41))
        self.btn4.setObjectName(_fromUtf8("btn4"))
        self.btnNumeros.addButton(self.btn4)
        self.gridLayout.addWidget(self.btn4, 1, 0, 1, 1)
        self.btn5 = QtGui.QPushButton(self.centralwidget)
        self.btn5.setMinimumSize(QtCore.QSize(61, 41))
        self.btn5.setObjectName(_fromUtf8("btn5"))
        self.btnNumeros.addButton(self.btn5)
        self.gridLayout.addWidget(self.btn5, 1, 1, 1, 1)
        self.btn6 = QtGui.QPushButton(self.centralwidget)
        self.btn6.setMinimumSize(QtCore.QSize(61, 41))
        self.btn6.setObjectName(_fromUtf8("btn6"))
        self.btnNumeros.addButton(self.btn6)
        self.gridLayout.addWidget(self.btn6, 1, 2, 1, 1)
        self.btn1 = QtGui.QPushButton(self.centralwidget)
        self.btn1.setMinimumSize(QtCore.QSize(61, 41))
        self.btn1.setObjectName(_fromUtf8("btn1"))
        self.btnNumeros.addButton(self.btn1)
        self.gridLayout.addWidget(self.btn1, 2, 0, 1, 1)
        self.btn2 = QtGui.QPushButton(self.centralwidget)
        self.btn2.setMinimumSize(QtCore.QSize(61, 41))
        self.btn2.setObjectName(_fromUtf8("btn2"))
        self.btnNumeros.addButton(self.btn2)
        self.gridLayout.addWidget(self.btn2, 2, 1, 1, 1)
        self.btn3 = QtGui.QPushButton(self.centralwidget)
        self.btn3.setMinimumSize(QtCore.QSize(61, 41))
        self.btn3.setObjectName(_fromUtf8("btn3"))
        self.btnNumeros.addButton(self.btn3)
        self.gridLayout.addWidget(self.btn3, 2, 2, 1, 1)
        self.btnNegativo = QtGui.QPushButton(self.centralwidget)
        self.btnNegativo.setMinimumSize(QtCore.QSize(61, 41))
        self.btnNegativo.setObjectName(_fromUtf8("btnNegativo"))
        self.gridLayout.addWidget(self.btnNegativo, 3, 0, 1, 1)
        self.btn0 = QtGui.QPushButton(self.centralwidget)
        self.btn0.setMinimumSize(QtCore.QSize(61, 41))
        self.btn0.setObjectName(_fromUtf8("btn0"))
        self.btnNumeros.addButton(self.btn0)
        self.gridLayout.addWidget(self.btn0, 3, 1, 1, 1)
        self.btnPonto = QtGui.QPushButton(self.centralwidget)
        self.btnPonto.setMinimumSize(QtCore.QSize(61, 41))
        self.btnPonto.setObjectName(_fromUtf8("btnPonto"))
        self.btnNumeros.addButton(self.btnPonto)
        self.gridLayout.addWidget(self.btnPonto, 3, 2, 1, 1)
        self.gridLayout_3.addLayout(self.gridLayout, 3, 0, 1, 1)
        self.gridLayout_2 = QtGui.QGridLayout()
        self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
        self.btnDiv = QtGui.QPushButton(self.centralwidget)
        self.btnDiv.setMinimumSize(QtCore.QSize(61, 41))
        self.btnDiv.setObjectName(_fromUtf8("btnDiv"))
        self.btnNumeros.addButton(self.btnDiv)
        self.gridLayout_2.addWidget(self.btnDiv, 0, 0, 1, 1)
        self.btnMult = QtGui.QPushButton(self.centralwidget)
        self.btnMult.setMinimumSize(QtCore.QSize(61, 41))
        self.btnMult.setObjectName(_fromUtf8("btnMult"))
        self.btnNumeros.addButton(self.btnMult)
        self.gridLayout_2.addWidget(self.btnMult, 1, 0, 1, 1)
        self.btnMenos = QtGui.QPushButton(self.centralwidget)
        self.btnMenos.setMinimumSize(QtCore.QSize(61, 41))
        self.btnMenos.setObjectName(_fromUtf8("btnMenos"))
        self.btnNumeros.addButton(self.btnMenos)
        self.gridLayout_2.addWidget(self.btnMenos, 2, 0, 1, 1)
        self.btnMais = QtGui.QPushButton(self.centralwidget)
        self.btnMais.setMinimumSize(QtCore.QSize(61, 41))
        self.btnMais.setObjectName(_fromUtf8("btnMais"))
        self.btnNumeros.addButton(self.btnMais)
        self.gridLayout_2.addWidget(self.btnMais, 3, 0, 1, 1)
        self.btnIgual = QtGui.QPushButton(self.centralwidget)
        self.btnIgual.setMinimumSize(QtCore.QSize(61, 41))
        self.btnIgual.setObjectName(_fromUtf8("btnIgual"))
        self.gridLayout_2.addWidget(self.btnIgual, 3, 2, 1, 1)
        self.btnMemoriaNegativa = QtGui.QPushButton(self.centralwidget)
        self.btnMemoriaNegativa.setMinimumSize(QtCore.QSize(61, 41))
        self.btnMemoriaNegativa.setObjectName(_fromUtf8("btnMemoriaNegativa"))
        self.gridLayout_2.addWidget(self.btnMemoriaNegativa, 2, 2, 1, 1)
        self.btnMemoriaPositiva = QtGui.QPushButton(self.centralwidget)
        self.btnMemoriaPositiva.setMinimumSize(QtCore.QSize(61, 41))
        self.btnMemoriaPositiva.setObjectName(_fromUtf8("btnMemoriaPositiva"))
        self.gridLayout_2.addWidget(self.btnMemoriaPositiva, 1, 2, 1, 1)
        self.btnLimparMemoria = QtGui.QPushButton(self.centralwidget)
        self.btnLimparMemoria.setMinimumSize(QtCore.QSize(61, 41))
        self.btnLimparMemoria.setObjectName(_fromUtf8("btnLimparMemoria"))
        self.gridLayout_2.addWidget(self.btnLimparMemoria, 0, 2, 1, 1)
        self.gridLayout_3.addLayout(self.gridLayout_2, 3, 1, 1, 1)
        self.lblMensagem = QtGui.QLabel(self.centralwidget)
        self.lblMensagem.setText(_fromUtf8(""))
        self.lblMensagem.setObjectName(_fromUtf8("lblMensagem"))
        self.gridLayout_3.addWidget(self.lblMensagem, 2, 1, 1, 1)
        Calculadora.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(Calculadora)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 419, 25))
        self.menubar.setObjectName(_fromUtf8("menubar"))
        self.menu_Arquivo = QtGui.QMenu(self.menubar)
        self.menu_Arquivo.setObjectName(_fromUtf8("menu_Arquivo"))
        Calculadora.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(Calculadora)
        self.statusbar.setObjectName(_fromUtf8("statusbar"))
        Calculadora.setStatusBar(self.statusbar)
        self.actionSair = QtGui.QAction(Calculadora)
        self.actionSair.setObjectName(_fromUtf8("actionSair"))
        self.menu_Arquivo.addAction(self.actionSair)
        self.menubar.addAction(self.menu_Arquivo.menuAction())

        self.retranslateUi(Calculadora)
        QtCore.QObject.connect(self.btnLimpar, QtCore.SIGNAL(_fromUtf8("clicked()")), self.txtCalculo.clear)
        QtCore.QMetaObject.connectSlotsByName(Calculadora)

    def retranslateUi(self, Calculadora):
        Calculadora.setWindowTitle(QtGui.QApplication.translate("Calculadora", "Calculadora Simples", None, QtGui.QApplication.UnicodeUTF8))
        self.btnLimpar.setText(QtGui.QApplication.translate("Calculadora", "&C", None, QtGui.QApplication.UnicodeUTF8))
        self.btn7.setText(QtGui.QApplication.translate("Calculadora", "7", None, QtGui.QApplication.UnicodeUTF8))
        self.btn8.setText(QtGui.QApplication.translate("Calculadora", "8", None, QtGui.QApplication.UnicodeUTF8))
        self.btn9.setText(QtGui.QApplication.translate("Calculadora", "9", None, QtGui.QApplication.UnicodeUTF8))
        self.btn4.setText(QtGui.QApplication.translate("Calculadora", "4", None, QtGui.QApplication.UnicodeUTF8))
        self.btn5.setText(QtGui.QApplication.translate("Calculadora", "5", None, QtGui.QApplication.UnicodeUTF8))
        self.btn6.setText(QtGui.QApplication.translate("Calculadora", "6", None, QtGui.QApplication.UnicodeUTF8))
        self.btn1.setText(QtGui.QApplication.translate("Calculadora", "1", None, QtGui.QApplication.UnicodeUTF8))
        self.btn2.setText(QtGui.QApplication.translate("Calculadora", "2", None, QtGui.QApplication.UnicodeUTF8))
        self.btn3.setText(QtGui.QApplication.translate("Calculadora", "3", None, QtGui.QApplication.UnicodeUTF8))
        self.btnNegativo.setText(QtGui.QApplication.translate("Calculadora", "+/-", None, QtGui.QApplication.UnicodeUTF8))
        self.btn0.setText(QtGui.QApplication.translate("Calculadora", "0", None, QtGui.QApplication.UnicodeUTF8))
        self.btnPonto.setText(QtGui.QApplication.translate("Calculadora", ".", None, QtGui.QApplication.UnicodeUTF8))
        self.btnDiv.setText(QtGui.QApplication.translate("Calculadora", "/", None, QtGui.QApplication.UnicodeUTF8))
        self.btnMult.setText(QtGui.QApplication.translate("Calculadora", "*", None, QtGui.QApplication.UnicodeUTF8))
        self.btnMenos.setText(QtGui.QApplication.translate("Calculadora", "-", None, QtGui.QApplication.UnicodeUTF8))
        self.btnMais.setText(QtGui.QApplication.translate("Calculadora", "+", None, QtGui.QApplication.UnicodeUTF8))
        self.btnIgual.setText(QtGui.QApplication.translate("Calculadora", "=", None, QtGui.QApplication.UnicodeUTF8))
        self.btnMemoriaNegativa.setText(QtGui.QApplication.translate("Calculadora", "M-", None, QtGui.QApplication.UnicodeUTF8))
        self.btnMemoriaPositiva.setText(QtGui.QApplication.translate("Calculadora", "M+", None, QtGui.QApplication.UnicodeUTF8))
        self.btnLimparMemoria.setText(QtGui.QApplication.translate("Calculadora", "MC", None, QtGui.QApplication.UnicodeUTF8))
        self.menu_Arquivo.setTitle(QtGui.QApplication.translate("Calculadora", "&Arquivo", None, QtGui.QApplication.UnicodeUTF8))
        self.actionSair.setText(QtGui.QApplication.translate("Calculadora", "Sair", None, QtGui.QApplication.UnicodeUTF8))


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    Calculadora = QtGui.QMainWindow()
    ui = Ui_Calculadora()
    ui.setupUi(Calculadora)
    Calculadora.show()
    sys.exit(app.exec_())


As diferenças são poucas porque o programa é pequeno, porém em programas maiores onde adicionamos objetos como:
QStringList e QString constantemente temos muitos problemas porque no PyQt para python3 os métodos ficarão diferentes por exemplo, o método QString para Strings maiúsculas é .toUpper() e para strings regulares do python é simplesmente .upper().

Dessa forma usando simplesmente a gambiarra que eu citei acima não irá funcionar porque quando invocarmos os métodos do QString eles não estarão presentes, felizmente existe no Python3 uma biblioteca que resolve isso pra nós, a sip, exemplo:


import sip

sip.setapi('QString', 1)

from PyQt4 import QtCore

Esse método funciona bem, no entanto quando temos funções que retornam QString's ou QStringList's do PyQt em Python2.x agora retornam objetos regulares do python e nem esse método e nem o anterior funcionam adequadamente, portanto é preciso realmente reescrever o código...