What is function binding in python? - python

I was trying to understand the kivy library in python. Below is the complete code.
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 1
self.inside = GridLayout()
self.inside.cols = 2
self.inside.name = TextInput(multiline=False)
self.inside.add_widget(self.inside.name)
self.inside.add_widget(Label(text="Name: "))
self.inside.name = TextInput(multiline=False)
self.inside.add_widget(self.inside.name)
self.inside.add_widget(Label(text="Email: "))
self.inside.name = TextInput(multiline=False)
self.inside.add_widget(self.inside.name)
self.inside.add_widget(Label(text="Phno: "))
self.add_widget(self.inside)
self.submit = Button(text="Submit", font_size=40)
self.submit.bind(on_press=self.pressed)
self.add_widget(self.submit)
def pressed(self, instance):
print("pressed")
class MyApp(App):
def build(self):
return MyGrid()
if __name__ == "__main__":
MyApp().run()
Here, while in MyGrid class, under init method, when I try to bind the submit button(self.submit.bind) with the pressed method, the argument with on_press doesn't include parentheses.
self.submit.bind(on_press=self.pressed)
Why does it work? Is it because it's a python convention to not include parentheses while passing it to a method?
And what is the use of the instance argument in the pressed method?

If you pass the function without parentheses, you are passing the function object itself
self.submit.bind(on_press=self.pressed)
This is generally used for callback mechanisms, among other things. If you added parentheses, self.pressed() would be invoked immediately at that very statement, instead of on_press saving that function to call later, in response to a button click.

Parentheses call the method. In the cases where you omit them, you're passing the method itself as a callback to be invoked later. When the method is called, parentheses are involved, but not until then.

Python is an Object Oriented Language in the sense that everything is an object (even methods). In the example above the bind method takes a callable object as a parameter (so another method).
To get the callable object rather than call the method you omit the brackets.

It helps to see what self.pressed actually evaluates to.
The instance self doesn't have an attribute named pressed. Its class, MyGrid, has a function-valued attribute by that name, though.
So self.pressed evaluates to MyGrid.pressed. But, because the function class implements the descriptor protocol, you don't get a reference to that function object. Instead, you get the result of MyGrid.pressed.__get__(self, MyGrid). And that returns a callable object of type method. That object, when called, will take whatever arguments it receives and immediately pass self and those arguments to a call to MyGrid.pressed.
In short, the value of self.pressed looks a lot like a function that might have been defined like
def bound_method(*args, **kwargs):
return MyGrid.pressed(self, *args, **kwargs)
self.pressed == MyGrid.pressed
== MyGrid.__dict__['pressed'].__get__(self, MyGrid)
and when self.pressed is eventually called,
self.pressed(ins) == MyGrid.__dict_['pressed'].__get(self, MyGrid)(self, ins)

Related

Unable to overwrite class method in pyqt6 application

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.

How can i correctly decorate PyQt callback function?

In python PyQt signal's callback functions also have "hidden" parameter "state". This parameter is making harder function decoration by user-defined decorators.
My example of code and a decorator. In my solution i have to admit TypeErrors, caused by incorrect parameters count, for every callback without lambda, otherwice i can't decorate. Is there a better way, in which i can exclude admitting TypeError ? Can i ignore "state" parametr somehow else in my decorator?
Code example : decorator and program example.
from PyQt4 import QtCore, QtGui
def decor(func_fuc):
def new_func(*args, **kwargs):
#do something
try:
func_fuc(*args, **kwargs)
except TypeError as TypE:
if all(i in TypE.args[0] for i in ['takes', 'positional', 'argument' ,'but', 'given']):
args = tuple([args[ar] for ar in range(len(args)-1)])
func_fuc(*args, **kwargs)
else:
raise TypE
#etc
return new_func
class MainWind(QtGui.QMainWindow):
#decor
def __init__(self,parent=None,geometry = QtCore.QRect(100,100,800,800)):
super(MainWind, self).__init__(parent,geometry=geometry)
self.pushik = QtGui.QPushButton(
self, clicked = self.vasa
,geometry = QtCore.QRect(100,100,50,50), text = 'vasa'
)
self.petjaBtn = QtGui.QPushButton(
self, clicked = lambda state : self.petja()
,geometry = QtCore.QRect(100,200,50,50), text = 'petja'
)
self.someBtn = QtGui.QPushButton(
self, clicked = self.someBtnClick
,geometry = QtCore.QRect(100,300,50,50), text = 'someBtn'
)
self.show()
##decor
def vasa(*args):
print('vasa call')
print(args) #(<__main__.MainWind object at 0x018FF940>, False)
##decor
def petja(*args):
print('petja call')
print(args) #(<__main__.MainWind object at 0x018FF940>,)
def someBtnClick(self): # usual usage
print('someBtnClick call')
# signature : accept only "self", but works fine on button click(with 2 params, self and state)
# if i write empty decorator, that just returns original call, i will get typeError (reason - extra argument)
#how to decorate without admitting "argument" TypeError?
app=QtGui.QApplication([])
wind = MainWind()
app.exec_()
If you examine inspect.getfullargspec() of decorated functions, you'll see that their signatures are "wong".
See Fix Your Decorators , wrapt library.
But unfortunately writing the decorator using #wrapt.decorator() still don't solve the problem, though getfullargspec() looks correct. It seems that the PyQt do yet another code inspection to count the function attributes.

