How do I shut down PyQt's QtApplication correctly? - python

I don't know the first thing about Qt, but I'm trying to be cheeky and borrow code from elsewhere (http://lateral.netmanagers.com.ar/weblog/posts/BB901.html#disqus_thread). ;)
I have a problem. When I run test() the first time, everything works swimmingly. However, when I run it the second time, I get nasty segfaults. I suspect that the problem is that I'm not ending the qt stuff correctly. What should I change about this program to make it work multiple times? Thanks in advance!
from PyQt4 import QtCore, QtGui, QtWebKit
import logging
logging.basicConfig(level=logging.DEBUG)
class Capturer(object):
"""A class to capture webpages as images"""
def __init__(self, url, filename, app):
self.url = url
self.app = app
self.filename = filename
self.saw_initial_layout = False
self.saw_document_complete = False
def loadFinishedSlot(self):
self.saw_document_complete = True
if self.saw_initial_layout and self.saw_document_complete:
self.doCapture()
def initialLayoutSlot(self):
self.saw_initial_layout = True
if self.saw_initial_layout and self.saw_document_complete:
self.doCapture()
def capture(self):
"""Captures url as an image to the file specified"""
self.wb = QtWebKit.QWebPage()
self.wb.mainFrame().setScrollBarPolicy(
QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff)
self.wb.mainFrame().setScrollBarPolicy(
QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff)
self.wb.loadFinished.connect(self.loadFinishedSlot)
self.wb.mainFrame().initialLayoutCompleted.connect(
self.initialLayoutSlot)
logging.debug("Load %s", self.url)
self.wb.mainFrame().load(QtCore.QUrl(self.url))
def doCapture(self):
logging.debug("Beginning capture")
self.wb.setViewportSize(self.wb.mainFrame().contentsSize())
img = QtGui.QImage(self.wb.viewportSize(), QtGui.QImage.Format_ARGB32)
painter = QtGui.QPainter(img)
self.wb.mainFrame().render(painter)
painter.end()
img.save(self.filename)
self.app.quit()
def test():
"""Run a simple capture"""
app = QtGui.QApplication([])
c = Capturer("http://www.google.com", "google.png", app)
c.capture()
logging.debug("About to run exec_")
app.exec_()
DEBUG:root:Load http://www.google.com
QObject::connect: Cannot connect (null)::configurationAdded(QNetworkConfiguration) to QNetworkConfigurationManager::configurationAdded(QNetworkConfiguration)
QObject::connect: Cannot connect (null)::configurationRemoved(QNetworkConfiguration) to QNetworkConfigurationManager::configurationRemoved(QNetworkConfiguration)
QObject::connect: Cannot connect (null)::configurationUpdateComplete() to QNetworkConfigurationManager::updateCompleted()
QObject::connect: Cannot connect (null)::onlineStateChanged(bool) to QNetworkConfigurationManager::onlineStateChanged(bool)
QObject::connect: Cannot connect (null)::configurationChanged(QNetworkConfiguration) to QNetworkConfigurationManager::configurationChanged(QNetworkConfiguration)
Process Python segmentation fault (this last line is comes from emacs)

You need to handle the QApplication outside of the test functions, sort of like a singleton (it's actually appropriate here).
What you can do is to check if QtCore.qApp is something (or if QApplication.instance() returns None or something else) and only then create your qApp, otherwise, use the global one.
It will not be destroyed after your test() function since PyQt stores the app somewhere.
If you want to be sure it's handled correctly, just setup a lazily initialized singleton for it.

A QApplication should only be initialized once!
It can be used by as many Capture instances as you like, but you should start them in the mainloop.
See: https://doc.qt.io/qt-4.8/qapplication.html
You could also try "del app" after "app.exec_", but I am unsure about the results.
(Your original code runs fine on my system)
I would use urllib instead of webkit:
import urllib
class Capturer:
def capture(self, s_url, s_filename):
s_file_out, httpmessage = urllib.urlretrieve(s_url, s_filename, self.report)
def report(self, i_count, i_chunk, i_size):
print('retrived %5d of %5d bytes' % (i_count * i_chunk, i_size))
def test():
c = Capturer()
c.capture("http://www.google.com/google.png", "google1.png")
c.capture("http://www.google.com/google.png", "google2.png")
if __name__ == '__main__':
test()

Related

PyQt5 - stuck in a loop while trying to close ui using macOS

I have created a UI with PyQt5. I can use it on Windows and it works perfectly, but when I try to use it on MacOS I get stuck trying to close it (with self.close()). Using the PyCharm debugger I found out that after self.close() it jumps to app.exec_() and the function that was entered to close it is executed again (for example on_later_button_clicked(self)). I have also already tried sys.exit(app.exec_()).
Here is my code:
import os
import sys
from PyQt5 import QtGui, QtWidgets
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QDialog
from PyQt5.uic import loadUi
from Modules.database import addNeverID
from Modules.supportedWebsites import getWebsites
def Start():
m = askForPartnerUrl()
# m.setFixedSize(500,500)
m.show()
return m
class askForPartnerUrl(QDialog):
def __init__(self):
super(askForPartnerUrl, self).__init__()
loadUi('lib/askForPartnerURL.ui', self)
self.setWindowTitle('Upload')
current_id = getFromFile("id.txt")
self.show_id.setText(current_id)
self.show_origin_url.setText(
'' + getFromFile("origin_url.txt") + '')
self.show_origin_url.setOpenExternalLinks(True)
id_beginns = ["1"]
website_eq = ["1"]
website_guess_str = "Nicht verfügbar!"
for i in range(len(id_beginns)):
if id_beginns[i] in current_id:
website_guess_str = '' + website_eq[i] + ''
self.website_guess.setOpenExternalLinks(True)
break
self.website_guess.setText(website_guess_str)
self.save_button.clicked.connect(self.on_save_button_clicked)
self.later_button.clicked.connect(self.on_later_button_clicked)
self.never_button.clicked.connect(self.on_never_button_clicked)
try:
os.remove('temp/currentObject/partner_url.txt')
except:
pass
#pyqtSlot()
def on_never_button_clicked(self):
addNeverID(getFromFile("id.txt"))
saveToFile("Never-ID", "partner_url.txt")
self.close()
def on_later_button_clicked(self):
saveToFile("Later-ID", "partner_url.txt")
self.close()
def on_save_button_clicked(self):
url_is_valid = False
for i in getWebsites():
if i in self.partner_url_input.text():
url_is_valid = True
break
if url_is_valid:
saveToFile(self.partner_url_input.text(), "partner_url.txt")
self.close()
else:
error_dialog = QtWidgets.QErrorMessage(self)
error_dialog.setWindowTitle("Eingabe nicht verwertbar")
error_dialog.showMessage('Die eingegebene URL ist nicht verwendbar! Bitte prüfe deine Eingabe.')
def showGUI():
app = QApplication(sys.argv)
app.setStyle('Fusion')
app.setWindowIcon(QtGui.QIcon('lib/icon.png'))
window = Start()
app.exec_()
def saveToFile(content, filename):
file = open("temp/currentObject/" + filename, "w+")
file.write(content)
file.close()
def getFromFile(filename):
file = open("temp/currentObject/" + filename)
content = file.read()
file.close()
return content
Many thanks in advance
The reason is that since you're using uic, it automatically enables the auto-connection feature, which automatically detects function names based on object/signals names and connects them, even if the functions do not have Qt slots decorators.
The result is that your slot will be actually called thrice:
without any argument (clicked());
with the checked argument (clicked(bool)): the argument is ignored by Qt since the function doesn't take any, but the function will be called anyway because no slot signature has been specified for it;
again with the checked argument, because you manually connected it in your code;
If you want to keep using the auto connection, use a unique slot decorator for that specific function, otherwise manually connect to a function (possibly with a slot, if you need a specific signature) that does not use the auto connection naming, but don't use both.
class askForPartnerUrl(QDialog):
def __init__(self):
super(askForPartnerUrl, self).__init__()
loadUi('askForPartnerURL.ui', self)
# ...
# remove the following lines:
# self.save_button.clicked.connect(self.on_save_button_clicked)
# self.later_button.clicked.connect(self.on_later_button_clicked)
# self.never_button.clicked.connect(self.on_never_button_clicked)
# manual connection
self.later_button.clicked.connect(self.saveLater)
# using the auto connection; the function doesn't need arguments, so
# you can ignore the argument type signature
#pyqtSlot()
def on_never_button_clicked(self):
addNeverID(getFromFile("id.txt"))
# ...
# with a normal function; in this case no slot decorator is required since
# you don't have arguments
def saveLater(self):
url_is_valid = False
# ...
PS: The reason for which it gets "stuck" is probably due to the way Python deals with the end of the program (which by default happens as soon as the last window is closed in Qt) on MacOS: after the first call to close() PyQt tries to quit the QApplication (free up memory, etc...), but while doing so the original click event is still in the process of firing the signals to the remaining second and third slot, hence the "loop" (but it's not an actual loop, and the third slot never gets called because it's the second one that blocks everything).
Note that this is a big oversimplification, I'm not an expert in memory usage and low level programming, but this is fundamentally what's happening.

PyQt Code Splitting - Design Vs Functionality

Am struggling to comprehend how to split code in (Py)Qt. The aim is to have the design & navigation tabs in QMainWindow, each tab triggering code in other files. So far it only launches with the ActionClass in the same document / putting in an external document causes 'app not defined' when clicking the tab. The below works without errors, but is clunky.
class Main(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.u = Ui_MainWindow()
self.u.setupUi(self)
self.u.tabs.currentChanged.connect(self.TabsChanged)
def TabsChanged(self, i):
if i == self.u.tabs.indexOf(self.u.tabFirst): ActionClass.__init__
class ActionClass(Main):
def __init__(self):
app.u.lineEdit.setText("test")
app = Main()
app.show()
sys.exit(app.exec_())
The examples I keep seeing have all code in one document. Is there another way to do this e.g. where the ActionClass is in another file/writing u.lineEdit.setText instead of app.u.lineEdit.setText. It seems inheritance & an instance of Main can't be accessed from the ActionClasses doc, so I can't see how they would communicate back to the Main?
Much appreciated
As suggest #M4rtini you can separate your code into python modules. And then import them (use them) in your main module.
For instance the code you posted can be separated in to files:
# actions_class.py
class ActionClass(Main):
def __init__(self):
app.u.lineEdit.setText("test")
and
# main.py
from action_class import ActionClass # This line no need much explanation ;)
class Main(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.u = Ui_MainWindow()
self.u.setupUi(self)
self.u.tabs.currentChanged.connect(self.TabsChanged)
def TabsChanged(self, i):
if i == self.u.tabs.indexOf(self.u.tabFirst): ActionClass.__init__
app = Main()
app.show()
sys.exit(app.exec_())
In order to understand how import works see the link I left you above.
More explanation
Lest's see:
The correct way of executin code inside a __init__ method is creating an instance. See the example below.
class A:
def __init__(self):
print("Executing A.__init__")
print("Doing things wrong")
A.__init__ # This don't print enything
print("Doing things well")
A() # This works as expected.
So, you line reads:
if i == self.u.tabs.indexOf(self.u.tabFirst): ActionClass.__init__
and should reads:
if i == self.u.tabs.indexOf(self.u.tabFirst): ActionClass()
On the other hand, is a bad practice put code that's not for initialize the instance inside the __init__ methods.
If you don't need the instance but yet you want to store the functions inside a class (something like a c++ namespace) you creating
use #staticmethod decorator.
class A:
#staticmethod
def foo():
print("Oh, wow, a static method in Python!")
A.foo()
So, your ActionClass could be rewritten as:
class ActionClass(Main):
#staticmethod
def do_action:
app.u.lineEdit.setText("test")
ans then you can use it like this:
if i == self.u.tabs.indexOf(self.u.tabFirst): ActionClass.do_action()

PySide crashing Python when emitting None between threads

[edit] This is not a pure duplicate of the PySide emit signal causes python to crash question. This question relates specifically to a (now) known bug in PySide preventing None from being passed across threads. The other question relates to hooking up signals to a spinner box. I've updated the title of this question to better reflect the problem I was facing. [/edit]
I've banged my head against a situation where PySide behaves subtly different from PyQt. Well, I say subtly but actually PySide crashes Python whereas PyQt works as I expect.
I'm completely new to PySide and still fairly new to PyQt so maybe I'm making some basic mistake, but damned if I can figure it out... really hoping one of you fine folks can give some pointers!
The full app is a batch processing tool and much too cumbersome to describe here, but I've stripped the problem down to its bare essentials in the code-sample below:
import threading
try:
# raise ImportError() # Uncomment this line to show PyQt works correctly
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
QtCore.Signal = QtCore.pyqtSignal
QtCore.Slot = QtCore.pyqtSlot
class _ThreadsafeCallbackHelper(QtCore.QObject):
finished = QtCore.Signal(object)
def Dummy():
print "Ran Dummy"
# return '' # Uncomment this to show PySide *not* crashing
return None
class BatchProcessingWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self, None)
btn = QtGui.QPushButton('do it', self)
btn.clicked.connect(lambda: self._BatchProcess())
def _BatchProcess(self):
def postbatch():
pass
helper = _ThreadsafeCallbackHelper()
helper.finished.connect(postbatch)
def cb():
res = Dummy()
helper.finished.emit(res) # `None` crashes Python under PySide??!
t = threading.Thread(target=cb)
t.start()
if __name__ == '__main__': # pragma: no cover
app = QtGui.QApplication([])
BatchProcessingWindow().show()
app.exec_()
Running this displays a window with a "do it" button. Clicking it crashes Python if running under PySide. Uncomment the ImportError on line 4 to see PyQt* correctly run the Dummy function. Or uncomment the return statement on line 20 to see PySide correctly run.
I don't understand why emitting None makes Python/PySide fail so badly?
The goal is to offload the processing (whatever Dummy does) to another thread, keeping the main GUI thread responsive. Again this has worked fine with PyQt but clearly not so much with PySide.
Any and all advice will be super appreciated.
This is under:
Python 2.7 (r27:82525, Jul 4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win32
>>> import PySide
>>> PySide.__version_info__
(1, 1, 0, 'final', 1)
>>> from PyQt4 import Qt
>>> Qt.qVersion()
'4.8.2'
So, if the argument is that PySide is neglected and this really is a bug, we might as well come up with a workaround, right?
By introducing a sentinel to replace None, and emitting it the problem can be circumvented, then the sentinel just has to be swapped back to None in the callbacks and the problem is bypassed.
Good grief though. I'll post the code I've ended up with to invite further comments, but if you got better alternatives or actual solutions then do give a shout. In the meantime I guess this'll do:
_PYSIDE_NONE_SENTINEL = object()
def pyside_none_wrap(var):
"""None -> sentinel. Wrap this around out-of-thread emitting."""
if var is None:
return _PYSIDE_NONE_SENTINEL
return var
def pyside_none_deco(func):
"""sentinel -> None. Decorate callbacks that react to out-of-thread
signal emitting.
Modifies the function such that any sentinels passed in
are transformed into None.
"""
def sentinel_guard(arg):
if arg is _PYSIDE_NONE_SENTINEL:
return None
return arg
def inner(*args, **kwargs):
newargs = map(sentinel_guard, args)
newkwargs = {k: sentinel_guard(v) for k, v in kwargs.iteritems()}
return func(*newargs, **newkwargs)
return inner
Modifying my original code we arrive at this solution:
class _ThreadsafeCallbackHelper(QtCore.QObject):
finished = QtCore.Signal(object)
def Dummy():
print "Ran Dummy"
return None
def _BatchProcess():
#pyside_none_deco
def postbatch(result):
print "Post batch result: %s" % result
helper = _ThreadsafeCallbackHelper()
helper.finished.connect(postbatch)
def cb():
res = Dummy()
helper.finished.emit(pyside_none_wrap(res))
t = threading.Thread(target=cb)
t.start()
class BatchProcessingWindow(QtGui.QDialog):
def __init__(self):
super(BatchProcessingWindow, self).__init__(None)
btn = QtGui.QPushButton('do it', self)
btn.clicked.connect(_BatchProcess)
if __name__ == '__main__': # pragma: no cover
app = QtGui.QApplication([])
window = BatchProcessingWindow()
window.show()
sys.exit(app.exec_())
I doubt that'll win any awards, but it does seem to fix the issue.

Auto Refresh in ArcGIS using Python

I am trying to create an "auto refresh" tool for ArcMap, to refresh the DataFrame. I believe version 10 had an add-on you could download for this purpose.. however we are running 10.1 at work and there is no such tool.
EDIT wxPython's timer should work, however using wx in arc is tricky. Here's what the code looks like currently:
import arcpy
import pythonaddins
import os
import sys
sMyPath = os.path.dirname(__file__)
sys.path.insert(0, sMyPath)
WATCHER = None
class WxExtensionClass(object):
"""Implementation for Refresher_addin.extension (Extension)"""
_wxApp = None
def __init__(self):
# For performance considerations, please remove all unused methods in this class.
self.enabled = True
def startup(self):
from wx import PySimpleApp
self._wxApp = PySimpleApp()
self._wxApp.MainLoop()
global WATCHER
WATCHER = watcherDialog()
class RefreshButton(object):
"""Implementation for Refresher_addin.button (Button)"""
def __init__(self):
self.enabled = True
self.checked = False
def onClick(self):
if not WATCHER.timer.IsRunning():
WATCHER.timer.Start(5000)
else:
WATCHER.timer.Stop()
class watcherDialog(wx.Frame):
'''Frame subclass, just used as a timer event.'''
def __init__(self):
wx.Frame.__init__(self, None, -1, "timer_event")
#set up timer
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onTimer, self.timer)
def onTimer(self, event):
localtime = time.asctime( time.localtime(time.time()) )
print "Refresh at :", localtime
arcpy.RefreshActiveView()
app = wx.App(False)
You will notice the PySimpleApp stuff in there. I got that from the Cederholm's presentation. I am wondering if I am misunderstanding something though. Should I create an entirely separate addin for the extension? THEN, create my toolbar/bar addin with the code I need? I ask this because I don't see the PySimpleApp referenced in your code below, or any importing from wx in the startup override method either... which I thought was required/the point of all this. I do appreciate your help. Please let me know what you see in my code.
You can't do this the way you are trying, because time.sleep will block and lock up the entire application. Python addins in ArcGIS is pretty new stuff, and there's a lot of functionality that hasn't been implemented yet. One of these is some kind of update or timer event like you get in .NET and ArcObjects. You might think of using threading.Thread and threading.Event in a case like this, but nothing to do with threads will work in the Python addin environment. At least I can't get it to work. So what I've done in situations like this is use wxPython and the Timer class. The code below will work if the addin is set up correctly.
import time
import os, sys
import wx
import arcpy
mp = os.path.dirname(__file__)
sys.path.append(mp)
WATCHER = None
class LibLoader1(object):
"""Extension Implementation"""
def __init__(self):
self.enabled = True
def startup(self):
global WATCHER
WATCHER = watcherDialog()
class ButtonClass5(object):
"""Button Implementation"""
def __init__(self):
self.enabled = True
self.checked = False
def onClick(self):
if not WATCHER.timer.IsRunning():
WATCHER.timer.Start(5000)
else:
WATCHER.timer.Stop()
class watcherDialog(wx.Frame):
'''Frame subclass, just used as a timer event.'''
def __init__(self):
wx.Frame.__init__(self, None, -1, "timer_event")
#set up timer
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onTimer, self.timer)
def onTimer(self, event):
localtime = time.asctime( time.localtime(time.time()) )
print "Refresh at :", localtime
arcpy.RefreshActiveView()
app = wx.App(False)
Make an extension addin, with a toolbar and a button class. Override the startup method of the extension as shown above. That will create an instance of a Frame subclass with a timer. Then, whenever you click the button on the toolbar, the timer will toggle on or off. The Timer argument is in milliseconds, so the code as shown will refresh every 5 seconds.
You can read more about using wxPython in addins here. Pay particular attention to MCederholm's posts, like about the print statement not working.
EDIT
The code uses a startup method override of the addin extension class. This method is supposed to run when Arcmap starts, but it seems from your comments that this startup method is failing to run on startup. That's possible if you don't create your addin just right, but it works fine for me in my tests. If you continue to get "AttributeError: 'NoneType' object has no attribute 'timer'", then change the onClick method of your button class like so:
def onClick(self):
if WATCHER is None:
global WATCHER
WATCHER = watcherDialog()
if not WATCHER.timer.IsRunning():
WATCHER.timer.Start(5000)
else:
WATCHER.timer.Stop()
The first 3 lines check to make sure that the WATCHER variable has been set to an instance of watcherDialog and is not still set to None. Don't know why your startup method is not running, but hopefully this will fix things for you.
You can use either the RefreshTOC or RefreshActiveView Method. Just add a timer

PyQt returning empty string from a class?

The code is along the lines of this:
class Solver(QDialog):
def __init__(self, parent=None):
blabla
def solver(self):
return qlineedit1.text()
class access(QMainWindow):
prda = Solver().solver()
print prda
The problem is that prda is an empty string. If i put "print qlineedit1.text()" in the Solver class, the text is displayed as it should be. However, when "transferred" to a different class, the string is empty. The weirdest part that it is there - if I do type(prda), I get QString type output.
So, how would I get prda to assume the value of qlineedit1.text() ? I was thinking of writing the text to file in Solver class, and then reading it from access class, but there has to be another solution. By the way, the Solver class and the access class are two dialogs.
Help?
Since next code works as expected, all i can think out without seeing more of your code is that your qlineedit1 is empty.
from PyQt4 import QtGui
class Solver(QtGui.QDialog):
def __init__(self, parent=None):
print "in Solver init"
def solver(self):
return "in solver() method"
class access(QtGui.QMainWindow):
prda = Solver()
print prda.solver()
#this will work too:
#prda = Solver().solver()
#print prda
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
acc=access()
acc.show()
sys.exit(app.exec_())

Categories