Making closeEvent() work outside of a class in PySide - pyside

Seemingly the closeEvent method in the following code is called upon clicking x (in the top right corner of the window). I'm guessing the information that allows python to make that connection is inside the self argument.
Is there a way to implement this into a procedural or functional program.
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Message box')
self.show()
def closeEvent(self, event):
reply = QtGui.QMessageBox.question(self, 'Message',
"Are you sure to quit?", QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
event.accept()
else:
event.ignore()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Apparently, you can assign your own function to the QWidget.closeEvent function (property), given that you take the automatically passed in instance argument and event into account:
def myHandler(widget_inst, event):
print("Handling closeEvent")
mywidget = QWidget()
mywidget.closeEvent = myHandler
This is going to get tedious and is not the way things were intended to be done.

Related

Frame.__init__(self, window) vs. super().__init__()

Is there any differences between the following two classes?
from tkinter import *
class MyFrame1(Frame):
def __init__(self, window):
super().__init__(window)
class MyFrame2(Frame):
def __init__(self, window):
Frame.__init__(self, window)
I think it is a matter of choice. Am I right?

AttributeError: Can't pickle local object in Multiprocessing

I am very new to python and I encounter this error.
CODE 1 :
import multiprocessing as mp
import os
def calc(num1, num2):
global addi
def addi(num1, num2):
print(num1+num2)
m = mp.Process(target = addi, args = (num1, num2))
m.start()
print("here is main", os.getpid())
m.join()
if __name__ == "__main__":
# creating processes
calc(5, 6)
ERROR 1 : ForkingPickler(file, protocol).dump(obj)
AttributeError: Can't pickle local object 'calc.<locals>.addi'
After reading around a little I understand that pickle cannot be used for local methods and so I also tried the below solution which gave another error.
CODE 2 :
import multiprocessing as mp
import os
def calc(num1, num2):
**global addi**
def addi(num1, num2):
print(num1+num2)
m = mp.Process(target = addi, args = (num1, num2))
m.start()
print("here is main", os.getpid())
m.join()
if __name__ == "__main__":
# creating processes
calc(5, 6)
ERROR 2 :
self = reduction.pickle.load(from_parent)
AttributeError: Can't get attribute 'addi' on <module '__mp_main__' from '/Users
Could someone please help me out with this? I am clueless on what to do next!
The python version I am using is python3.8.9
Thank you so much!
Basically, the reason you are getting this error is because multiprocessing uses pickle, which can only serialize top-module level functions in general. Function addi is not a top-module level function. In fact, the line global addi is not doing anything because addi has never been declared in the outer module. So you have three ways to fix this.
Method 1
You can define addi in the global scope before executing calc function:
import multiprocessing as mp
import os
def addi(num1, num2):
print(num1 + num2)
def calc(num1, num2):
m = mp.Process(target=addi, args=(num1, num2))
m.start()
print("here is main", os.getpid())
m.join()
if __name__ == "__main__":
# creating processes
calc(5, 6)
Output
here is main 9924
11
Method 2
You can switch to multiprocess, which uses dill instead of pickle, and can serialize such functions.
import multiprocess as mp # Note that we are importing "multiprocess", no "ing"!
import os
def calc(num1, num2):
def addi(num1, num2):
print(num1 + num2)
m = mp.Process(target=addi, args=(num1, num2))
m.start()
print("here is main", os.getpid())
m.join()
if __name__ == "__main__":
# creating processes
calc(5, 6)
Output
here is main 67632
11
Method 2b
While it's a useful library, there are a few valid reasons why you may not want to use multiprocess. A big one is the fact that the standard library's multiprocessing and this fork are not compatible with each other (especially if you use anything from within the subpackage multiprocessing.managers). This means that if you are using this fork in your own project, but also use third-party libraries which themselves use the standard library's multiprocesing instead, you may see unexpected behaviour.
Anyway, in cases where you want to stick with the standard library's multiprocessing and not use the fork, you can use dill yourself to serialize python closures like the function addi by subclassing the Process class and adding some of our own logic. An example is given below:
import dill
from multiprocessing import Process # Use the standard library only
import os
class DillProcess(Process):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._target = dill.dumps(self._target) # Save the target function as bytes, using dill
def run(self):
if self._target:
self._target = dill.loads(self._target) # Unpickle the target function before executing
self._target(*self._args, **self._kwargs) # Execute the target function
def calc(num1, num2):
def addi(num1, num2):
print(num1 + num2)
m = DillProcess(target=addi, args=(num1, num2)) # Note how we use DillProcess, and not multiprocessing.Process
m.start()
print("here is main", os.getpid())
m.join()
if __name__ == "__main__":
# creating processes
calc(5, 6)
Output
here is main 23360
11
Method 3
This method is for those who cannot use any third-party libraries in their code. I will recommend making sure that the above methods did not work before resorting to this one because it's a little hacky and you do need to restructure some of your code.
Anyways, this method works by referencing your local functions in the top-module scope, so that they become accessible by pickle. To do this dynamically, we create a placeholder class and add all the local functions as its class attributes. We would also need to make sure that the functions' __qualname__ attribute is altered to point to their new location, and that this all is done every run outside the if __name__ ... block (otherwise newly started processes won't see the attributes). Consider a slightly modified version of your code here:
import multiprocessing as mp
import os
def calc(num1, num2):
def addi(num1, num2):
print(num1 + num2)
# Another local function you might have
def addi2():
print('hahahaha')
m = mp.Process(target=addi, args=(num1, num2))
m.start()
print("here is main", os.getpid())
m.join()
if __name__ == "__main__":
# creating processes
calc(5, 6)
Below is a how you can make it work by using the above detailed method:
import multiprocessing as mp
import os
# This is our placeholder class, all local functions will be added as it's attributes
class _LocalFunctions:
#classmethod
def add_functions(cls, *args):
for function in args:
setattr(cls, function.__name__, function)
function.__qualname__ = cls.__qualname__ + '.' + function.__name__
def calc(num1, num2, _init=False):
# The _init parameter is to initialize all local functions outside __main__ block without actually running the
# whole function. Basically, you shift all local function definitions to the top and add them to our
# _LocalFunctions class. Now, if the _init parameter is True, then this means that the function call was just to
# initialize the local functions and you SHOULD NOT do anything else. This means that after they are initialized,
# you simply return (check below)
def addi(num1, num2):
print(num1 + num2)
# Another local function you might have
def addi2():
print('hahahaha')
# Add all functions to _LocalFunctions class, separating each with a comma:
_LocalFunctions.add_functions(addi, addi2)
# IMPORTANT: return and don't actually execute the logic of the function if _init is True!
if _init is True:
return
# Beyond here is where you put the function's actual logic including any assertions, etc.
m = mp.Process(target=addi, args=(num1, num2))
m.start()
print("here is main", os.getpid())
m.join()
# All factory functions must be initialized BEFORE the "if __name__ ..." clause. If they require any parameters,
# substitute with bogus ones and make sure to put the _init parameter value as True!
calc(0, 0, _init=True)
if __name__ == '__main__':
a = calc(5, 6)
So there are a few things you would need to change in your code, namely that all local functions inside are defined at the top and all factory functions need to be initialized (for which they need to accept the _init parameter) outside the if __name__ ... clause. But this is probably the best you can do if you can't use dill.
set_start_method('fork') in main

