Python turtle/tkinter 事件中的部分 vs lambda

Partial vs lambda in Python turtle/tkinter events

在 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()

或者我可以使用从 functools 导入的 partial 函数将 lambda 替换为:

screen.onclick(partial(change_color, 'blue'))

这很有效很好。回到我们原来的程序,我们可以用 ontimer() 事件替换我们的 onclick() 事件,更新我们的 lambda,并且一切正常 fine:

screen.ontimer(lambda: change_color('blue'), 1000)

但是,当我们将 lambda 替换为 partial 时:

screen.ontimer(partial(change_color, 'blue'), 1000)

立即失败(不是在计时器启动时):

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__'
>

由于 turtle 位于 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()

效果很好很好。我们还可以:

root.after(1000, lambda: change_color('blue'))

效果很好很好。但是当我们这样做时:

root.after(1000, partial(change_color, 'blue'))

立即再次失败

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__'
>

partialfunctools 文档声明其 return 值的行为类似于 function 但显然它是不同的,如果不是缺乏,不知何故。这是为什么?为什么 tkinter/turtle 接受 partial 函数作为 click 事件处理程序,而不是 timer 事件处理程序?

here 看来,functools.partial 没有从内部函数复制 __module____name__ 属性。您可以通过手动定义 __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()

Why is that?

就是这样设计的。 默认情况下,partial stores 没有属性的打包函数,但是它们仍然可用:

partial_func = partial(change_color, 'blue')
print(partial_func.func.__name__)

您应该使用 update_wrapper function (or a decorator counterpart) 来明确设置正确的选项:

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 包装器中略有不同,can be observed if you track down your error.

tkinter 创建额外的包装器 callit,它使用 __name__(因此,AttributeError)处理目标函数的取消调度,而绑定 does not have such an algorithm for implicit unbinding .