Calling functions with arguments on "command" and "bind" - python

i want to point, that i am learning python since short time.
The question is going be to beginner one.
I need to add command to menu at top of program, which would call function "color_picker("red").
kolory.add_command(label="Czerwony", command=color_picker('red'))
When i am using that, its somehow wrong, cuz its called once the program started, its not waiting for me to click the menu button. (i am sure of it, as i added "showinfo" to that function, and it shows the message before i do anything)
kolory.add_command(label="Czerwony", command=lambda: color_picker('red')) That one kinda works, but i don't know what does "lambda" mean here. Is it only way to call functions with arguments under menu options?
Same question goes to binding keyboard shortcuts.
okno.bind("1", color_picker) - that will call the function but does not have the argument, which should be a color. How can i do that?
So, how to assign functions WITH arguments, to keyboard shortcuts and to menu using add_command?
PS. Was searching trough google, but it seems python does not have so good documentation like c# does for example. Or i am too stupid to find it.
EDIT:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1410, in __call__
return self.func(*args)
TypeError: color_picker() takes at most 1 argument (2 given)
Thats the error message, when i try to use "pick_red" in okno.bind

I'm not sure if I understand the question, but here goes;
The problem is that you are calling the color_picker function (by adding () after the function name).
What you want to do is pass the actual function, not the result of the function call as the command keyword argument, e.g. add_command(label="Czerwony", command=color_picker)
However, since you want to give it a fixed argument 'red', you must use partial from functools, something like;
from functools import partial
pick_red = partial(color_picker, "red")
kolory.add_command(label="Czerwony", command=pick_red)
EDIT:
Now that your error message shows that you are using Tkinter, we can see that according to documentation the function that is given to bind() is always passed an event parameter, so you need a function that can accept it;
def pick_red_with_event(event):
# We really do nothing with event for now but we always get it...
color_picker("red")
okno.bind("1", pick_red_with_event)
Same thing works for okno.bind, if you have defined pick_red as above, just do:
okno.bind("1", pick_red)

Related

How can I call a python function from inside an AHK script?

How can I run a python function from an AHK script? If it's possible, how would I:
Pass arguments to the python function?
Return data from the python function back to my running AHK script?
The only relevant information I could find was this answer, however I could not manage to implement it into my own script.
My best attempt at implementing this is the following snippet of code:
e::; nothing, because RunWait it's the command recommended in the question that I linked, so that means that I have to do a RunWait every time I press e?
There is no native way to do this, as the only interaction AHK can do with Python is to run an entire script. However, you can modify the python script to accept arguments, then you can interact between the scripts (like the linked question).
The solution is as follows- similar to the question you linked, set up the python script so that it takes the function name and function parameters as arguments, then have it run the function with those arguments.
Similar to my answer on the linked question, you can use sys.argv to do this:
# Import the arguments from the sys module
from sys import argv
# If any arguments were passed (the first argument is the script name itself, so you must check for >1 instead of >0)
if len(argv) > 1:
# This next line is basically saying- If argv is longer than 2 (the script name and function name)
# Then set the args variable to everything other than the first 2 items in the argv array
# Otherwise, set the args variable to None
args = argv[2:] if len(argv) > 2 else None
# If arguments were passed, then run the function (second item in argv) with the arguments (the star turns the list into args, or in other words turns the list into the parameter input format)
# Otherwise, run the function without arguments
argv[1](*args) if args else argv[1]()
# If there is code here, it will also execute. If you want to only run the function, then call the exit() function to quit the script.
Then, from AHK, all you would need to do is run the RunWait or Run command (depending on whether you want to wait for the function to finish) to call the function.
RunWait, script.py "functionName" "firstArgument" "secondArgument"
The second part of your question is tricky. In the question you linked, there is a nice explanation on how to return integer values (TLDR: use sys.exit(integer_value)), however if you want to return all sorts of data, like strings, for example, then the problem becomes more confusing. The thing is that at this point, I think the best solution is to write the output to a file, then have AHK read the file after the Python script is done executing. However, if you're already going to go down the "write to a file, then read it" route, then you might as well have already done that from the start and used that method to pass the function name and arguments to the python script.

How to invoke a function that has a parameter(s) without using parenthesis in Python

