본문 바로가기
PyQt5_

Translating C++

by 자동매매 2023. 3. 16.

Appendix B: Translating C++

Examples to Python

When writing applications with PyQt6 we are really writing applications with Qt.

PyQt6 acts as a wrapper around the Qt libraries, translating Python method calls to C++, handling type conversions and transparently creating Python objects to represent Qt objects in your applications. The result of all this cleverness is that you can use Qt from Python while writing mostly Pythonic code — if we ignore the camelCase.

While there is a lot of PyQt6 example code out there, there are far more Qt C++ examples. The core documentation is written for C++. The library is written in C++. This means that sometimes, when you’re looking how to do something, the only resource you’ll find is a C++ tutorial or some C++ code.

Can you use it? Yes! If you have no experience with C++ (or C-like languages) then the code can look like gibberish. But before you were familiar with Python, Python probably looked a bit like gibberish too. You don’t need to be able to write C++ to be able to read it. Understanding and decoding is easier than writing.

With a little bit of effort you’ll be able to take any C++ example code and translate it into fully-functional Python & PyQt6. In this chapter we’ll take a snippet of Qt5 code and step-by-step convert it into fully-working Python code.

The example code

We’ll start with the following example block of code creating a simple window with a QPushButton and a QLineEdit. Pressing on the button will clear the line edit. Pretty exciting stuff, but this includes a few key parts of translating Qt examples to PyQt6 — namely, widgets, layouts and signals.

#include

int main ( int argc, char *argv[])

{

QApplication app(argc, argv);

QWidget window;

QLineEdit *lineEdit = new QLineEdit();

QPushButton *button = new QPushButton("Clear");

QHBoxLayout *layout = new QHBoxLayout();

layout->addWidget(lineEdit);

layout->addWidget(button);

QObject::connect(&button, &QPushButton::pressed,

&lineEdit, &QLineEdit::clear);

window.setLayout(layout);

window.setWindowTitle("Why?");

window.show();

return app.exec();

}

ë

Remember that a Qt widget without a parent is always a

separate window. Here we have a single window created as a

QWidget.

Below we’ll step through the process of converting this code to Python.

Imports

In C++ imports are called includes. They’re found at the top of the file, just as in Python (though only by convention) and look like this —

#include

In C-like languages the # indicates that include is a pre-processor directive not a comment. The value between <> is the name of the module to import. Note that unlike Python, importing a module makes all contents of that module available in

the global namespace. This is the equivalent of doing the following in Python —

from PyQt6.QtWidgets import *

Global imports like this are generally frowned upon in Python, and you should instead either —

  1. only import the objects you need, or
  2. import the module itself and use it to reference it’s children

from PyQt6.QtWidgets import QApplication, QWidget, QLineEdit,

QPushButton, QHBoxLayout

Or, alternatively...

from PyQt6 import QtWidgets

...and then reference as QtWidgets.QApplication(). Which you choose for your own code is entirely up to you, however in this example we’re going to follow the first style. Applying that to the code gives us the following result so far.

from PyQt6.QtWidgets import (

QApplication, QWidget, QLineEdit, QPushButton, QHBoxLayout

)

int main(int argc, char *argv[])

{

QApplication app(argc, argv);

QWidget window;

QLineEdit *lineEdit = new QLineEdit();

QPushButton *button = new QPushButton("Clear");

QHBoxLayout *layout = new QHBoxLayout();

layout->addWidget(lineEdit);

layout->addWidget(button);

QObject::connect(&button, &QPushButton::pressed,

&lineEdit, &QLineEdit::clear);

window.setLayout(layout);

window.setWindowTitle("Why?");

window.show();

return app. exec ();

}

Z

Since we’re making changes iteratively, the code won’t work

until the very end.

*int main(int argc, char argv[])

Every C++ program needs a main(){} block which contains the first code to be run when the application is executed. In Python any code at the top-level of the module (i.e. not indented inside a function, class or methods) will be run when the script is executed.

from PyQt6.QtWidgets import (

QApplication, QWidget, QLineEdit, QPushButton, QHBoxLayout

)

QApplication app(argc, argv);

QWidget window;

QLineEdit *lineEdit = new QLineEdit ();

QPushButton *button = new QPushButton ("Clear");

QHBoxLayout *layout = new QHBoxLayout ();

layout->addWidget(lineEdit);

layout->addWidget(button);

QObject::connect(&button, &QPushButton::pressed,

&lineEdit, &QLineEdit::clear);

window.setLayout(layout);

window.setWindowTitle("Why?");

window.show();

app.exec();

You may have seen the following code block in Python application code, which is also often referred to as the main block.

if __name__ == '__main__':

...your code here...

However, this works in a subtly different way. While this block will be run when a script is executed, so would any code that is not indented. The purpose of this block is actually to prevent this code executing when the module is imported, rather than executed as a script.

You can nest your code inside this block if you wish, although unless your file is going to be imported as a module it isn’t strictly necessary.

C++ types

Python is a dynamically typed language, meaning you can change the type of a

variable after it has been defined. For example, the following is perfectly valid Python.

a = 1

a = 'my string'

a = [ 1 , 2 , 3 ]

Many other languages, C++ included, are statically typed , meaning that once you define the type of a variable it cannot be changed. For example, the following is very definitely not valid C++.

int a = 1 ;

a = 'my string';

The above highlights an immediate consequence of static typing in languages: you define the type of a variable when you create it.

In C++ this is done explicitly by providing a type decorator on the line when the variable is defined, above int.

In lines like the following the first name is the name of type (class) that is being created by the remainder of the line.

QApplication app (argc, argv);

QWidget window;

QLineEdit *lineEdit = new QLineEdit ();

