pyqt5: QPushButton class ,clicked method and the connect [duplicate] - python

This question already has an answer here:
Declaration of the custom Signals
(1 answer)
Closed 1 year ago.
i am trying to learn pyqt5 take look at the following code
qbtn = QPushButton('Quit', self)
qbtn.clicked.connect(QApplication.instance().quit)
clicked signature in qtwidgets.py
def clicked(self, checked: bool = ...) -> None: ...
where is the implementation of the clicked method ?
how it is not callable i mean it's method right ?
print (qbtn.clicked())
output:
TypeError: native Qt signal is not callable
and finally if it doesn't have any implementation how it effects the QPushButton
?
print(qbtn)
print (qbtn.clicked)
print (qbtn.clicked.connect)
output:
<PyQt5.QtWidgets.QPushButton object at 0x7f963ca88160>
<bound PYQT_SIGNAL clicked of QPushButton object at 0x7f963ca88160>
<built-in method connect of PyQt5.QtCore.pyqtBoundSignal object at 0x7f963ca91cc0>

There are two important aspects that should always be kept in mind:
PyQt (similarly to PySide) is a binding: it is an interface to the Qt framework, and as such provides access to it using standard python functions and methods; in order to create it, a special tool is used, SIP, which actually creates the binding from objects exposed to python to those of Qt and viceversa (for PySide, the tool used is called Shiboken);
signals are not methods, they are interfaces to which callable objects can connect to, and those objects will be called whenever the signal is emitted, provided they have a compatible signature;
The file you're referring to is a pyi file. From What does ā€œiā€ represent in Python .pyi extension?:
The *.pyi files are used by PyCharm and other development tools to provide more information, such as PEP 484 type hints, than it is able to glean from introspection of extension types and methods. They are not intended to be imported, executed or used for any other purpose other than providing info to the tools. If you don't use use a tool that makes use of .pyi files then you can safely ignore this file.
You mentioned the following line:
def clicked(self, checked: bool = ...) -> None: ...
which is only found in those files, and is just that: an information.
Signals in C++ are declared in headers similarly to functions, having arguments that indicate the signal signature(s), and are then "transformed" into signals when Qt (or the program that declares its own signals) is compiled.[1]
Since PyQt and PySide are created using the aforementioned automated tools, the result is that signals might be listed as methods; notably, the official PySide docs list signals even including def: in PySide2 a specific "Signal" section is used, while in PySide6 they are not even identified as such.
In the python bindings, signals are unbound attributes for classes, but when a signal is referenced as a QObject instance, PyQt automatically binds the instance to the signal to create a bound signal.
>>> hasattr(QtWidgets.QPushButton.clicked, 'emit')
False
>>> hasattr(QtWidgets.QPushButton().clicked, 'emit')
True
You can see that the signal is dynamically bound by using a simple id (which should return an unique and constant value for an object):
>>> b = QtWidgets.QPushButton()
>>> id(b.clicked)
2971296616
>>> id(b.clicked)
2971299208
# or even:
>>> b.clicked == b.clicked
False
So, what you see from some documentation or reference file, is primarily the signature used to create the signal, but also the expected signature for the signal emission/receiver, similarly to what can be done in Python when declaring a new signal (with the difference that it's not possible to define default values, like the checked=False of QAbstractButton).
[1] this is a major oversimplification, I don't have enough knowledge of C++ to explain how exactly signal creation works, but it's just for the sake of explanation.

Related

How to reference a specific overloaded QCommandLineParser.process() in Python?

Qt5 provides two functions to process command line arguments in QCommandLineParser class. The signatures are:
process(const QStringList &arguments)
process(const QCoreApplication &app)
This works fine in C++ but Python has no overloading feature nor signature detection. Apparently,
from PyQt5.QtCore import QCommandLineParser as qlp
qlp.process(("myapp", "-opt", "file"))
(example oversimplified to make the point)
references the process(const QCoreApplication &app) variant and errors out because the argument in no QCoreApplication instance.
At this step, I don't want to instantiate some kind of application object because I don't know yet if I'll run a QCoreApplication or a QGuiApplication, which is determined by parsing the arguments.
How can I force the desired variant?
Alternately, how can I preparse the command line arguments to check if I need a GUI or not? (But this alternative may need as much work as parsing the arguments with QCommandLineParser)

PyQt5 dbus: Force type signature of signal argument to be array of string

I'm writing an MPRIS player, which communicates with clients over
dbus. I need to emit a signal when my playback state changes. However,
the signal requires a format of (sa{sv}as), and my code is producing
(sa{sv}av). Here's the important part:
self.signal = QDBusMessage.createSignal(
"/org/mpris/MediaPlayer2",
"org.freedesktop.DBus.Properties",
"PropertiesChanged"
)
self.signal.setArguments(
[interface, {property: values}, ['']]
)
The problem is the third item in the list given to setArguments. It is
an empty string in a list because I need to produce a type of 'array
of string' (as) but pyqt5 translates that into 'array of variant' (av).
I never need to put any actual data in that list, I just need the type
signature to be correct.
Is there any way to do this in PyQt5? Perhaps using QDBusArgument?
I... I got it working. Wow. That was a campaign.
I don't know what is going wrong exactly, I wasn't able to dig out of the PyQt5 source where exactly the conversion happens. However, I did look into QDbusArgument(). There is no documentation for this in python, and the C++ docs are worthless due to major differences, so I took to the source code. in sip/QtDbus/qdbusargument.sip, I discovered a completely undocumented new method called qdbusargument_add. This maps to QDbusArgument().add() in python. It is used to add arguments with an explicit type id to a QDbusArgument. And it has a special case for QStringList!
From then I just bruteforced every possibility I could think of with arguments to QDbusArgument(), and finally got the following:
def PropertiesChanged(self, interface, property, values):
"""Sends PropertiesChanged signal through sessionBus.
Args:
interface: interface name
property: property name
values: current property value(s)
"""
emptyStringListArg = QDBusArgument()
emptyStringListArg.add([""], QMetaType.QStringList)
self.signal.setArguments([interface, {property: values}, emptyStringListArg])
QDBusConnection.sessionBus().send(self.signal)
It'd be great if the add() function could be documented, but I can't seem to send messages to the PyQt5 mailing list. Do I have to register first?

PyGTK custom signals send a list as parameter

I'm trying to add a custom signal to a class -
class TaskBrowser(gobject.GObject):
__list_signal__ = (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (<List datatype>,))
__gsignals__ = {'tasks-deleted': __list_signal__}
...
def on_delete_tasks(self, widget=None, tid=None):
...
gobject.idle_add(self.emit, "tasks-deleted", deleted_tasks) #deleted_tasks is of type 'list'
...
...
In the __gsignals__ dict, when I state list as parameter type, I get the following error traceback -
File "/home/manhattan/GTG/Hamster_in_hands/GTG/gtk/browser/browser.py", line 61, in <module>
class TaskBrowser(gobject.GObject):
File "/usr/lib/python2.7/site-packages/gobject/__init__.py", line 60, in __init__
cls._type_register(cls.__dict__)
File "/usr/lib/python2.7/site-packages/gobject/__init__.py", line 115, in _type_register
type_register(cls, namespace.get('__gtype_name__'))
TypeError: Error when calling the metaclass bases
could not get typecode from object
I saw the list of possible parameter types, and there's no type for list
Is there a way I can pass a list as a signal parameter ?
The C library needs to know the C type of the parameters, for Gtk, Gdk, Gio and GLib objects the types in the wrappers will work as they mirror the C types in the Gtk and family libraries.
However, for any other type you need to pass either object or gobject.TYPE_PYOBJECT. What that means is that the on the C side a "python object" type is passed. Every object accessible from a python script is of that type, that pretty much means anything you can pass through your python script will fit an object parameter.
That also means, of course, that this feature doesn't work in python! Python relies on duck typing, that means we figure out if an object is of a type when we do stuff with it and it works. Passing the type of the parameter works for C as a way to make sure the objects passed are of the type the program needs them to be, but in python every object is of the same type in the C side so this feature becomes completely useless on the python side.
But that doesn't means it is completely useless overall. For example, in python int is an object. But not in C. If you are using property bindings, which were coded in the C side of the Gtk library, you will want to specify the appropriate type as bindings of different property types don't work.
Using C side wrapped signal handlers with object parameter types is also bound not to work, since the C side needs a specific type to function.
In pygtk3 this error has been occured for me because import gobject directly.
and fixed this error by from gi.repository import GObject.
you can see details in this link.

Global state in Python module

I am writing a Python wrapper for a C library using the cffi.
The C library has to be initialized and shut down. Also, the cffi needs some place to save the state returned from ffi.dlopen().
I can see two paths here:
Either I wrap this whole stateful business in a class like this
class wrapper(object):
def __init__(self):
self.c = ffi.dlopen("mylibrary")
self.c.initialize()
def __del__(self):
self.c.terminate()
Or I provide two global functions that hide the state in a global variable
def initialize():
global __library
__library = ffi.dlopen("mylibrary")
__library.initialize()
def terminate():
__library.terminate()
del __library
The first path is somewhat cumbersome in that it requires the user to always create an object that really serves no other purpose other than managing the library state. On the other hand, it makes sure that terminate() is actually called every time.
The second path seems to result in a somewhat easier API. However, it exposes some hidden global state, which might be a bad thing. Also, if the user forgets to call terminate(), the C library is not unloaded correctly (which is not a big problem on the C side).
Which one of these paths would be more pythonic?
Exposing a wrapper object only makes sense in python if the library actually supports something like multiple instances in one application. If it doesn't support that or it's not really relevant go for kindall's suggestion and just initialize the library when imported and add an atexit handler for cleanup.
Adding wrappers around a stateless api or even an api without support for keeping different sets of state is not really pythonic and would raise expectations that different instances have some kind of isolation.
Example code:
import atexit
# Normal library initialization
__library = ffi.dlopen("mylibrary")
__library.initialize()
# Private library cleanup function
def __terminate():
__library.terminate()
# register function to be called on clean interpreter termination
atexit.register(__terminate)
For more details about atexit this question has some more details, as has the python documentation of course.

How to refer to a base instance python gtk widget from a function in a module

I am writing a Python GTK application for studying some sort of math data. The main script has a single class with only three methods: __INIT__, main(self) for starting the loop and delete_event for killing it.
__INIT__ creates the GUI, which includes a TextBuffer and TextView widgets so that the analysis functions (defined on a separate functions.py module) can output their results to a common log/message area. A relevant extract follows:
include module functions(.py)
(...)
class TURING:
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
(...)
self.logscroll = gtk.ScrolledWindow()
self.logscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
self.logbuffer = gtk.TextBuffer()
self.logpage = gtk.TextView(self.logbuffer)
self.logpage.set_editable(gtk.FALSE)
self.logpage.set_cursor_visible(gtk.FALSE)
self.logpage.set_wrap_mode(gtk.WRAP_CHAR)
self.logscroll.add(self.logpage)
self.logscroll.show()
self.logpage.show()
(...)
enditer = self.logbuffer.get_end_iter()
self.logbuffer.insert(enditer, 'Welcome!')
(...)
def main(self):
gtk.main()
if __name__ == "__main__":
turing = TURING()
turing.main()
The intermediate two lines successfully print a welcome message onto the message area defined by self.logpage.
Now, one of the functions in method functions checks whether the database is up to date and if not asks the user to load a new batch of raw data.
One way of doing this is to include a menu item that triggers that function, like this:
item_dataCheck.connect("activate", functions.data_check, '')
functions.data_check runs fine however when it tries to write its output to self.logbuffer an error is thrown complaining that menu item item_dataCheck has no property logbuffer. The offending code is
enditer = self.logbuffer.get_end_iter()
self.logbuffer.insert(enditer, 'Please update the database.')
Obviously the name self is representing the widget that invoked the function, viz., item_dataCheck. My question is how can I from functions.data_check refer directly to logbuffer as a member of the turing instance of the TURING class. I tried to write
enditer = turing.logbuffer.get_end_iter()
turing.logbuffer.insert(enditer, 'Please update the database.')
but that's is not working. I have tried hard to find a solution but with no success.
I believe the matter is quite trivial and I know I still have some serious conceptual problems with Python and OOP, but any help will be heartly appreciated. (I started out card punching Fortran programs on a Burroughs mainframe, so I could count on some community mercy...)
You can provide additional arguments when connecting signals to functions or methods. So your handler functions.data_check can accept extra arguments apart from self:
def data_check(self, logbuffer):
# ...
Then, you can connect with arguments:
item_dataCheck.connect("activate", functions.data_check, logbuffer)
Also, the self parameter is normally used as the first parameter in method definitions. This is a very strong convention so you should use obj or something similar instead. Specially since your signal handlers may be methods too; in which case you could mess it up with its arguments.

Categories