I'm building a data visualization app in Python using Tkinter for the GUI and data science libraries Matplotlib, Seaborn, Pandas and NumPy for the backend.
I have the following line of code where button["command"] is the command for a Tkinter Button which is assigned to a function self.create_analytics_dashboard(button_plots) that creates a new Tkinter frame when the button is pressed. The button plots argument is an object responsible for displaying the right plots based on the button pressed.
button["command"] = self.create_analytics_dashboard(button_plots)
There are 3 pertinent Tkinter frames to this problem:
main_dashboard
plots_dashboard
analytics_dashboard
The first 2 frames simply contain buttons that navigate to the next frame and the anaylytics_dashboard frame has buttons which displays the actual visualizations.
The expected order of these frames is as listed above however, due to the line where I assign the button["command"] aforementioned, the program skips the plots_dashboard and goes from the main_dashboard directly to the analytics_dashboard.
There are no error messages however, if I remove the parenthesis and the parameter inside it (button_plots) as shown in the line below the program will display the frames in the right order without skipping the plots_dashboard frame:
button["command"] = self.create_analytics_dashboard
Obviously, I have tried Googling it but all the results just seem to talk about the difference between invoking functions with and without parenthesis or the purposes of each. This article helps explain the difference quite succinctly:
When we call a function with parentheses, the function gets execute and returns the result to the callable.
In another case, when we call a function without parentheses, a function reference is sent to the callable rather than executing the function itself.
So, since the function works as intended when I don't use parenthesis i.e. send a function reference to the callable rather than executing the function itself, I tried using a partial function from the functools library to see if it would work:
from functools import partial
...
button_command = partial(self.create_analytics_dashboard)
button["command"] = button_command(button_plots)
Unfortunately, nothing changed. The plots_dashboard frame was still skipped as before. I need a way to pass button_plots as an argument so it can be used in the self.create_analytics_dashboard function without using parenthesis since this will just execute the function rather than sending a function reference to the callable.
If there is another way of passing a variable from one function in a class to another function in the same class in Python then that could work as well. I simply need the button_plots variable to be available in the self.create_analytics_dashboard function one way or another.
Apologies for the long post but this has been bothering me for a long time so I tried to give as much as detail as possible. If anything in the question does not make sense please let me know. Any help is much appreciated.
Thanks
To use functools.partial you must include the arguments when you create the partial function:
button_command = partial(self.create_analytics_dashboard, button_plots)
button["command"] = button_command
Or, more concisely:
button["command"] = partial(self.create_analytics_dashboard, button_plots)

Using a callback as an argument

I have a bit of trouble using a callback as an argument to a function. I am using a python package called keyboard which has a function called keyboard.add_word_listener() with required arguments of text and callback. The text is simply the word the function is looking for, and according to the docs, the callback is "is an argument-less function to be invoked each time the given word is typed." I have been passing a function through that argument that is essentially just printing things. To the best of my knowledge, this should run all of the code within the function whenever it detects the text is typed. However, it immediately runs the function, before I type anything, and when I actually do type the text, it gives the error 'NoneType' object is not callable. If anyone could tell me why this isn't working the way it should, that would be great. Minimum reproducible example:
import keyboard
stopKey = "Windows"
def test():
print("Success!")
keyboard.add_word_listener("test", test())
running = True
while running:
if keyboard.is_pressed(stopKey):
running = False
As you can see, when you run the program, it immediately prints "Success!" and if you type "test" + space anywhere, it gives an error message.
dos not use parenthesis to pass the fucntion, you are "calling" it
keyboard.add_word_listener("test", test) # no parenths

pywinauto find_elements() returns ElementNotFoundError