How to execute a class method from another class with the method passed a parameter in Python

I'm a beginner in learning python..
I'm looking for help in solving an OOP problem
My main program has something simplified like below:
class abc(Frame):
def _init_(self, master)
Frame.__init__(self)
self.B1 = Mybutton(self.master, self.cmd)
def cmd(self):
print("hello world")
In the main program, I import Mybutton class in another file, which is simplified as below:
class Mybutton():
def _init_(self, parent, command):
self.command = command
def A_ramdom_fcn(self):
...
self.command() ------------------>> here I want to execute the command
in class abc, not in class Mybutton.
How to execute a method from another class that is passed as an instance method, you may ask why not just execute it in class abc, but I have event attached to button press, it needs to do a roundabout to achieve this..
First, fix the typos: missing : in abc's init method, and it should be __init__ (with two underscores) for both classes.
It seems like you've gotten yourself turned around. You've set things up correctly using composition: an abc has a Mybutton, and it looks like you correctly pass the function to Mybutton so that it can execute it. In fact, your code will work as written if you do, for example
a = abc(master) # no context for what master is, but I assume you have it
a.B1.A_ramdom_fcn()
With the way you've set things up, you don't want to import and make instances of Mybutton in your main program (what abc would they belong to?). You want to import and make instances of abc. You then access their internal Mybutton like I've shown in the example above. This works because when you pass self.cmd to the Mybutton constructor while inside the abc constructor, it's already a bound method of the abc you're constructing.
As an addendum, it looks like you might be having an XY problem with regards to why you need such a roundabout method. Is there any reason why you can't simply pass abc.cmd to the button press handler?
Theoretically, what you are trying is possible, you can capture the object method into variable and call it later (python 3):
class Window:
def __init__(self):
self.my_button = Mybutton(self.cmd)
def cmd(self):
print("hello world")
class Mybutton:
def __init__(self, command):
self.command = command
def a_ramdom_fcn(self):
self.command.__call__()
win = Window()
win.my_button.a_ramdom_fcn()
I assume you are trying to make the generic Button class which doesn't know what to do when it's clicked and you want to put the actual logic into your Window class.
That makes sense, but it would be even better to extract the logic into the third, Command class. This allows us to limit the Window responsibility and also avoid the trick with method-as-variable (the command we pass to the button object is just another object):
class HelloWorldCommand:
def execute(self):
print("Hello world")
class Window:
def __init__(self):
self.my_button = Mybutton(
HelloWorldCommand()
)
class Mybutton:
def __init__(self, command):
self.command = command
def a_ramdom_fcn(self):
self.command.execute()
win = Window()
win.my_button.a_ramdom_fcn()

Gio SimpleAction to call a function

