I wanted to spawn a QWebKit instance in PySide and quickly got a segmentation fault - I forgot to set up an instance of QApplication. Since SIGSEGV is not a good failure mode, is there a way to catch that and throw an exception instead?
The QCoreApplication class (which QApplication inherits), has the static method instance() which allows you to check this:
if QApplication.instance() is not None:
# do stuff..
else:
raise RuntimeError('no application object')
Related
I am using pyvisa to communicate via USB with an instrument. I am able to control it properly. Since it is a high voltage source, and it is dangerous to forget it with high voltage turned on, I wanted to implement the __del__ method in order to turn off the output when the code execution finishes. So basically I wrote this:
import pyvisa as visa
class Instrument:
def __init__(self, resource_str='USB0::1510::9328::04481179::0::INSTR'):
self._resource_str = resource_str
self._resource = visa.ResourceManager().open_resource(resource_str)
def set_voltage(self, volts: float):
self._resource.write(f':SOURCE:VOLT:LEV {volts}')
def __del__(self):
self.set_voltage(0)
instrument = Instrument()
instrument.set_voltage(555)
The problem is that it is not working and in the terminal I get
$ python3 comunication\ test.py
Exception ignored in: <function Instrument.__del__ at 0x7f4cca419820>
Traceback (most recent call last):
File "comunication test.py", line 12, in __del__
File "comunication test.py", line 9, in set_voltage
File "/home/superman/.local/lib/python3.8/site-packages/pyvisa/resources/messagebased.py", line 197, in write
File "/home/superman/.local/lib/python3.8/site-packages/pyvisa/resources/messagebased.py", line 157, in write_raw
File "/home/superman/.local/lib/python3.8/site-packages/pyvisa/resources/resource.py", line 190, in session
pyvisa.errors.InvalidSession: Invalid session handle. The resource might be closed.
I guess that what is happening is that pyvisa is being "deleted" before the __del__ method of my object is being called. How can I prevent this? How can I tell Python that pyvisa is "important" for objects of the Instrument class so it is not unloaded until all of them have been destroyed?
In general, you cannot assume that __del__ will be called. If you're coming from an RAII (resource allocation is initialization) language such as C++, Python makes no similar guarantee of destructors.
To ensure some action is reversed, you should consider an alternative such as context managers:
from contextlib import contextmanager
#contextmanager
def instrument(resource_str='USB0::1510::9328::04481179::0::INSTR'):
...
try:
... # yield something
finally:
# set voltage of resource to 0 here
You would use it like
with instrument(<something>) as inst:
...
# guaranteed by here to set to 0.
I believe Ami Tavory's answer is generally considered to be the recommended solution, though context managers aren't always suitable depending on how the application is structured.
The other option would be to explicitly call the cleanup functions when the application is exiting. You can make it safer by wrapping the whole application in a try/finally, with the finally clause doing the cleanup. Note that if you don't include a catch then the exception will be automatically re-raised after executing the finally, which may be what you want. Example:
app = Application()
try:
app.run()
finally:
app.cleanup()
Be aware, though, that you potentially just threw an exception. If the exception happened, for example, mid-communication then you may not be able to send the command to reset the output as the device could be expecting you to finish what you had already started.
Finally I found my answer here using the package atexit. This does exactly what I wanted to do (based on my tests up to now):
import pyvisa as visa
import atexit
class Instrument:
def __init__(self, resource_str):
self._resource = visa.ResourceManager().open_resource(resource_str)
# Configure a safe shut down for when the class instance is destroyed:
def _atexit():
self.set_voltage(0)
atexit.register(_atexit) # https://stackoverflow.com/a/41627098
def set_voltage(self, volts: float):
self._resource.write(f':SOURCE:VOLT:LEV {volts}')
instrument = Instrument(resource_str = 'USB0::1510::9328::04481179::0::INSTR')
instrument.set_voltage(555)
The advantage of this solution is that it is user-independent, it does not matter how the user instantiates the Instrument class, in the end the high voltage will be turned off.
I faced the same kind of safety issue with another type of connected device. I could not predict safely the behavior of the __del__ method as discussed in questions like
I don't understand this python __del__ behaviour.
I ended with a context manager instead. It would look like this in your case:
def __enter__(self):
"""
Nothing to do.
"""
return self
def __exit__(self, type, value, traceback):
"""
Set back to zero voltage.
"""
self.set_voltage(0)
with Instrument() as instrument:
instrument.set_voltage(555)
Have a look at the following MWE.
import sys
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.button = QPushButton('Bham!')
self.setCentralWidget(self.button)
self.button.clicked.connect(self.btnClicked)
def btnClicked(self):
print(sys.excepthook)
raise Exception
#import traceback
#sys.excepthook = traceback.print_exception
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
app.exec_()
I have a number of questions. I don't know if they are all related (I guess so), so forgive me if they are not.
When I run the above code from the terminal, all is fine. The program runs, if I click the button it prints the traceback and dies. If I run it inside an IDE (I tested Spyder and PyCharm), the traceback is not displayed. Any idea why? Essentially the same question was raised in other posts also on SO, here and here. Please don't mark this as a duplicate of either of those; please read on.
By adding the commented lines, the traceback is again displayed properly. However, they also have the nasty side effect that the app does no longer terminate on unhandled exceptions! I have no idea why this happens, as AFAIK excepthook only prints the traceback, it cannot prevent the program from exiting. At the moment it is called, it's too late for rescue.
Also, I don't understand how Qt comes into play here, as exceptions that are not thrown inside a slot still crash the app as I would expect. No matter if I change excepthook or not, PyQt does not seem to override it as well (at least the print seems to suggest so).
FYI, I am using Python 3.5 with PyQt 5.6, and I am aware of the changes in the exception handling introduced in PyQt 5.5. If those are indeed the cause for the behaviour above, I would be glad hear some more detailed explanations.
When an exception happens inside a Qt slot, it's C++ which called into your Python code. Since Qt/C++ know nothing about Python exceptions, you only have two possibilities:
Print the exception and return some default value to C++ (like 0, "" or NULL), possibly with unintended side effects. This is what PyQt < 5.5 does.
Print the exception and then call qFatal() or abort(), causing the application to immediately exit inside C++. That's what PyQt >= 5.5 does, except when you have a custom excepthook set.
The reason Python still doesn't terminate is probably because it can't, as it's inside some C++ code. The reason your IDE isn't showing the stack is probably because it doesn't deal with the abort() correctly - I'd suggest opening a bug against the IDE for that.
Whilst #the-compiler's answer is correct in explaining why it happens, I thought I might provide a workaround if you'd like these exceptions to be raised in a more pythony way.
I decorate any slots with this decorator, which catches any exceptions in the slot and saves them to a a global variable:
exc_info = None
def pycrash(func):
"""Decorator that quits the qt mainloop and stores sys.exc_info. We will then
raise it outside the qt mainloop, this is a cleaner crash than Qt just aborting as
it does if Python raises an exception during a callback."""
def f(*args, **kwargs):
global exc_info
try:
return func(*args, **kwargs)
except:
if exc_info is None # newer exceptions don't replace the first one
exc_info = sys.exc_info()
qapplication.exit()
return f
Then just after my QApplication's exec(), I check the global variable and raise if there's anything there:
qapplication.exec_()
if exc_info is not None:
type, value, traceback = exc_info
raise value.with_traceback(traceback)
This is not ideal because quitting the mainloop doesn't stop other slots higher in the stack from still completing, and if the failed slot affects them, they might see some unexpected state. But IMHO it's still much better than PyQt just aborting with no cleanup.
I'm using custom exceptions to differ my exceptions from Python's default exceptions.
Is there a way to define a custom exit code when I raise the exception?
class MyException(Exception):
pass
def do_something_bad():
raise MyException('This is a custom exception')
if __name__ == '__main__':
try:
do_something_bad()
except:
print('Oops') # Do some exception handling
raise
In this code, the main function runs a few functions in a try code.
After I catch an exception I want to re-raise it to preserve the traceback stack.
The problem is that 'raise' always exits 1.
I want to exit the script with a custom exit code (for my custom exception), and exit 1 in any other case.
I've looked at this solution but it's not what I'm looking for:
Setting exit code in Python when an exception is raised
This solution forces me to check in every script I use whether the exception is a default or a custom one.
I want my custom exception to be able to tell the raise function what exit code to use.
You can override sys.excepthook to do what you want yourself:
import sys
class ExitCodeException(Exception):
"base class for all exceptions which shall set the exit code"
def getExitCode(self):
"meant to be overridden in subclass"
return 3
def handleUncaughtException(exctype, value, trace):
oldHook(exctype, value, trace)
if isinstance(value, ExitCodeException):
sys.exit(value.getExitCode())
sys.excepthook, oldHook = handleUncaughtException, sys.excepthook
This way you can put this code in a special module which all your code just needs to import.
I'm using Python with PyQt, for my interface, and Yapsi, for add plugins. Yapsy found all my plugins and add all to a menu in my mainWindow. Each plugin, is activated with triggered signal. This signal for a QAction don't have params and I need know what plugin was emit the signal.
This is the relevant code:
pluginMenu = self.menuBar().addMenu("P&lugins")
# Create plugin manager
self.manager = PluginManager(categories_filter={ "Formatters": Formatter})
self.manager.setPluginPlaces(["plugins"])
# Load plugins
self.manager.locatePlugins()
self.manager.loadPlugins()
# A do-nothing formatter by default
self.formatters = {}
for plugin in self.manager.getPluginsOfCategory("Formatters"):
# plugin.plugin_object is an instance of the plugin
print(plugin.plugin_object.name)
# The method to create action associated each input to default triggered() signal
newAction = self.createAction(plugin.plugin_object.name, slot=self.updatePreview())
self.addActions(pluginMenu, (newAction, None))
self.formatters[plugin.plugin_object.name] = (plugin.plugin_object, newAction)
def updatePreview(self):
# Here I need know what plugin emit the signal
#===================================================================
I thought to conect the signal with other signal with some params but I don't know how to do it.
I don't know what's Yapsi, but there is QObject.sender method:
QObject QObject.sender (self)
Returns a pointer to the object that sent the signal, if called in a
slot activated by a signal; otherwise it returns 0. The pointer is
valid only during the execution of the slot that calls this function
from this object's thread context.
The pointer returned by this function becomes invalid if the sender is
destroyed, or if the slot is disconnected from the sender's signal.
Warning: This function violates the object-oriented principle of
modularity. However, getting access to the sender might be useful when
many signals are connected to a single slot.
Warning: As mentioned above, the return value of this function is not
valid when the slot is called via a Qt.DirectConnection from a thread
different from this object's thread. Do not use this function in this
type of scenario.
Some more tips here: http://blog.odnous.net/2011/06/frequently-overlooked-and-practical.html
The correct way to do this is with a QSignalMapper.
Example code:
signalmap = QSignalMapper(self)
signalmap.mapped[QString].connect(self.handler)
...
signalmap.setMapping(action, name)
action.triggered[()].connect(signalmap.map)
This will re-emit the triggered signal with a string "name" parameter. It's also possible to re-emit signals with an int, QWidget or QObject parameter.
What's the best way to log all of the exceptions in a pyqt4 application using the standard python logging api?
I've tried wrapping exec_() in a try, except block, and logging the exceptions from that, but it only logs exceptions from the initialization of the app.
As a temporary solution, I wrapped the most important methods in try, except blocks, but that can't be the only way to do it.
You need to override sys.excepthook
def my_excepthook(type, value, tback):
# log the exception here
# then call the default handler
sys.__excepthook__(type, value, tback)
sys.excepthook = my_excepthook