简体   繁体   English

Python 海龟/tkinter 事件中的部分 vs lambda

[英]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. partialfunctools文档说明它的返回值将表现得像一个函数,但显然它是不同的,如果不是缺少,不知何故。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM