emit_signal: Error calling method from signal 'animation_finished': 'set': Method expected 2 arguments, but called with 3 - godot

I'm trying to reset the value of a variable after the animation is complete like this:
extends Node2D
var testing=false
func _ready():
var ani_player=$AnimationPlayer
ani_player.play("ani_2")
ani_player.connect("animation_finished",self,"set",["testing",false])
but for some reason it gives the error:
E 0:00:00.479 emit_signal: Error calling method from signal
'animation_finished': 'Node2D(Main.gd)::set': Method expected 2
arguments, but called with 3.. <C++ Source> core/object.cpp:1242 #
emit_signal()
which makes no sense I'm passing 2 variables ["testing",false] why does it keeping getting a third one?
am I missing something?

The variables you bind are passed in addition to the ones that the signal passes… And animation_finished passes the name of the animation. So the method you are calling (set) would actually get three arguments. To deal with that make and extra method - which ignores the first parameter - and connect to it instead.

based on #Theraot 's answer here's what I did:
extends Node2D
var testing=false
func animation_finished_callback(var _extra, var new_val):
testing=new_val
func _ready():
var ani_player=$AnimationPlayer
ani_player.play("ani_2")
ani_player.connect("animation_finished",self,"animation_finished_callback",[false],CONNECT_ONESHOT)

Related

Usage of __setattr__ to rewrite whole method of library class issue: missing 1 required positional argument: 'self'

I've got some imported packages with tricky structure
and need to call some method that bases on lots of other methods
with non-default parameters, which are not class attributes themself like pipeline in sklearn.
Minimal example of this module structure:
class Library_class:
def __init__(
self,
defined_class_options,
):
self.defined_class_options = defined_class_options
def method1( self , default_non_class_arg = 12 ):
assert self.defined_class_options==3
return default_non_class_arg
def method2( self, image ):
return image/ self.method1()
Default usage:
class_instance = Library_class( 3 )
class_instance.method2( 36 )
> 3.0
I need to set default_non_class_arg to 6 for example.
I've tried multiple approaches:
Analogous to https://stackoverflow.com/a/35634198/7607734
class_instance.method2( 36 ,
method1__default_non_class_arg=3 )
TypeError: method2() got an unexpected keyword argument 'method1__default_non_class_arg'
It don't work probably because class definitely don't have set_params
With setattr on redefined function
class_instance.__setattr__('method1',Library_class.new_method1)
class_instance.method2( 36 )
TypeError: new_method1() missing 1 required positional argument: 'self'
Both your snippets and question are quite messy, almost to the point of being unreadable.
Anyway, if you wantt to replace method1 with another function, say new_method1 in an specific instance, just do that. Your call to .__setattr__ does that, but it is not needed at all, (and if it was, due to you not having the method to be replaced name at code writting time, and needed it as a parameter, it is more correct to call the built-in setattr, not the instance method: `setattr(class_instance, "method1", new_method1").
Ordinarily, if you know, at code writting time you have to replace "method1" in an instance, the assigment operator will do it:
class_instance.method1 = new_method1
What went wrong in your examle is that if you assign a method to an instance, instead of a class, you are bypassing the mechanism that Python uses to insert the self attribute into it - so your new_method1 needs a different signature. (and this is exactly what the error message "TypeError: new_method1() missing 1 required positional argument: 'self'" is saying):
class MyClass:
...
def method1(self, param1=36):
...
...
def new_method1(param1=6): # <-- written outside of any class body, sans self
...
my_instance = MyClass()
my_instance.method1 = new_method1
this will work.
new_method1 could be written in a class body as well, and could be replaced just the same, but you would have to write it without the self parameter the same, and then it would not work straight as a normal method.
OR, you can, at assigment time, insert the self argument yourself - the functools.partial call is a convenient way to do that:
class MyClass:
...
def method1(self, param1=36):
...
def new_method1(self, param1=6):
...
...
my_instance = MyClass()
from functools import partial
MyClass.method1 = partial(MyClass.new_method1, my_instance)
Now, this should answer what you are asking, but it would not be honest of me to end the answer without saying this is not a good design. The best thing there is to pull your parameter from another place, it might be from an instance attribute, instead of replacing the method entirely just to change it.
Since for normal attributes, Python will read the class attribute if no instance attribute exists, it will happen naturally, and all you have to do is to set the new default value in your instance.
class MyClass:
default_param_1 = 36 # Class attribute. Valid for every instance unless overriden
...
def method1(self, param1=None):
if param1 is None:
param1 = self.default_param_1 #Automatically fetched from the class if not set on the instance
...
...
my_instance = MyClass()
my_instance.default_param_1 = 6
...

Bind variable number of PyQt toggles to same number of functions [duplicate]

I'm connecting multiple signal/slots using a for loop in PyQt. The code is bellow:
# Connect Scan Callbacks
for button in ['phase', 'etalon', 'mirror', 'gain']:
getattr(self.ui, '{}_scan_button' .format(button)).clicked.connect(
lambda: self.scan_callback(button))
What I expect:
Connect button phase_scan_button clicked signal to the scan_callback slot and send the string phase as a parameter to the slot. The same for etalon, mirror and gain.
What I'm getting:
For some reason my functions is always passing the string gain as parameter for all the buttons. Not sure if I'm being stupid (likely) or it is a bug.
For reference, the slot method:
def scan_callback(self, scan):
print(scan) # Here I always get 'gain'
if self.scanner.isWorking:
self.scanner.isWorking = False
self.scan_thread.terminate()
self.scan_thread.wait()
else:
self.scanner.isWorking = True
self.scan_thread.start()
getattr(self.ui, '{}_scan_button' .format(
scan)).setText('Stop Scan')
getattr(self, '_signal{}Scan' .format(scan)).emit()
My preferred way of iterating over several widgets in pyqt is storing them as objects in lists.
myButtons = [self.ui.phase_scan_button, self.ui.etalon_scan_button,
self.ui.mirror_scan_button, self.ui.gain_scan_button]
for button in myButtons:
button.clicked.connect(lambda _, b=button: self.scan_callback(scan=b))
If you need the strings "phase", "etalon", "mirror" and "gain" separately, you can either store them in another list, or create a dictionary like
myButtons_dict = {"phase": self.ui.phase_scan_button,
"etalon": self.ui.etalon_scan_button,
"mirror": self.ui.mirror_scan_button,
"gain": self.ui.gain_scan_button}
for button in myButtons_dict:
myButtons_dict[button].clicked.connect(lambda: _, b=button self.scan_callback(scan=b))
Note, how I use the lambda expression with solid variables that are then passed into the function self.scan_callback. This way, the value of button is stored for good.
Your lambdas do not store the value of button when it is defined. The code describing the lambda function is parsed and compiled but not executed until you actually call the lambda.
Whenever any of the buttons is clicked, the current value of variable button is used. At the end of the loop, button contains "gain" and this causes the behaviour you see.
Try this:
funcs = []
for button in ['phase', 'etalon', 'mirror', 'gain']:
funcs.append( lambda : print(button))
for fn in funcs:
fn()
The output is:
gain
gain
gain
gain
Extending the example, as a proof that the lambdas don't store the value of button note that if button stops existing, you'll have an error:
del button
for fn in funcs:
fn()
which has output
funcs.append( lambda : print(button))
NameError: name 'button' is not defined
As noted here : Connecting slots and signals in PyQt4 in a loop
Using functools.partial is a nice workaround for this problem.
Have been struggling with same problem as OP for a day.

QCheckbox issue [duplicate]

I am struggling to get this working.
I tried to transpose from a c++ post into python with no joy:
QMessageBox with a "Do not show this again" checkbox
my rough code goes like:
from PyQt5 import QtWidgets as qtw
...
mb = qtw.QMessageBox
cb = qtw.QCheckBox
# following 3 lines to get over runtime errors
# trying to pass the types it was asking for
# and surely messing up
mb.setCheckBox(mb(), cb())
cb.setText(cb(), "Don't show this message again")
cb.show(cb())
ret = mb.question(self,
'Close application',
'Do you really want to quit?',
mb.Yes | mb.No )
if ret == mb.No:
return
self.close()
the above executes with no errors but the checkbox ain't showing (the message box does).
consider that I am genetically stupid... and slow, very slow.
so please go easy on my learning curve
When trying to "port" code, it's important to know the basis of the source language and have a deeper knowledge of the target.
For instance, taking the first lines of your code and the referenced question:
QCheckBox *cb = new QCheckBox("Okay I understand");
The line above in C++ means that a new object (cb) of type QCheckBox is being created, and it's assigned the result of QCheckBox(...), which returns an instance of that class. To clarify how objects are declared, here's how a simple integer variable is created:
int mynumber = 10
This is because C++, like many languages, requires the object type for its declaration.
In Python, which is a dynamic typing language, this is not required (but it is possible since Python 3.6), but you still need to create the instance, and this is achieved by using the parentheses on the class (which results in calling it and causes both calling __new__ and then __init__). The first two lines of your code then should be:
mb = qtw.QMessageBox()
cb = qtw.QCheckBox()
Then, the problem is that you're calling the other methods with new instances of the above classes everytime.
An instance method (such as setCheckBox) is implicitly called with the instance as first argument, commonly known as self.
checkboxInstance = QCheckBox()
checkboxInstance.setText('My checkbox')
# is actually the result of:
QCheckBox.setText(checkboxInstance, 'My checkbox')
The last line means, more or less: call the setText function of the class QCheckBox, using the instance and the text as its arguments.
In fact, if QCheckBox was an actual python class, setText() would look like this:
class QCheckBox:
def setText(self, text):
self.text = text
When you did cb = qtw.QCheckBox you only created another reference to the class, and everytime you do cb() you create a new instance; the same happens for mb, since you created another reference to the message box class.
The following line:
mb.setCheckBox(mb(), cb())
is the same as:
QMessageBox.setCheckBox(QMessageBox(), QCheckBox())
Since you're creating new instances every time, the result is absolutely nothing: there's no reference to the new instances, and they will get immediately discarded ("garbage collected", aka, deleted) after that line is processed.
This is how the above should actually be done:
mb = qtw.QMessageBox()
cb = qtw.QCheckBox()
mb.setCheckBox(cb)
cb.setText("Don't show this message again")
Now, there's a fundamental flaw in your code: question() is a static method (actually, for Python, it's more of a class method). Static and class methods are functions that don't act on an instance, but only on/for a class. Static methods of QMessageBox like question or warning create a new instance of QMessageBox using the provided arguments, so everything you've done before on the instance you created is completely ignored.
These methods are convenience functions that allow simple creation of message boxes without the need to write too much code. Since those methods only allow customization based on their arguments (which don't include adding a check box), you obviously cannot use them, and you must code what they do "under the hood" explicitly.
Here is how the final code should look:
# create the dialog with a parent, which will make it *modal*
mb = qtw.QMessageBox(self)
mb.setWindowTitle('Close application')
mb.setText('Do you really want to quit?')
# you can set the text on a checkbox directly from its constructor
cb = qtw.QCheckBox("Don't show this message again")
mb.setCheckBox(cb)
mb.setStandardButtons(mb.Yes | mb.No)
ret = mb.exec_()
# call some function that stores the checkbox state
self.storeCloseWarning(cb.isChecked())
if ret == mb.No:
return
self.close()

Binding method with multiple arguments to <<ListboxSelect>>

Trying to make a method execute on the selection of an item in a tkinter listbox. I can bind some function to is using .bind("<<ListboxSelect>>", method), but if i try to give any arguments to it, like method(argumentone, argumenttwo), the method no longer executes on the selection of an item from the listbox. It is however executed at startup when Mainloop starts. Furthermore, giving arguments to a method is problematic, as i cant pass the event argument already passed to any bound method, as it is not assigned to any variable beforehand. I'd like to be able to pass the event and one other variable to some method ideally. Any help on achieving this would be very welcome. Some example code of what I'm trying to achieve
def onSelection(event, foo):
widget = event.widget
print(widget.curselection())
print(foo)
sam = "sam"
chars = Listbox(main)
chars.insert(someList)
chars.bind("<<ListboxSelect>>", onSelection(evt, sam))
Note that this code doesn't work. evt isn't defined.
When you specify a function in bind() that ends in parenthesis it just runs the function without bind generating an event. Instead you have to give bind a name. You can accomplish this with lambda or functools.partial. I'll use lambda in my example.
Bind is generating an event when triggered, so lambda will have to take it as input. Then call onSelection() with desired arguments.
from tkinter import *
main = Tk()
def onSelection(event, foo):
widget = event.widget
print(widget.curselection())
print(foo)
someList = ['Stilton', 'Brie', 'Edam', 'Cheddar', 'Ilchester']
chars = Listbox(main)
chars.pack(padx=10, pady=10)
for item in someList:
chars.insert("end", item)
sam = "sam"
chars.bind("<<ListboxSelect>>", lambda event: onSelection(event, sam))
# Take care of event created by bind ----^ ^
# Pass the event as well as argument to callback function ----|
main.mainloop()

Raspberry Pi GPIO Interrupt callback variable

So I have a pretty simple question which I haven't found the answer to anywhere around here. Simply put, I define a callback function which requires a variable, and that variable may change. Do I need to remove event detection on that pin, then add it again to get the callback associated with the event to use the new variable value on next event?
def t_doSomething(var):
print(var)
var = 'foo'
channel = 17
GPIO.add_event_detect(channel, GPIO.RISING, callback=t_doSomething(var) bouncetime=200)
#Event called by rising edge prints 'foo'
#Change the variable.
var = 'bar'
#Event called a second time prints...?
When you say I assume the value of the variable may change. Also assuming the environment is raspbian Linux not (windows 10 IoT core etc) what you do should work. After you change the variable, when you call the function again or when you put it in a loop of some sort. The new printed value should be the new value of the "var". So #1 below should print "foo" and #2 should print "bar".
def t_doSomething(var):
print(var)
GPIO.add_event_detect(channel, GPIO.RISING, callback=t_doSomething(var)bouncetime=200)
var = 'foo'
channel = 17
#Change the variable.
var = 'bar'
do_something()
if GPIO.event_detected(channel):
print(var)
Hope this helps!
Actually you do not pass the function, but call it and pass its result. Instead
GPIO.add_event_detect(channel, GPIO.RISING, callback=t_doSomething,bouncetime=200)
would have passed the function instead. It is not possible to pass a variable to this callback. You would need to work with global variables instead.

Resources