It worked fine all day suddenly it stopped. I don't believe I changed the code at all maybe my brain stopped working???
class BorderWidgetWithLabel(BorderWidget):
def __init__(self, **kwargs):
super(BorderWidgetWithLabel, self).__init__(**kwargs)
self.text=" "
self.text=kwargs.get('text',' ')
print self.text
self.txt_bg_color=kwargs.get("txt_bg_color", [0,0,0,0])
self.bg_color=kwargs.get("bg_color", [0,0,0,0])
self.bind(pos=self.update_canvas)
self.bind(size=self.update_canvas)
self.update_canvas()
def update_canvas(self, *args):
#need to reset everything
self.canvas.clear()
with self.canvas:
get_color(self.border_color)
if(self.text!=""):
texture=simple_text_texture(self.text)
The problem:
/typeclasses/borderwidget.py", line 75, in update_canvas
if(self.text!=""):
AttributeError: 'BorderWidgetWithLabel' object has no attribute 'text'
I started getting that error I added self.text=" " in the __init__ as well as the if statement in the update_canvas. What am I doing wrong??
If I get rid of all the uses of self.text then the print self.text works no problem.
UPDATE:: Fixed by adding text=' ' as a class variable. Is this necessary?? or is there something wrong with the code. As print self.text in the init works fine without the class variable I am unable to provide a less complete sample
UPDATE:: I removed the self.update_canvas() last line of the init and it worked without the class variable but I attempted to reproduce that with the following
class SomeClass:
def __init__(self, **kwargs):
self.text=kwargs.get("text","hello")
self.update_class()
def update_class(self, *args):
if(self.text!=' '):
print self.text
SomeClass()
I don't know why I was unable to reproduce it then
So, it looks like you have
class BW:
def __init__(self):
self.update_canvas()
def update_canvas(self):
pass
class BWL(BW):
def __init__(self):
super(BWL, self).__init__()
self.text = 'text'
self.update_canvas()
def update_canvas(self):
print self.text
Running BWL() would throw the exception you listed. This is because the order of events is
call BWL.init
which calls BW.init (with self = BWL)
which calls self.update_canvas (which is BWL.update_canvas)
which accesses self.text
Then, when BW.init returns, it creates self.text
and finally calls self.update_canvas again.
This is probably not what you want even without the bug... probably remove the call to super or the call to update_canvas in the child, and set up the properties you need in the update_canvas method BEFORE calling the super class.
Related
So normally you can overwrite a class method by doing something like this.
class A():
def test(self):
return 1+1
def edit_patch(func):
def inner(*args,**kwargs):
print('this test worked')
return inner
a=A()
a.test = edit_patch(a.test)
Now a.test will return 'this test worked' instead of 2. I'm trying to do something similar in my pyqt6 application. The function below belongs to the "main" class in my code and is connected to a button click. This function is meant to instantiate another class (which is another window in pyqt6). That part works, but I would like to alter the behavior of the select function in this instance. However the method above doesn't seem to work as the select function continues to exhibit the default behavior.
def edit_proj(self):
self.psearch=PSearch(conn=self.conn,parent=self)
self.psearch.select = edit_patch(self.psearch.select)
self.psearch.show()
Any help on this would be great
As requested, here is an MRE
from PyQt6 import QtCore, QtGui, QtWidgets
def edit_patch(func):
def inner(*args,**kwargs):
print('this test worked')
return inner
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(50, 50)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.EditProjButton = QtWidgets.QPushButton(self.centralwidget)
self.EditProjButton.setObjectName("EditProjButton")
self.EditProjButton.clicked.connect(self.nextwindow)
def nextwindow(self):
print('hello from main window')
self.newwindow=Ui_ProjSearchForm(QtWidgets.QWidget())
self.newwindow.select = edit_patch(self.newwindow.select)
self.newwindow.show()
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(self.select)
def setupUi(self, ProjSearchForm):
ProjSearchForm.setObjectName("ProjSearchForm")
ProjSearchForm.resize(100, 100)
self.gridLayout = QtWidgets.QGridLayout(ProjSearchForm)
self.gridLayout.setObjectName("gridLayout")
self.SearchButton = QtWidgets.QPushButton(ProjSearchForm)
self.SearchButton.setObjectName("SearchButton")
self.gridLayout.addWidget(self.SearchButton, 0, 2, 1, 1)
def select(self):
print('this is default behavior')
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec())
Signal connections work by passing a reference to a callable, and that reference is an "internal" pointer to that function. Overwriting the name of that function will have absolutely no result.
Take this example:
class Test(QPushButton):
def __init__(self):
super().__init__('Click me!')
self.clicked.connect(self.doSomething)
self.doSomething = lambda: print('bye!')
def doSomething(self):
print('hello!')
The code above will always print "hello!", because you passed the reference to the instance method doSomething that existed at the time of the connection; overwriting it will not change the result.
If you need to create a connection that can be overwritten, you have different possibilities.
Pass the function to the constructor
You can set the function as an optional argument in the __init__ and then connect it if specified, otherwise use the default behavior:
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm(edit_patch(self.newwindow.select))
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
if func is not None:
self.SearchButton.clicked.connect(func)
else:
self.SearchButton.clicked.connect(self.select)
Create a method for the connection
In this case we pass the reference to a specific method that will create the connection, eventually disconnecting any previous connection (remember that signals can be connected to multiple functions, and even the same function multiple times). This is similar to the approach above.
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm()
self.newwindow.setSelectFunc(edit_patch(self.newwindow.select))
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(self.select)
def select(self):
print('this is default behavior')
def setSelectFunc(self, func):
try:
self.SearchButton.clicked.disconnect(self.select)
except TypeError:
pass
self.select = func
self.SearchButton.clicked.connect(self.select)
Use a lambda
As said above, the problem was in trying to overwrite the function that was connected to the signal: even if the connected function is a wrapper, the direct reference for the connection is not actually overwritten.
If you, instead, connect to a lambda that finally calls the instance method, it will work as expected, because the lambda is dynamically computed at the time of its execution and at that time self.select will be a reference to the overwritten function.
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm()
self.newwindow.select = edit_patch(self.newwindow.select)
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(lambda: self.select())
def select(self):
print('this is default behavior')
Some unrelated but still important notes:
You should never edit pyuic generated files, nor try to merge their code into your script or mimic their behavior. Instead, follow the official guidelines about using Designer.
Passing a new QWidget instance as argument is pointless (other than wrong and potentially dangerous); if you want to create a new window for the new widget, just avoid any parent at all, otherwise use QDialog for modal windows.
Only classes and constants should have capitalized names, everything else should be named starting with lowercase letters (this includes object names created in Designer); read more about this and other important topics in the official Style Guide for Python Code.
Ok I think I've figured out a solution (in the MRE posted in question). There's some shenanigans that go on in the back ground once you connect a button in the UI to a function. It's not a "live" connection like in the a.test example, so edits to the function later don't have an impact on how the button functions.
So, if we replace
self.newwindow.select = edit_patch(self.newwindow.select)
with
self.newwindow.SearchButton.clicked.disconnect()
self.newwindow.select = edit_patch(self.newwindow.select)
self.newwindow.SearchButton.clicked.connect(self.newwindow.select)
we suddenly get the desired behavoir from the button. This was entirely too frustrating.
I am using a 3rd party Python library (wxPython), which has a buggy class in one of its modules.
The problematic code section looks like this:
def OnText(self, event):
value = self.GetValue()
if value != self.__oldvalue:
pass # Here some more code follows ...
self.__oldvalue = value
The problem is the if statement, because at the first call to this method self.__oldvalue has not been initialized yet. So for a workaround until this bug has been fixed by the library devs I thought I could fix this with a little workaround. I simply wanted to derive a child class from that faulty class and initialize self.__oldvalue in this constructor:
class MyIntCtrl(wx.lib.intctrl.IntCtrl):
def __init__(self, *args, **kw):
self.__oldvalue = None
super().__init__(*args, **kw)
However, now when I use this new class MyIntCtrl instead of the original IntCtrl class, I do get exactly the same error as before:
Traceback (most recent call last):
File "/usr/local/lib/python3.6/dist-packages/wx/lib/intctrl.py", line 509, in OnText
if value != self.__oldvalue:
AttributeError: 'MyIntCtrl' object has no attribute '_IntCtrl__oldvalue'
Now I am wondering: What am I doing wrong, how else can I fix this issue in a child class?
Any member of class which starts with __ (double underscore) is private, you can use single underscore _ or not use underscores in naming for access them in derived classes.
class Parent:
def __init__(self):
self.__private_field = "private field"
self._protected_field = "protected field"
self.public_field = "public field"
class Child(Parent):
def __init__(self):
pass
def do(self):
print(self.__private_field) # It will throw exception
print(self._protected_field) # It will not throw exception
print(self.public_field) # It will not throw exception
Or you can bypass private/protected members by calling them like:
print(_Parent__private_field)
I'm writing a text editor in Jython. This text editor has a toolbar which is displayed with a ToolbarView class and handled by a ToolbarController class. Some actions can't be dealt with by the ToolbarController on its own, so these are delegated to the MainController class.
To avoid repeating code since there are many actions delegated from the ToolbarController to the MainController, I've used getattr as suggested in a previous question I asked here. I have also realised I can use the same mechanism in the ToolbarView code for the actions of the buttons, but I can't get it to work and I end up getting an infinite loop and a Java StackOverflowError.
This is an extract of the relevant code:
ToolbarView class:
from javax.swing import JToolBar, ImageIcon, JButton
class ToolbarView(JToolBar):
def __init__(self, controller):
#Give reference to controller to delegate action response
self.controller = controller
options= ['NewFile', 'OpenFile', 'SaveFile', 'CloseFile']
for option in options:
methods[option] = "on" + option + "Click"
print methods[option]
for name, method in methods.items():
button = JButton(name, actionPerformed=getattr(self, method))
self.add(button)
def __getattr__(self, name):
return getattr(self.controller, name)
ToolbarController class:
from .ToolbarView import ToolbarView
class ToolbarController(object):
def __init__(self, mainController):
#Create view with a reference to its controller to handle events
self.view = ToolbarView(self)
#Will also need delegating to parent presenter
self.mainController = mainController
def __getattr__(self, name):
return getattr(self.mainController, name)
MainController class:
from .ToolbarController import ToolbarController
class MainController(object):
def __init__(self):
self.toolbarController = ToolbarController(self)
def onNewFileClick(self, event):
print("MainController: Creating new file...")
def onEditFileClick(self, event):
print("MainController: Editting new file...")
def onSaveFileClick(self, event):
print("MainController: Saving new file...")
def onCloseFileClick(self, event):
print("MainController: Closing new file...")
So what I expect is when I click the button, MainController.onNewFileClick gets executed and prints out that message in console. It works if I want to delegate from the ToolbarView to the ToolbarController, but it doesn't work when I pass that delegation from the ToolbarController to the MainController. It seems to call itself on an infinite loop. The error I get is:
Traceback (most recent call last):
File "main.py", line 3, in <module>
MainController()
File "/home/training/Jython/controller/MainController", line 8, in __init__
self.toolbarController = ToolbarController(self)
File "/home/Jython/controller/ToolbarController.py", line 8, in __init__
self.view = ToolbarView(self)
File "/home/Jython/controller/ToolbarView.py", line 44, in __init__
button = JButton(name, actionPerformed=getattr(self, method))
File "/home/Jython/controller/ToolbarView.py", line 54, in __getattr__
return getattr(self.controller, name)
File "/home/Jython/controller/ToolbarController.py", line 15, in __getattr__
return getattr(self.mainController, name)
File "/home/Jython/controller/ToolbarController.py", line 15, in __getattr__
return getattr(self.mainController, name)
[...]
File "/home/Jython/controller/ToolbarController.py", line 15, in __getattr__
return getattr(self.mainController, name)
RuntimeError: maximum recursion depth exceeded (Java StackOverflowError)
What am I doing wrong? I've tried something similar in python (delegating from a class to another class to another class) and it works if a put () after the getattr, but here I get confused because of the actionPerformed in the JButton. I've tried it but results are the same.
it seems you are using Jython, which i don't really know. anyways, in python, you override __getattr__, then you should expect getattr to use your overridden hook instead. so i think you really mean:
class ToolbarView(JToolBar):
def __init__(self, controller):
#Give reference to controller to delegate action response
self.controller = controller
options= ['NewFile', 'OpenFile', 'SaveFile', 'CloseFile']
for option in options:
methods[option] = "on" + option + "Click"
print methods[option]
for name, method in methods.items():
button = JButton(name, actionPerformed=super(ToolbarView, self).__getattr__(method))
self.add(button)
def __getattr__(self, name):
return getattr(self.controller, name)
watch how buttons are created.
in terms of why you have a SO problem, it is because how getattr is handled. if you override __getattr__, this hook will only get called if you try to reference to a undefined field:
>>> class A(object):
defined = True
def __getattr__(self, name):
print "referenced :" + name
>>> a = A()
>>> a.defined
True
>>> a.undefined
referenced :undefined
hope it's clear how the hook work now.
so the SO is actually caused by you were referencing something that does not belong to MainController.
in your MainController, only onNewFileClick is defined, but you defined 3 other options:
options= ['NewFile', 'OpenFile', 'SaveFile', 'CloseFile']
so, this will happen at the second round of iteration. since MainController has no onOpenFileClick, an AttributeError will be raised, but captured by ToolbarController, and therefore the overridden __getattr__ is invoked and on and on. that's why your call stack explodes.
I was blaming this to getattr since I'm not that confident using it yet, but it turns out it was something rather basic.
I was assigning the mainController to the ToolbarController AFTER creating the ToolbarView which then calls ToolbarView.__getatrr__, which calls ToolbarController.__getattr__ which tries to access self.mainController which doesn't exist yet!
This is the change I needed to make in the ToolbarController class.
Before
from .ToolbarView import ToolbarView
class ToolbarController(object):
def __init__(self, mainController):
#Create view with a reference to its controller to handle events
self.view = ToolbarView(self)
#Will also need delegating to parent presenter
self.mainController = mainController
def __getattr__(self, name):
return getattr(self.mainController, name)
After:
from .ToolbarView import ToolbarView
class ToolbarController(object):
def __init__(self, mainController):
#Needs to delegate to main presenter.
#Note self.mainController needs to exist before creating the ToolbarView
#since it needs delegating actions to it!
self.mainController = mainController
#Create view with a reference to its controller to handle events
self.view = ToolbarView(self)
def __getattr__(self, name):
return getattr(self.mainController, name)
Thanks a lot to #HuStmpHrrr and #ArtOfWarfare for their help.
When I run the next part of code:
class VState(State):
def __init__(self, name='', stateType=None, **kwargs):
super(VState, self).__init__(**kwargs)
self.vBackground = 'my_background'
self.name = name
def setBackgroundImage(self):
print (self.vBackground)
return 'gui/my_background_image'
it will be done. When I call setBackgroundImage() method from .kv file, I get an error: "AttributeError: 'VState' object has no attribute 'vBackground'"
.kv file:
...
source: 'atlas://' + root.setBackgroundImage()
but when I run code above without referencing to any attribute, it will be done again... Without line
print (self.vBackground)
it will be done. Why I can't refer to any attributes from kv file?
Thanks for something ideas...
The kv is first evaluated during the widget __init__, which in this case happens in your super call before you set self.vBackground.
You can instead change the order to
self.vBackground = 'my_background'
super(VState, self).__init__(**kwargs)
It may be even better to use a StringProperty.
I'm writing a calculator using wxPython for the GUI. I've made a class called display to use StaticText to display the text. Anyways, when I try to update the screen, it raises an exception.
Here's the code:
class display:
def __init__(self,parent, id):
print "display class is working"
global string1
self.view = wx.StaticText(frame, -1, "Waiting", (30,7), style = wx.ALIGN_CENTRE)
#staticmethod
def update(self):
global string1
self.view.SetLabel(string1)
Whenever I try to run the Update() function, it raises this exception:
AttributeError: 'function' object has no attribute 'view'
When I wrote "self.view = wx. etc etc", I tried to set the StaticText to a variable name, so I could use the SetLabel function. The text seems to work until I try to update it. Why can't I update it? How do I fix it?
#staticmethods take no arguments ... so its not actually getting self ... you need to either make it a #classmethod which gets cls or you need to just make it a normal method
class display:
view = None
def __init__(self,parent, id):
print "display class is working"
global string1
display.view = wx.StaticText(frame, -1, "Waiting", (30,7), style = wx.ALIGN_CENTRE)
#classmethod
def update(cls):
global string1
cls.view.SetLabel(string1)