How to filter by no extension in QFileSystemModel

The following code runs (after importing necessary libraries):
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MainWindow')
self.layout = QHBoxLayout()
self.file_system_widget = FileBrowser()
self.layout.addWidget(self.file_system_widget)
widget = QWidget()
widget.setLayout(self.layout)
self.setCentralWidget(widget)
class FileBrowser(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
layout = QVBoxLayout()
self.model = QFileSystemModel()
self.model.setRootPath(self.model.myComputer())
self.model.setNameFilters(['*.*'])
self.model.setNameFilterDisables(1)
self.tree = QTreeView()
self.tree.setModel(self.model)
self.tree.setAnimated(False)
self.tree.setSortingEnabled(True)
layout.addWidget(self.tree)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
try:
os.mkdir('Imports')
except:
pass
main = MainWindow()
main.show()
app.exec()
It gives the following result on my C drive:
(Some of these files are provided here https://drive.google.com/drive/folders/1ejY0CjfEwS6SGS2qe_uRX2JvlruMKvPX).
My objective is to modify the line self.model.setNameFilters(['*.*']) such that, in the tree view, it only shows files with dcm extension and also files without extension. That is, the part I draw red gets removed.
How do I achieve such a goal? For keeping dcm files, I can write lines like self.model.setNameFilters(['*.dcm']) to keep them and remove the others. But I am not sure how to deal with files without extension or how to deal with the two requirements at the same time .
The QFileSystemModel class only supports basic wildcard filtering, so you will need to use a QSortFilterProxyModel to get fully customisable filtering. This will allow you to use a regular expression to do the filtering, which achieves most of what you want quite simply. However, reproducing the behaviour of setNameFilterDisables will require a reimplemention of the flags method of the proxy model, and the sorting will also need some adjustment.
Below is a simple demo based on your example that implements all of that:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class FilterProxy(QSortFilterProxyModel):
def __init__(self, disables=False, parent=None):
super().__init__(parent)
self._disables = bool(disables)
def filterAcceptsRow(self, row, parent):
index = self.sourceModel().index(row, 0, parent)
if not self._disables:
return self.matchIndex(index)
return index.isValid()
def matchIndex(self, index):
return (self.sourceModel().isDir(index) or
super().filterAcceptsRow(index.row(), index.parent()))
def flags(self, index):
flags = super().flags(index)
if (self._disables and
not self.matchIndex(self.mapToSource(index))):
flags &= ~Qt.ItemIsEnabled
return flags
class FileBrowser(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
layout = QVBoxLayout()
self.model = QFileSystemModel()
self.model.setFilter(
QDir.AllDirs | QDir.AllEntries | QDir.NoDotAndDotDot)
self.proxy = FilterProxy(True, self)
self.proxy.setFilterRegularExpression(r'^(.*\.dcm|[^.]+)$')
self.proxy.setSourceModel(self.model)
self.tree = QTreeView()
self.tree.setModel(self.proxy)
self.tree.setAnimated(False)
header = self.tree.header()
header.setSectionsClickable(True)
header.setSortIndicatorShown(True)
header.setSortIndicator(0, Qt.AscendingOrder)
header.sortIndicatorChanged.connect(self.model.sort)
self.model.setRootPath(self.model.myComputer())
root = self.model.index(self.model.rootPath())
self.tree.setRootIndex(self.proxy.mapFromSource(root))
layout.addWidget(self.tree)
self.setLayout(layout)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MainWindow')
self.layout = QHBoxLayout()
self.file_system_widget = FileBrowser()
self.layout.addWidget(self.file_system_widget)
widget = QWidget()
widget.setLayout(self.layout)
self.setCentralWidget(widget)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
app.exec()

expand wxWrapSizer on secondary direction when adding new items

I have a panel that is a list of buttons. The number of buttons changes during runtime (due to user action elsewhere). I use a wxWrapSizer to manage these button since I want the height of this panel to remain the same and create a second column of buttons when it runs out of vertical space. The height is managed by the parent sizer based on the other widgets height. This almost works fine but the second column of buttons does not appear until after the window is manually resized.
I have created a minimal example to reproduce the issue:
import wx
start_buttons = 5
class ButtonsPanel(wx.Panel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.SetSizer(wx.WrapSizer(wx.VERTICAL))
for i in range(start_buttons):
self.add_button()
def add_button(self):
self.GetSizer().Add(wx.Button(self, label='foo'),
wx.SizerFlags().Expand())
class MyPanel(wx.Panel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# hardcoded size for sake of example only
add_button = wx.Button(self, label="add", size=(80, 250))
add_button.Bind(wx.EVT_BUTTON, self.OnAddButton)
self.buttons_panel = ButtonsPanel(self)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(add_button)
sizer.Add(self.buttons_panel, wx.SizerFlags().Expand())
self.SetSizer(sizer)
def OnAddButton(self, evt):
self.buttons_panel.add_button()
self.buttons_panel.Layout()
class MyFrame(wx.Frame):
def __init__(self, *args):
super().__init__(*args)
panel = MyPanel(self)
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
Clicking the large "Add" button will add new "foo" buttons but then it stops once it reaches the bottom of the frame. Manually resizing the frame will make the hidden second column appear.
Perform a Layout() of the main panel's sizer rather than the buttons panel sizer.
As the buttons panel's sizer "lives" inside the main panel's sizer, this is the one that needs to be recalculated, as it will recalculate it's children.
def OnAddButton(self, evt):
self.buttons_panel.add_button()
#self.buttons_panel.Layout()
self.Layout()
Edit:
for more complex setups you may need to note the parentor grandparent and update from within the buttonpanel i.e.
import wx
start_buttons = 5
class ButtonsPanel(wx.Panel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.SetSizer(wx.WrapSizer(wx.VERTICAL))
self.parent = parent
for i in range(start_buttons):
self.add_button()
def add_button(self):
self.GetSizer().Add(wx.Button(self, label='foo'),
wx.SizerFlags().Expand())
self.parent.Layout()
class MyPanel(wx.Panel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# hardcoded size for sake of example only
add_button = wx.Button(self, label="add", size=(80, 250))
add_button.Bind(wx.EVT_BUTTON, self.OnAddButton)
self.buttons_panel = ButtonsPanel(self)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(add_button)
sizer.Add(self.buttons_panel, wx.SizerFlags().Expand())
self.SetSizer(sizer)
def OnAddButton(self, evt):
self.buttons_panel.add_button()
#self.buttons_panel.Layout()
class MyFrame(wx.Frame):
def __init__(self, *args):
super().__init__(*args)
panel = MyPanel(self)
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
If it gets truly hellish, you could use pubsub to fire a Layout in the right place.

Adding methods in __init__() in Python

I'm making classes which are similar, but with different functions, depending on the use of the class.
class Cup:
def __init__(self, content):
self.content = content
def spill(self):
print(f"The {self.content} was spilled.")
def drink(self):
print(f"You drank the {self.content}.")
Coffee = Cup("coffee")
Coffee.spill()
> The coffee was spilled.
However, it is known during the initialization of an object whether or not the cup will be spilled or drank. If there are many cups, there's no need for all of them to have both functions, because only one of them will be used. How can I add a function during initialization?
Intuitively it should be something like this, but this apparently didn't work:
def spill(self):
print(f"The {self.content} was spilled.")
class Cup:
def __init__(self, content, function):
self.content = content
self.function = function
Coffee = Cup("coffee", spill)
Coffee.function()
> The coffee was spilled
If you create a class in Python with methods e.g.
class A
def method(self, param1, param)
It will make sure that when you call A().method(x,y) it fill the self parameter with instance of A. When you try specify method yourself outside of the class then you have to also make sure that the binding is done properly.
import functools
class Cup:
def __init__(self, content, function):
self.content = content
self.function = functools.partial(function, self)

Resources