QPushButton *button = new QPushButton ("Clear");

QHBoxLayout *layout = new QHBoxLayout ();

In Python we do not need these type definitions, so we can just delete them.

lineEdit = new QLineEdit ();

button = new QPushButton ("Clear");

layout = new QHBoxLayout ();

For application and window it’s exactly the same principle. However, if you’re not familiar with C++ it might not be obvious those lines are creating an variable at all.

There are differences between creating objects with new and without in C++ but you don’t need to concern yourself with that in Python and can consider them both equivalent.

QWidget *window = new QWidget ();

QWidget window;

QApplication *app = new QApplication (argc, argv);

QApplication app;

To convert to Python, take the class name (e.g. QApplication) from the left, and place it in front of open and closing brackets (), adding them if they aren’t already there. Then move the name of the variable to the left, with an =. For window that gives us —

window = QWidget()

In Python QApplication only accepts a single parameter, a list of arguments from sys.argv (equivalent to argv). This gives us the code —

import sys

app = QApplication(sys.argv);

So far our complete code block is looking like the following.

from PyQt6.QtWidgets import (

QApplication, QWidget, QLineEdit, QPushButton, QHBoxLayout

)

import sys

app = QApplication(argc, argv);

window = QWidget()

lineEdit = QLineEdit();

button = QPushButton("Clear");

layout = QHBoxLayout();

layout->addWidget(lineEdit);

layout->addWidget(button);

QObject::connect(&button, &QPushButton::pressed,

&lineEdit, &QLineEdit::clear);

window.setLayout(layout);

window.setWindowTitle("Why?");

window.show();

app. exec ();

Signals

Signals are key to making the example work, and unfortunately the C++ syntax for Qt signals is a little tricky. The example signal we’re working with is shown below.

QObject::connect(&button, &QPushButton::pressed,

&lineEdit, &QLineEdit::clear);

If you’re not familiar with C++ this will be quite difficult to parse. But if we remove all the syntax it will get much clearer.

connect(button, QPushButton.pressed, lineEdit, QLineEdit.clear)

// or...

connect(, , , >)

Working from left to right we have, the object we’re connecting from, the signal we’re connecting from on that object, then the object we’re connecting to, then finally the slot (or function) we’re connecting to on that object. This is the equivalent of writing the following in PyQt6 —

button.pressed.connect(lineedit.clear)

Making that change gives us the following in progress code.

from PyQt6.QtWidgets import (

QApplication, QWidget, QLineEdit, QPushButton, QHBoxLayout

)

app = QApplication(sys.argv)

window = QWidget()

lineEdit = QLineEdit()

button = QPushButton("Clear")

layout = QHBoxLayout()

layout->addWidget(lineEdit);

layout->addWidget(button);

button.pressed.connect(lineEdit.clear)

window.setLayout(layout);

window.setWindowTitle("Why?");

window.show();

app. exec ();

Syntax

By now we’ve converted all the really troublesome parts, so we can do a final syntax-correction pass. These are a simple search-replace.

First search for all instances of -> or :: and replace with .. You’ll notice that the C++ code also uses. in some places — this comes back to how those variables were created earlier (new vs. not). Again, you can ignore that here and simply use

. everywhere.

layout.addWidget(lineEdit);

layout.addWidget(button);

Finally, remove all line-ending semi-colon ; marks.

layout.addWidget(lineEdit)

layout.addWidget(button)

ë

You technically don’t have to do this, as ; is a valid line-

terminator in Python. It’s just not necessary.

The following code is now working Python.

import sys

from PyQt6.QtWidgets import (

QApplication,

QHBoxLayout,

QLineEdit,

QPushButton,

QWidget,

)

app = QApplication(sys.argv)

window = QWidget()

lineEdit = QLineEdit()

button = QPushButton("Clear")

layout = QHBoxLayout()

layout.addWidget(lineEdit)

layout.addWidget(button)

button.pressed.connect(lineEdit.clear)

window.setLayout(layout)

window.setWindowTitle("Why?")

window.show()

app. exec ()

In Python code it is normal (though not required ) to subclass the window class so the initialization code can be self-contained within the init block. The code below has been reworked into that structure, moving all except the creation of the window object (now MyWindow) and app, and app.exec() call into the init block.

import sys

from PyQt6.QtWidgets import (

QApplication,

QHBoxLayout,

QLineEdit,

QPushButton,

QWidget,

)

class MyWindow (QWidget):

def __init__ (self, *args, **kwargs):

super().__init__(*args, **kwargs)

lineEdit = QLineEdit()

button = QPushButton("Clear")

layout = QHBoxLayout()

layout.addWidget(lineEdit)

layout.addWidget(button)

button.pressed.connect(lineEdit.clear)

self.setLayout(layout)

self.setWindowTitle("Why?")

self.show()

app = QApplication(sys.argv)

window = MyWindow()

app. exec ()

Applying the process to your own code

This is a very simple example, however if you follow the same process you can reliably convert any C++ Qt code over to it’s Python equivalent. When converting your own sample of code try and stick to this stepwise approach to minimize the risk of missing something or inadvertently breaking it. If you end up with Python code that runs but is subtly different it can be hard to debug.

Z

If you have a code example you would like help with translating,

you can always get in touch and I’ll try and help you out.

 

'PyQt5_' 카테고리의 다른 글

PyQt6 and PySide6 —What’s the difference?  (0) 2023.03.16
Installing PyQt6  (0) 2023.03.16
Moonsweeper  (0) 2023.03.16
Mozzarella Ashbadger  (0) 2023.03.16
Using Custom Widgets in QtDesigner  (0) 2023.03.16

댓글