[英]Partial vs lambda in Python turtle/tkinter events
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:在 Python turtle 中,如果我想传递与事件系统指定的不同的事件处理程序参数,我可以使用
lambda
来弥补差异:
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:或者我可以使用从functools导入的
partial
函数将lambda
替换为:
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 :回到我们原来的程序,我们可以用
ontimer()
事件替换我们的onclick()
ontimer()
事件,更新我们的lambda
,一切正常:
screen.ontimer(lambda: change_color('blue'), 1000)
But, when we replace this lambda
with a partial
:但是,当我们用
partial
替换这个lambda
时:
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:由于龟上面坐的Tkinter,和Tkinter的是在堆栈跟踪牵连,我们可以去一个级别:
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. partial
的functools文档说明它的返回值将表现得像一个函数,但显然它是不同的,如果不是缺少,不知何故。 Why is that?这是为什么? And why does tkinter/turtle accept
partial
functions as click event handers, but not as timer event handlers?为什么tkinter/turtle接受
partial
函数作为点击事件处理程序,而不接受作为计时器事件处理程序?
Why is that?
这是为什么?
It's designed this way.它是这样设计的。 By default,
partial
stores the packed function without attributes, however they are still available:默认情况下,
partial
存储没有属性的打包函数,但它们仍然可用:
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:您应该使用
update_wrapper
函数(或装饰器对应物)来明确设置正确的选项:
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?
为什么 tkinter/turtle 接受部分函数作为点击事件处理程序,而不接受作为计时器事件处理程序?
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 包装器中略有不同, 如果您跟踪错误, 可以观察到这一点。
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 . tkinter
创建另外的包装callit
,该手柄使用目标函数的unscheduling __name__
(因此, AttributeError
),而所述结合不具有用于隐式解除绑定这样的算法。
From here , it looks like functools.partial
does not copy the __module__
and __name__
attributes from the inner function.从这里,看起来
functools.partial
没有从内部函数复制__module__
和__name__
属性。 You can work around it by defining __name__
manually:您可以通过手动定义
__name__
来解决它:
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()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.