Frame.__init__(self, window) vs. super().__init__() - user-interface

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?

Related

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()

how to call __post_init__ in both subclass and baseclass which are defined by #dataclass [duplicate]

I'm trying to use the new python dataclasses to create some mix-in classes (already as I write this I think it sounds like a rash idea), and I'm having some issues. Behold the example below:
from dataclasses import dataclass
#dataclass
class NamedObj:
name: str
def __post_init__(self):
print("NamedObj __post_init__")
self.name = "Name: " + self.name
#dataclass
class NumberedObj:
number: int = 0
def __post_init__(self):
print("NumberedObj __post_init__")
self.number += 1
#dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
If I then try:
nandn = NamedAndNumbered('n_and_n')
print(nandn.name)
print(nandn.number)
I get
NumberedObj __post_init__
NamedAndNumbered __post_init__
n_and_n
1
Suggesting it has run __post_init__ for NamedObj, but not for NumberedObj.
What I would like is to have NamedAndNumbered run __post_init__ for both of its mix-in classes, Named and Numbered. One might think that it could be done if NamedAndNumbered had a __post_init__ like this:
def __post_init__(self):
super(NamedObj, self).__post_init__()
super(NumberedObj, self).__post_init__()
print("NamedAndNumbered __post_init__")
But this just gives me an error AttributeError: 'super' object has no attribute '__post_init__' when I try to call NamedObj.__post_init__().
At this point I'm not entirely sure if this is a bug/feature with dataclasses or something to do with my probably-flawed understanding of Python's approach to inheritance. Could anyone lend a hand?
This:
def __post_init__(self):
super(NamedObj, self).__post_init__()
super(NumberedObj, self).__post_init__()
print("NamedAndNumbered __post_init__")
doesn't do what you think it does. super(cls, obj) will return a proxy to the class after cls in type(obj).__mro__ - so, in your case, to object. And the whole point of cooperative super() calls is to avoid having to explicitely call each of the parents.
The way cooperative super() calls are intended to work is, well, by being "cooperative" - IOW, everyone in the mro is supposed to relay the call to the next class (actually, the super name is a rather sad choice, as it's not about calling "the super class", but about "calling the next class in the mro").
IOW, you want each of your "composable" dataclasses (which are not mixins - mixins only have behaviour) to relay the call, so you can compose them in any order. A first naive implementation would look like:
#dataclass
class NamedObj:
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
#dataclass
class NumberedObj:
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
#dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
BUT this doesn't work, since for the last class in the mro (here NamedObj), the next class in the mro is the builtin object class, which doesn't have a __post_init__ method. The solution is simple: just add a base class that defines this method as a noop, and make all your composable dataclasses inherit from it:
class Base(object):
def __post_init__(self):
# just intercept the __post_init__ calls so they
# aren't relayed to `object`
pass
#dataclass
class NamedObj(Base):
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
#dataclass
class NumberedObj(Base):
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
#dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
The problem (most probably) isn't related to dataclasses. The problem is in Python's method resolution. Calling method on super() invokes the first found method from parent class in the MRO chain. So to make it work you need to call the methods of parent classes manually:
#dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
NamedObj.__post_init__(self)
NumberedObj.__post_init__(self)
print("NamedAndNumbered __post_init__")
Another approach (if you really like super()) could be to continue the MRO chain by calling super() in all parent classes (but it needs to have a __post_init__ in the chain):
#dataclass
class MixinObj:
def __post_init__(self):
pass
#dataclass
class NamedObj(MixinObj):
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
#dataclass
class NumberedObj(MixinObj):
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
#dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
In both approaches:
>>> nandn = NamedAndNumbered('n_and_n')
NamedObj __post_init__
NumberedObj __post_init__
NamedAndNumbered __post_init__
>>> print(nandn.name)
Name: n_and_n
>>> print(nandn.number)
1

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)

Making closeEvent() work outside of a class in 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.

Resources