In Python turtle, if I want to pass an event handler arguments that differ from what the event system specifies, I can use a lambda to bridge the difference:
from turtle import Screen, Turtle
from functools import partial
def change_color(color, x=None, y=None):
screen.bgcolor(color)
screen = Screen()
screen.onclick(lambda x, y: change_color('blue'))
screen.mainloop()
Or I can use the partial function imported from functools to replace the lambda with:
screen.onclick(partial(change_color, 'blue'))
And that works fine. Returning to our original program, we can replace our onclick() event with an ontimer() event, updating our lambda, and everything works fine:
screen.ontimer(lambda: change_color('blue'), 1000)
But, when we replace this lambda with a partial:
screen.ontimer(partial(change_color, 'blue'), 1000)
It fails immediately (not when the timer would have fired) with:
Traceback (most recent call last):
File "test.py", line 9, in <module>
screen.ontimer(partial(change_color, 'blue'), 1000)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/turtle.py", line 1459, in ontimer
self._ontimer(fun, t)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/turtle.py", line 718, in _ontimer
self.cv.after(t, fun)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter/__init__.py", line 755, in after
callit.__name__ = func.__name__
AttributeError: 'functools.partial' object has no attribute '__name__'
>
Since turtle sits atop tkinter, and tkinter is implicated in the stack trace, we can go down a level:
import tkinter as tk
from functools import partial
def change_color(color):
root.configure(bg=color)
root = tk.Tk()
root.after(1000, change_color, 'blue')
root.mainloop()
Which works fine. We can also do:
root.after(1000, lambda: change_color('blue'))
Which works fine. But when we do:
root.after(1000, partial(change_color, 'blue'))
it again immediately fails with:
Traceback (most recent call last):
File "test.py", line 9, in <module>
root.after(1000, partial(change_color, 'blue'))
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter/__init__.py", line 755, in after
callit.__name__ = func.__name__
AttributeError: 'functools.partial' object has no attribute '__name__'
>
The functools documentation for partial states its return value will behave like a function but clearly it's different, if not lacking, somehow. Why is that? And why does tkinter/turtle accept partial functions as click event handers, but not as timer event handlers?
Why is that?
It's designed this way.
By default, partial stores the packed function without attributes, however they are still available:
partial_func = partial(change_color, 'blue')
print(partial_func.func.__name__)
You should use update_wrapper function (or a decorator counterpart) to explicitly set correct options:
from turtle import Screen, Turtle
from functools import partial, update_wrapper
def change_color(color, x=None, y=None):
screen.bgcolor(color)
def partial_change_color(color):
partial_f = partial(change_color, color)
update_wrapper(partial_f, change_color)
return partial_f
screen = Screen()
screen.ontimer(partial_change_color('blue'), 1000)
screen.mainloop()
And why does tkinter/turtle accept partial functions as click event handers, but not as timer event handlers?
Again, it's designed this way.
Because binding and scheduling algorithms are slightly different in the tkinter wrapper, which can be observed if you track down your error.
tkinter creates additional wrapper callit, which handles unscheduling of a target function using __name__ (hence, AttributeError), while the binding does not have such an algorithm for implicit unbinding.
From here, it looks like functools.partial does not copy the __module__ and __name__ attributes from the inner function. You can work around it by defining __name__ manually:
import tkinter as tk
from functools import partial
def change_color(color):
root.configure(bg=color)
root = tk.Tk()
c = partial(change_color, 'blue')
c.__name__ = "c"
root.after(1000, c)
root.mainloop()
Related
After Executing This code i am getting error win not found running tkinter from different function is important as its a homework
Code:
import tkinter as tk
from tkinter import *
def zui(kaj,saj):
zun=kaj
kaj=kaj+"=tk.Tk()"
exec(kaj)
saj=zun+".title('"+saj+"')"
exec(saj)
def zabel(self,naj,iaj,oaj,baj,gaj,taj):
spsp=self+"="+"Label("+naj+", text='"+iaj+"', bg='"+oaj+"', height="+gaj+", width="+taj+",fg='"+baj+"')"
spsp=str(spsp)
exec(spsp)
def zosition(qak,iak,nak):
sspp=qak+".grid(row="+iak+", column="+nak+")"
exec(sspp)
def zainzoop(tak):
sft=tak+".mainloop()"
exec(sft)
zui("win","zahid app")
zabel("label","win","hello world","white","black","4","10")
zosition("win","1","1")
zainzoop("win")
Traceback:
Traceback (most recent call last):
File "c:/PyProjects/Patient Data Entry/hello.py", line 20, in <module>
zabel("label","win","hello world","white","black","4","10")
File "c:/PyProjects/Patient Data Entry/hello.py", line 12, in zabel
exec(spsp)
File "<string>", line 1, in <module>
NameError: name 'win' is not defined
This is a very weird way on using tkinter, anyway who am I to judge. exec() takes a globals() argument to make the declaration global. This is how the exec should look like:
exec(spsp,globals())
exec(sspp,globals())
exec(sft,globals())
and then in the end, zosition() should be called like:
zosition("label","1","1")
Just in case your curious on how it would normally look like:
import tkinter as tk
kaj = tk.Tk()
kaj.title('zahid app')
spsp = tk.Label(kaj,text='Hello World',bg='white',fg='black',height=4,width=10)
spsp.grid(row=1,column=1)
kaj.mainloop() #much easier right?
I'm trying to create a simple Gui with tkinter using classes.
But I don't really understand how to make the for-loop work inside the count method, could anyone tell me where should I add the missing argument?
from tkinter import *
import time
class App:
def __init__(self, master):
self.container1 = Frame(master)
self.container1.pack()
self.button1 = Button(self.container1, text="count")
self.button1.bind("<Button-1>", self.count)
self.button1.pack()
def count(self):
for i in range(100):
self.button1["text"] = str(i)
time.sleep(1)
root = Tk()
Myapp = App(root)
root.mainloop()
The error is:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.5/tkinter/__init__.py", line 1553, in __call__
return self.func(*args)
TypeError: count() takes 1 positional argument but 2 were given
When you bind an event, a positional argument event is provided to the callback function.
Change your count method to this:
def count(self, event):
You will also need to get rid of time.sleep(1) since .sleep() is a blocking call, which means that it will block the tkinter mainloop which will cause your program to not respond.
I'm trying to write a GUI for my code. My plan is to use tkinter's StringVar, DoubleVar, etc. to monitor my input in real time. So I found out the DoubleVar.trace('w', callback) function. However, every time I make the change I get an exception:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\Anaconda2\lib\lib-tk\Tkinter.py", line 1542, in __call__
return self.func(*args)
TypeError: 'NoneType' object is not callable
I have no idea what's going wrong. I'm using python 2.7
My code is as follows:
from Tkinter import *
class test(Frame):
def __init__(self,master):
Frame.__init__(self,master=None)
self.main_frame = Frame(master);
self.main_frame.pack()
self.testvar = DoubleVar()
self.slider_testvar = Scale(self.main_frame,variable = self.testvar,from_ = 0.2, to = 900, resolution = 0.1, orient=HORIZONTAL,length = 300)
self.slider_testvar.grid(row = 0, column = 0, columnspan = 5)
self.testvar.trace('w',self.testfun())
def testfun(self):
print(self.testvar.get())
root = Tk()
root.geometry("1024x768")
app = test(master = root)
root.mainloop()
Consider this line of code:
self.testvar.trace('w',self.testfun())
This is exactly the same as this:
result = self.testfun()
self.testvar.trace('w', result)
Since the function returns None, the trace is going to try to call None, and thus you get 'NoneType' object is not callable
The trace method requires a callable. That is, a reference to a function. You need to change that line to be the following (notice the missing () at the end):
self.testvar.trace('w',self.testfun)
Also, you need to modify testfun to take arguments that are automatically passed by the tracing mechanism. For more information see What are the arguments to Tkinter variable trace method callbacks?
I have an application that has a entry field and a button:
from subprocess import *
from Tkinter import *
def remoteFunc(hostname):
command = 'mstsc -v {}'.format(hostname)
runCommand = call(command, shell = True)
return
app = Tk()
app.title('My App')
app.geometry('200x50+200+50')
remoteEntry = Entry(app)
remoteEntry.grid()
remoteCommand = lambda x: remoteFunc(remoteEntry.get()) #First Option
remoteCommand = lambda: remoteFunc(remoteEntry.get()) #Second Option
remoteButton = Button(app, text = 'Remote', command = remoteCommand)
remoteButton.grid()
app.bind('<Return>', remoteCommand)
app.mainloop()
and I want that when I insert an ip/computer name to the entry field it will sent it as a parameter to the command of the button, so when I press Return or pressing the button it will remote the computer with that name/ip.
When i execute this code with the first option (look at the code) it works only I press the Return key, and if I press the button this is the error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1532, in __call__
return self.func(*args)
TypeError: <lambda>() takes exactly 1 argument (0 given)
If I try the second option of remoteCommand only if I try to press the button It work but I if press the Return key i get this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1532, in __call__
return self.func(*args)
TypeError: <lambda>() takes no arguments (1 given)
The only difference between the two is if lambda gets an argument or not.
The best solution in my opinion is to not use lambda. IMO, lambda should be avoided unless it really is the best solution to a problem, such as when a closure needs to be created.
Since you want the same function to be called from a binding on the return key, and from the click of a button, write a function that optionally accepts an event, and then simply ignores it:
For example:
def runRemoteFunc(event=None):
hostname = remoteEntry.get()
remoteFunc(hostname)
...
remoteButton = Button(..., command = remoteFunc)
...
app.bind('<Return>', remoteCommand)
Commands do not get arguments. Event handlers get an event as an argument. To have a function serve as both, use a default argument.
def remote(event=None):
remoteFunc(remoteEntry.get())
In all the tutorials on the web I've seen with Pyglet, it doesnt seems that any of them have used classes to contain the pyglet.window.Window instance. For example, most tutorials seem to go something like
import pyglet
game_window = pyglet.window.Window()
#game_window.event
def on_draw():
#dostuff
while __name__ == "__main__":
pyglet.app.run()
I'm having trouble restructuring this code into a class. My code which is intended to do so, is here:
import pyglet
from pyglet.gl import *
from Board import Board
class Frontend:
def __init__(self,xs, ys):
self.GameInstance = Board(xs,ys)
self.GameWindow = pyglet.window.Window(width=512, height=512,visible=False)
#GameWindow.event
def on_draw(self):
self.GameWindow.clear()
f = Frontend()
When I run this code, I get the following error:
Traceback (most recent call last):
File "C:/Users/PycharmProjects/Nothing/2048/Frontend.py", line 7, in <module>
class Frontend:
File "C:/Users/PycharmProjects/Nothing/2048/Frontend.py", line 13, in Frontend
#GameWindow.event
NameError: name 'GameWindow' is not defined
When I replace #GameWindow.event with #self.GameWindow.event in an attempt to resolve the NameError I get:
Traceback (most recent call last):
File "C:/Users/PycharmProjects/Nothing/2048/Frontend.py", line 7, in <module>
class Frontend:
File "C:/Users/PycharmProjects/Nothing/2048/Frontend.py", line 13, in Frontend
#self.GameWindow.event
NameError: name 'self' is not defined
Which i expect. However, I'm not sure why this code isnt working - can someone explain why and how to fix it?
You can inherit from Window.
This should work:
class Frontend(pyglet.window.Window):
def __init__(self, xs, ys):
self.GameInstance = Board(xs,ys)
super().__init__(width=512, height=512,visible=False)
def on_draw(self):
self.clear()
Your code is not working because you can not reference the instance of the Frontend class outside the methods. If you will have only one instance of the Frontend class you can do something like:
class Frontend:
window = pyglet.window.Window()
def __init__(self):
...
#window.event
def on_draw():
...
As I've commented on ragezor's answer, the pyglet docs recommend inheritance.
But another option may be to separate the event handling logic in its own class, as an EventDispatcher:
http://pyglet.org/doc-current/programming_guide/events.html#creating-your-own-event-dispatcher
Personally, if I knew would only have one Frontend instance, I would question the necessity of having a class. But that's a whole can of worms. Thought I'd give you another nice option, at least, you can't go wrong inheriting from Window though, especially if all your events are Window-related.
A third idea (fourth I guess since ragezor gave you two options):
class Frontend:
def __init__(self,xs, ys):
self.GameInstance = Board(xs,ys)
self.GameWindow = pyglet.window.Window(width=512, height=512,visible=False)
self.on_draw = self.GameWindow.event(self.on_draw)
def on_draw(self):
self.GameWindow.clear()
In other words, apply the #GameWindow.event decorator manually.
One last thing, don't use camelcase for attributes, it goes against PEP8 convention and confused me for a second while editing this code. Call it game_window instead.
My guess is that you need to do
#self.Gamewindow.event