Using a callback as an argument - python

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

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)

Why are f-strings not printed in Python class methods?

I would like to understand why f-strings do not print output in class methods. I would like to use their concise syntax for making breakpoints.
MWE:
class FStringTest:
def test(self):
print('why does this work')
f'but not this'
print(FStringTest().test())
f'yet this works'
Output:
why does this work
None
yet this works
Are you running this in Jupyter or an interactive python shell?
Because if you were, then the 'P' in REPL, which stands for print (R=READ,E=EVALUATE,P=PRINT,L=LOOP), will print the yet this works automatically for you without you explicitly calling the print function.
So:
why does this work
This is what the print inside your method returns.
None
You're seeing this because you're printing the value that your test() method is returning, and since it happens that it returns nothing (no return) it gives you this 'None' value.
yet this works
This is just what the REPL echoing back to you.
Note: Save this as a python script (.py) and try running it in an IDE like VSC, or via the command line using py <script_name>.py, it will not show you that last line of output.

Python nameError help when importing from another file

I have a function in maya that imports in other functions and creates a shelf with buttons for specific functions. I have a function that has a scriptJob command that works fine. if I import that file in manually and not through the shelf button, but gives a NameError when using the shelf script to run it.
This is an example of the script
myShelf.py file:
import loopingFunction
loopingFunction.runThis()
loopingFunction.py file:
import maya.cmds as mc
def setSettings():
#have some settings set before running this
runThis()
def runThis():
print "yay this ran"
evalDeferred(mc.scriptJob(ro=True,ac=["'someMesh.outMesh',runThis"])
if I run this through the shelf function, I get a runThis nameError is not defined and if I try modifying the scriptJob command to loopingFunction.runThis, I get a nameError loopingFunction is not defined(not sure if using loopingFunction.runThis is even correct, to be honest)
Not sure how I can get around this without having to manually import in the function rather than through the shelf file.
Using string references for callback functions like this often leads to scope problems. (More on that, and why not to use strings, here)
If you pass the function directly to the callback as an object, instead of using a string, it should work correctly as along as you have actually imported the code.
in this case you want an evalDeferred -- do you actually need it? -- so it helps to add a little function around the actual code so that the scriptjob creation actually happens later -- otherwise it will get evaluated before the deferral is scheduled.
def runThis():
print "callback ran"
def do_scriptjob():
cmds.scriptJob(ro=True, ac=('someMesh.outMesh', runThis)
cmds.evalDeferred(do_scriptjob)
In both runThis and do_scriptjob I did not add the parens -- we are letting the evalDeferred and the scriptJob have the function objects to call when they are ready. Using the parens would pass the results of the functions which is not what you want here.
Incidentally it looks like you're trying to create a new copy of the scriptJob inside the scriptJob itself. It'd be better to just drop the runOnce flag and leave the scriptJob lying around -- if something in runThis actually affected the someMesh.outMesh attribute, your maya will go into an infinite loop. I did not change the structure in my example, but I would not recommend writing this kind of self-replicating code if you can possibly avoid it.
You have a problem of nested/maya scope variables
mc.scriptJob(ro=True,ac=["'someMesh.outMesh',runThis"]
This line is a maya command string that is evaluated in the main maya scope (like a global)
As your function have a namespace with the import : 'loopingFunction', you need to enforce it in the string command.
import loopingFunction
loopingFunction.runThis()
You should write
evalDeferred(mc.scriptJob(ro=True,ac=["'someMesh.outMesh',loopingFunction.runThis"])
If you want something more general, you can do :
def runThis(ns=''):
print "yay this ran"
if ns != '':
ns += '.'
evalDeferred(mc.scriptJob(ro=True,ac=["'someMesh.outMesh',{}runThis".format(ns)])
and then run in shelf :
import loopingFunction
loopingFunction.runThis('loopingFunction')
like this you can write any formof namepsaces :
import loopingFunction as loopF
loopF.runThis('loopF')

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

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)

Categories

Resources