I'm trying to automate a simple app using pywinauto & Python 3.6. The app has a Windows "Open" dialog like this and I want to click on the "Cancel" button:
I used SWAPY to get the class_name and control_id attributes for the button.
Now the problem is that when I call the find_element() method with these parameters it raises an ElementNotFoundError. Here's my code:
cancel_button = pywinauto.findwindows.find_element(class_name="button", control_id=2)
I've tried (class_name="button", control_id="2"), (class_name="Button", control_id=2) but they all give the same error. The same problem occurs for any other element I try to find on this dialog.
So how do I use the attributes read from SWAPY? I didn't find the official pywinauto documentation to be very useful. It doesn't explain a lot of things clearly.
EDIT: I decided not to use the find_elements method and instead used find_windows() to get a handle to the Open dialog.
w_open_handle = pywinauto.findwindows.find_windows(title=u'Open', class_name='#32770')[0]
I then get a WindowSpecification object using this handle:
w_open = app.window_(handle=w_open_handle)
I then call:
w_open['Cancel'].click()
and this works. Now I want to enter a file name in the "File name:" edit box and click on Open button to open that file. So I do this:
w_open['File name:'].type_keys("abc.txt")
This works. I printed out the control identifiers using print_control_identifiers() and got the name for Open button. So using draw_outline() I draw a boundary outside it and it shows the correct button.
w_open['SplitButton6'].draw_outline()
But calling .click() method on 'SplitButton6' throws a WindowSpecification class has no 'click' method error. Any idea what's causing this? The error seems to be misleading since WindowSpec class does have a .click method.
Correct answer is that you missed top_level_only=False (it's True by default because higher level API calls it at least twice). Then you may have 2 controls matching this criterion (maybe from different applications). find_element is a low level function. I wouldn't recommend its direct usage (the code is too long, there are many pitfalls that were taken into account on a higher level API).
>>> pywinauto.findwindows.find_element(class_name="Button", control_id=2, top_level_only=False)
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
File "...\pywinauto\findwindows.py", line 98, in find_element
raise exception
ElementAmbiguousError: There are 2 elements that match the criteria {'class_name': 'Button', 'control_id': 2, 'top_level_only': False}
>>> pywinauto.findwindows.find_element(class_name="Button", title='Cancel', top_level_only=False)
<win32_element_info.HwndElementInfo - 'Cancel', Button, 395554>
Using higher level API (Application object and WindowSpecifications described in the Guide) you shouldn't care about passing process id, backend name and other things to find_element every time.
P.S. In my mind SWAPY could be significantly improved, but it's not maintained last year. I hope to re-write it in the future with smaller code base and MS UI Automation support. But currently fully automatic script generator is a higher priority.
EDIT:
This button w_open['SplitButton6'].draw_outline() could be detected as general HwndWrapper object instead of ButtonWrapper. You can check it using this:
w_open['SplitButton6'].wrapper_object()
And this is what exactly written in the Getting Started Guide (which you said you've read).
Fortunately you can use method .click_input() for any control:
w_open['SplitButton6'].click_input()
I can say more: WindowSpecification does NOT have click method. It's a method of ButtonWrapper which is instantiated dynamically. For example these statements work the same way (but Python can hide .wrapper_object() call):
w_open['SplitButton6'].wrapper_object().click_input()
w_open['SplitButton6'].click_input()
And again this is all described in the Getting Started Guide. Please read the whole guide. You will find many useful high level things. I can advise for some corner cases if something is still not clear.

Python: internally defined open() causes "Type Error", "argument requires no arguments, 1 given"

This is quite likely something simple that I have an issue with, but I do not have another machine to figure out if it's my laptop's python version right now.
When I run my program, I recieve the following error: "Type Error: function open() requires no arguments (2 given)"
The code snippet in question:
import tkinter as tk
from tkinter import filedialog as fdg
def update_queue():
conf_file = open("config.txt", "a")
fd = fdg.LoadFileDialog(master)
file = fd.go(pattern="*.jpg")
conf_file.write(file)
conf_file.close()
I'm not yet too good with Python, and would appreciate any pointers ("Your code looks twistier than last night's burnt spaghetti" counts as well) as to why the open() function fails.
Also of note, if I call open outside of a defined function, it opens the file, and can complete all actions done on it, but if I close the file, I cannot re open the file from within a function. I attempted to use the os file functions, but recieved the error "LoadFileDialog does not work with buffer-defined file functions." Understandable.
If I use conf_file.flush(), assuming I opened it outside of a function, will it flush whatever I write/append, so that I can read from the file again later?
EDIT: What I mean, is, will this work all the time, or would this be considered a hack?
is that the whole code? make sure you did not import another open function somewhere. or redefined it.
Assuming that open() was declared later on and you just didn't include it in the code, you probably declared it as
def open():
#code here
If this is the case, you just didn't add in the arguments when declaring the function and it should be:
def open(x, y):
#code here
where x and y could be anything you like.
Please come back and post the rest of your code (i highly doubt this is all of it) to get better answers. What is truly going on is at most a speculation on out part.

Categories

Resources