I have made menu using Gio action in a Gtk3 app.
The menu item is created as:
#in main file
MenuElem = menu.MenuManager
# Open Menu
action = Gio.SimpleAction(name="open")
action.connect("activate", MenuElem.file_open_clicked)
self.add_action(action)
The file_open_clicked is in menu.py, class MenuManager, defined as:
import gi
import pybib
import view
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class MenuManager:
def __init__(self):
self.parsing = pybib.parser()
self.TreeView = view.treeview()
#file_open_clicked
#in menu.py
def file_open_clicked(self, widget):
dialog = Gtk.FileChooserDialog("Open an existing fine", None,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
response = dialog.run()
if response == Gtk.ResponseType.OK:
filename = dialog.get_filename()
dialog.destroy()
self.TreeView.bookstore.clear()
self.TreeView.viewer(self.parsing.booklist)
# self.TreeView.view.set_model()
elif response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
dialog.destroy()
I am getting error:
Traceback (most recent call last):
File "/home/rudra/Devel/mkbib/Python/src/menu.py", line 81, in file_open_clicked
self.TreeView.bookstore.clear()
AttributeError: 'SimpleAction' object has no attribute 'TreeView'
I know SimpleAction takes one more option, and TreeView should be called.
But I dont know how.
Kindly help
Let me break down your code for you.
#in main file
MenuElem = menu.MenuManager
Here you set MenuElem to point to menu.MenuManager class. Probably you meant to initialize the object here such that MenuElem become an instance of the menu.MenuManagerclass. Such that the __init__ function of the MenuManager class was called. Thus the code should be:
#in main file
MenuElem = menu.MenuManager()
Then the next part where something goes wrong is in here:
def file_open_clicked(self, widget):
If we check the docs for the activate signal we see that it has 2 parameters. So currently without initializing the object self is set to the first parameter namely the SimpleAction and the widget is set to the activation parameter.
But as we now have initialized the MenuManager object, the file_open_clicked function will get 3 input parameters namely self, SimpleAction and parameter. Thus we need to accept them all like this:
def file_open_clicked(self, simpleAction, parameter):
Now the code will work as self is actually an object with the attribute TreeView. (Just for your information in Python variables and attributes are normally written in lowercase)
Your problem is that the TreeView attribute only exists on the MenuManager class, whereas, when you call the file_open_clicked method, the first argument (self) is the SimpleAction object created. Using the file_open_clicked method of a MenuManager instance would fix this.
menu_manager = MenuManager()
action = Gio.SimpleAction(name="open")
action.connect("activate", menu_manager.file_open_clicked)

Python TypeError when using Bind for a wx.Button

I have a class called WxFrame which creates a wxPython frame. I added a method called createRunButton which receives self and pydepp, which is the object of a PyDEPP class
import wx
class WxFrame(wx.Frame):
def __init__(self, parent, title):
super(WxFrame, self).__init__(parent, title=title)
self.Maximize()
self.Show()
def createRunButton(self,pydepp):
#pydepp.run()
self.runButton = wx.Button(self, label="Run")
self.Bind(wx.EVT_BUTTON, pydepp.run, self.runButton
This is the PyDEPP class:
class PyDEPP:
def run(self):
print "running"
I instantiate and run it with:
import wx
from gui.gui import WxFrame
from Depp.Depp import PyDEPP
class PyDEPPgui():
"""PyDEPPgui create doc string here ...."""
def __init__(self,pydepp):
self.app = wx.App(False)
##Create a wxframe and show it
self.frame = WxFrame(None, "Cyclic Depp Data Collector - Ver. 0.1")
self.frame.createRunButton(pydepp)
self.frame.SetStatusText('wxPython GUI successfully initialised')
if __name__=='__main__':
#Launch the program by calling the PyDEPPgui __init__ constructor
pydepp = PyDEPP()
pydeppgui = PyDEPPgui(pydepp)
pydeppgui.app.MainLoop()
The error I get when running the above code is:
TypeError: run() takes exactly 1 argument (2 given)
However, if I comment out the bind and uncomment the line pydepp.run(), then it works fine.
The answer is obvious I'm sure, but I have never studied CompSci or OO coding.
The event gets passed as an argument to the callback function. This should work:
class PyDEPP:
def run(self, event):
print "running"
When the event is triggered two arguments are passed to the callback function run(): the object which has triggered the event, and a wxEvent object. Since run only accepts one argument in your code the interpreter is giving that error which tells you that you've provided too many arguments.
Replace
run(self): # Expects one argument, but is passed two. TypeError thrown
with
run(self, event): # Expects two arguments, gets two arguments. All is well
and it should work.
This is one instance where the error tells you a lot about what's wrong with the code. Given that "run() takes exactly 1 argument (2 given)", you immediately know that either you've accidentally passed an extra argument, or run should be expecting another.

Categories

Resources