Retrieve/get 来自 TKinter 小部件的返回命令回调函数
Retrieve/get back command callback function from TKinter widget
我(出于一些精心设置的原因)试图从 tkinter 小部件中检索实际的 command
回调函数,例如为按钮设置回调 b
import tkinter as tk
root = tk.Tk()
b = tk.Button(root, text='btn', command=lambda:print('foo'))
两者
b['command']
b.cget('command')
我认为两者等同于
b.tk.call(b._w, 'cget', '-command')
只会 return 像 "2277504761920<lambda\>"
这样的字符串,而不是实际的命令功能。有没有办法得到实际的回调函数?
正在查看tkinter.__init__.py
:
class BaseWidget:
...
def _register(self, func, subst=None, needcleanup=1):
"""Return a newly created Tcl function. If this
function is called, the Python function FUNC will
be executed. An optional function SUBST can
be given which will be executed before FUNC."""
f = CallWrapper(func, subst, self).__call__
name = repr(id(f))
try:
func = func.__func__
except AttributeError:
pass
try:
name = name + func.__name__
except AttributeError:
pass
self.tk.createcommand(name, f)
if needcleanup:
if self._tclCommands is None:
self._tclCommands = []
self._tclCommands.append(name)
return name
和
class CallWrapper:
"""Internal class. Stores function to call when some user
defined Tcl function is called e.g. after an event occurred."""
def __init__(self, func, subst, widget):
"""Store FUNC, SUBST and WIDGET as members."""
self.func = func
self.subst = subst
self.widget = widget
def __call__(self, *args):
"""Apply first function SUBST to arguments, than FUNC."""
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit:
raise
except:
self.widget._report_exception()
我们得到 tkinter 将函数包装在 CallWrapper
class 中。这意味着如果我们获得所有 CallWrapper
对象,我们就可以恢复该功能。使用@hussic 的建议,猴子用更易于使用的 class 修补 CallWrapper
class,我们可以轻松获得所有 CallWrapper
对象。
这是我根据@hussic 的建议实现的解决方案:
import tkinter as tk
tk.call_wappers = [] # A list of all of the `MyCallWrapper` objects
class MyCallWrapper:
__slots__ = ("func", "subst", "__call__")
def __init__(self, func, subst, widget):
# We aren't going to use `widget` because that can take space
# and we have a memory leak problem
self.func = func
self.subst = subst
# These are the 2 lines I added:
# First one appends this object to the list defined up there
# the second one uses lambda because python can be tricky if you
# use `id(<object>.<function>)`.
tk.call_wappers.append(self)
self.__call__ = lambda *args: self.call(*args)
def call(self, *args):
"""Apply first function SUBST to arguments, than FUNC."""
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit:
raise
except:
if tk._default_root is None:
raise
else:
tk._default_root._report_exception()
tk.CallWrapper = MyCallWrapper # Monkey patch tkinter
# If we are going to monkey patch `tk.CallWrapper` why not also `tk.getcommand`?
def getcommand(name):
for call_wapper in tk.call_wappers:
candidate_name = repr(id(call_wapper.__call__))
if name.startswith(candidate_name):
return call_wapper.func
return None
tk.getcommand = getcommand
# This is the testing code:
def myfunction():
print("Hi")
root = tk.Tk()
button = tk.Button(root, text="Click me", command=myfunction)
button.pack()
commandname = button.cget("command")
# This is how we are going to get the function into our variable:
myfunction_from_button = tk.getcommand(commandname)
print(myfunction_from_button)
root.mainloop()
正如@hussic 在评论中所说,列表 (tk.call_wappers
) 仅被附加到一个问题。如果你有一个 .after
tkinter 循环,问题就会很明显,因为每次 .after
被调用时,一个对象将被添加到列表中。要解决此问题,您可能需要使用 tk.call_wappers.clear()
手动清除列表。我将其更改为使用 __slots__
功能以确保它不会占用大量 space 但这并不能解决问题。
我无法想象任何情况,我完全不确定这是否能回答您的问题,但它可能等同于您正在寻找的内容:
按钮的 invoke
方法对我来说似乎相当。所以 solution-1 将是:
import tkinter as tk
def hi():
print('hello')
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = b.invoke
#cmd = lambda :b._do('invoke')
root.mainloop()
如果这不是您要查找的内容,您可以在 tcl 级别调用该函数。 解法-2:
import tkinter as tk
def hi():
print('hello')
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = lambda :root.tk.call(b['command'])
#cmd= lambda :root.tk.eval(b['command'])
cmd()
root.mainloop()
解决方案 3,return
您的函数 invoke
:
import tkinter as tk
def hi():
print('hello')
return hi
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = b.invoke()
print(cmd) #still a string but comparable
root.mainloop()
这是一个更复杂的解决方案。它修补 Misc._register
、Misc.deletecommand
和 Misc.destroy
以从 dict
tkinterfuncs 中删除值。在这个例子中,有很多打印来检查是否在字典中添加和删除了值。
import tkinter as tk
tk.tkinterfuncs = {} # name: func
def registertkinterfunc(name, func):
"""Register name in tkinterfuncs."""
# print('registered', name, func)
tk.tkinterfuncs[name] = func
return name
def deletetkinterfunc(name):
"""Delete a registered func from tkinterfuncs."""
# some funcs ('tkerror', 'exit') are registered outside Misc._register
if name in tk.tkinterfuncs:
del tk.tkinterfuncs[name]
# print('delete', name, 'tkinterfuncs len:', len(tkinterfuncs))
def _register(self, func, subst=None, needcleanup=1):
"""Return a newly created Tcl function. If this
function is called, the Python function FUNC will
be executed. An optional function SUBST can
be given which will be executed before FUNC."""
name = original_register(self, func, subst, needcleanup)
return registertkinterfunc(name, func)
def deletecommand(self, name):
"""Internal function.
Delete the Tcl command provided in NAME."""
original_deletecommand(self, name)
deletetkinterfunc(name)
def destroy(self):
"""
Delete all Tcl commands created for
this widget in the Tcl interpreter.
"""
if self._tclCommands is not None:
for name in self._tclCommands:
# print('- Tkinter: deleted command', name)
self.tk.deletecommand(name)
deletetkinterfunc(name)
self._tclCommands = None
def getcommand(self, name):
"""
Gets the command from the name.
"""
return tk.tkinterfuncs[name]
original_register = tk.Misc.register
tk.Misc._register = tk.Misc.register = _register
original_deletecommand = tk.Misc.deletecommand
tk.Misc.deletecommand = deletecommand
tk.Misc.destroy = destroy
tk.Misc.getcommand = getcommand
if __name__ == '__main__':
def f():
root.after(500, f)
root = tk.Tk()
root.after(500, f)
but1 = tk.Button(root, text='button1', command=f)
but1.pack()
but2 = tk.Button(root, text='button2', command=f)
but2.pack()
but3 = tk.Button(root, text='button3', command=lambda: print(3))
but3.pack()
print(root.getcommand(but1['command']))
print(root.getcommand(but2['command']))
print(root.getcommand(but3['command']))
but3['command'] = f
print(root.getcommand(but3['command']))
root.mainloop()
当您将命令分配给小部件或将函数绑定到事件时,python 函数将包装在 tkinter.CallWrapper
对象中。该包装器包含对 python 函数的引用以及对小部件的引用。要获得小部件的回调,您可以迭代包装器的实例以取回原始函数。
例如,这样的方法可能有效:
import tkinter as tk
import gc
def get_callback(widget):
for obj in gc.get_objects():
if isinstance(obj, tk.CallWrapper) and obj.widget == widget:
return obj.func
return None
然后可以直接调用这个函数的return值。考虑以下代码块:
import tkinter as tk
import gc
def get_callback(widget):
for obj in gc.get_objects():
if isinstance(obj, tk.CallWrapper) and obj.widget == widget:
return obj.func
def do_something():
print(f"button1: {get_callback(button1)} type: {type(get_callback(button1))}")
print(f"button2: {get_callback(button2)} type: {type(get_callback(button2))}")
root = tk.Tk()
button1 = tk.Button(root, text="do_something", command=do_something)
button2 = tk.Button(root, text="lambda", command=lambda: do_something())
button1.pack(padx=20, pady=20)
button2.pack(padx=20, pady=20)
root.mainloop()
当我单击任一按钮时,我在控制台输出中看到了这一点,这证明 get_callback
方法 return 是一个可调用的。
button1: <function do_something at 0x103386040> type: <class 'function'>
button2: <function <lambda> at 0x103419700> type: <class 'function'>
按钮是一个可以分配属性的对象,只需在按钮外定义您的函数并将函数分配给一个属性
func_print = lambda: print("nice")
x = Button(..., command=func_print)
x.my_func = func_print
def something():
x.my_func()
something()
>>> nice
我一直在寻找同样的问题,但我找不到任何好的答案,然后我创建了我的答案,实际上这很容易
我(出于一些精心设置的原因)试图从 tkinter 小部件中检索实际的 command
回调函数,例如为按钮设置回调 b
import tkinter as tk
root = tk.Tk()
b = tk.Button(root, text='btn', command=lambda:print('foo'))
两者
b['command']
b.cget('command')
我认为两者等同于
b.tk.call(b._w, 'cget', '-command')
只会 return 像 "2277504761920<lambda\>"
这样的字符串,而不是实际的命令功能。有没有办法得到实际的回调函数?
正在查看tkinter.__init__.py
:
class BaseWidget:
...
def _register(self, func, subst=None, needcleanup=1):
"""Return a newly created Tcl function. If this
function is called, the Python function FUNC will
be executed. An optional function SUBST can
be given which will be executed before FUNC."""
f = CallWrapper(func, subst, self).__call__
name = repr(id(f))
try:
func = func.__func__
except AttributeError:
pass
try:
name = name + func.__name__
except AttributeError:
pass
self.tk.createcommand(name, f)
if needcleanup:
if self._tclCommands is None:
self._tclCommands = []
self._tclCommands.append(name)
return name
和
class CallWrapper:
"""Internal class. Stores function to call when some user
defined Tcl function is called e.g. after an event occurred."""
def __init__(self, func, subst, widget):
"""Store FUNC, SUBST and WIDGET as members."""
self.func = func
self.subst = subst
self.widget = widget
def __call__(self, *args):
"""Apply first function SUBST to arguments, than FUNC."""
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit:
raise
except:
self.widget._report_exception()
我们得到 tkinter 将函数包装在 CallWrapper
class 中。这意味着如果我们获得所有 CallWrapper
对象,我们就可以恢复该功能。使用@hussic 的建议,猴子用更易于使用的 class 修补 CallWrapper
class,我们可以轻松获得所有 CallWrapper
对象。
这是我根据@hussic 的建议实现的解决方案:
import tkinter as tk
tk.call_wappers = [] # A list of all of the `MyCallWrapper` objects
class MyCallWrapper:
__slots__ = ("func", "subst", "__call__")
def __init__(self, func, subst, widget):
# We aren't going to use `widget` because that can take space
# and we have a memory leak problem
self.func = func
self.subst = subst
# These are the 2 lines I added:
# First one appends this object to the list defined up there
# the second one uses lambda because python can be tricky if you
# use `id(<object>.<function>)`.
tk.call_wappers.append(self)
self.__call__ = lambda *args: self.call(*args)
def call(self, *args):
"""Apply first function SUBST to arguments, than FUNC."""
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit:
raise
except:
if tk._default_root is None:
raise
else:
tk._default_root._report_exception()
tk.CallWrapper = MyCallWrapper # Monkey patch tkinter
# If we are going to monkey patch `tk.CallWrapper` why not also `tk.getcommand`?
def getcommand(name):
for call_wapper in tk.call_wappers:
candidate_name = repr(id(call_wapper.__call__))
if name.startswith(candidate_name):
return call_wapper.func
return None
tk.getcommand = getcommand
# This is the testing code:
def myfunction():
print("Hi")
root = tk.Tk()
button = tk.Button(root, text="Click me", command=myfunction)
button.pack()
commandname = button.cget("command")
# This is how we are going to get the function into our variable:
myfunction_from_button = tk.getcommand(commandname)
print(myfunction_from_button)
root.mainloop()
正如@hussic 在评论中所说,列表 (tk.call_wappers
) 仅被附加到一个问题。如果你有一个 .after
tkinter 循环,问题就会很明显,因为每次 .after
被调用时,一个对象将被添加到列表中。要解决此问题,您可能需要使用 tk.call_wappers.clear()
手动清除列表。我将其更改为使用 __slots__
功能以确保它不会占用大量 space 但这并不能解决问题。
我无法想象任何情况,我完全不确定这是否能回答您的问题,但它可能等同于您正在寻找的内容:
按钮的 invoke
方法对我来说似乎相当。所以 solution-1 将是:
import tkinter as tk
def hi():
print('hello')
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = b.invoke
#cmd = lambda :b._do('invoke')
root.mainloop()
如果这不是您要查找的内容,您可以在 tcl 级别调用该函数。 解法-2:
import tkinter as tk
def hi():
print('hello')
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = lambda :root.tk.call(b['command'])
#cmd= lambda :root.tk.eval(b['command'])
cmd()
root.mainloop()
解决方案 3,return
您的函数 invoke
:
import tkinter as tk
def hi():
print('hello')
return hi
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = b.invoke()
print(cmd) #still a string but comparable
root.mainloop()
这是一个更复杂的解决方案。它修补 Misc._register
、Misc.deletecommand
和 Misc.destroy
以从 dict
tkinterfuncs 中删除值。在这个例子中,有很多打印来检查是否在字典中添加和删除了值。
import tkinter as tk
tk.tkinterfuncs = {} # name: func
def registertkinterfunc(name, func):
"""Register name in tkinterfuncs."""
# print('registered', name, func)
tk.tkinterfuncs[name] = func
return name
def deletetkinterfunc(name):
"""Delete a registered func from tkinterfuncs."""
# some funcs ('tkerror', 'exit') are registered outside Misc._register
if name in tk.tkinterfuncs:
del tk.tkinterfuncs[name]
# print('delete', name, 'tkinterfuncs len:', len(tkinterfuncs))
def _register(self, func, subst=None, needcleanup=1):
"""Return a newly created Tcl function. If this
function is called, the Python function FUNC will
be executed. An optional function SUBST can
be given which will be executed before FUNC."""
name = original_register(self, func, subst, needcleanup)
return registertkinterfunc(name, func)
def deletecommand(self, name):
"""Internal function.
Delete the Tcl command provided in NAME."""
original_deletecommand(self, name)
deletetkinterfunc(name)
def destroy(self):
"""
Delete all Tcl commands created for
this widget in the Tcl interpreter.
"""
if self._tclCommands is not None:
for name in self._tclCommands:
# print('- Tkinter: deleted command', name)
self.tk.deletecommand(name)
deletetkinterfunc(name)
self._tclCommands = None
def getcommand(self, name):
"""
Gets the command from the name.
"""
return tk.tkinterfuncs[name]
original_register = tk.Misc.register
tk.Misc._register = tk.Misc.register = _register
original_deletecommand = tk.Misc.deletecommand
tk.Misc.deletecommand = deletecommand
tk.Misc.destroy = destroy
tk.Misc.getcommand = getcommand
if __name__ == '__main__':
def f():
root.after(500, f)
root = tk.Tk()
root.after(500, f)
but1 = tk.Button(root, text='button1', command=f)
but1.pack()
but2 = tk.Button(root, text='button2', command=f)
but2.pack()
but3 = tk.Button(root, text='button3', command=lambda: print(3))
but3.pack()
print(root.getcommand(but1['command']))
print(root.getcommand(but2['command']))
print(root.getcommand(but3['command']))
but3['command'] = f
print(root.getcommand(but3['command']))
root.mainloop()
当您将命令分配给小部件或将函数绑定到事件时,python 函数将包装在 tkinter.CallWrapper
对象中。该包装器包含对 python 函数的引用以及对小部件的引用。要获得小部件的回调,您可以迭代包装器的实例以取回原始函数。
例如,这样的方法可能有效:
import tkinter as tk
import gc
def get_callback(widget):
for obj in gc.get_objects():
if isinstance(obj, tk.CallWrapper) and obj.widget == widget:
return obj.func
return None
然后可以直接调用这个函数的return值。考虑以下代码块:
import tkinter as tk
import gc
def get_callback(widget):
for obj in gc.get_objects():
if isinstance(obj, tk.CallWrapper) and obj.widget == widget:
return obj.func
def do_something():
print(f"button1: {get_callback(button1)} type: {type(get_callback(button1))}")
print(f"button2: {get_callback(button2)} type: {type(get_callback(button2))}")
root = tk.Tk()
button1 = tk.Button(root, text="do_something", command=do_something)
button2 = tk.Button(root, text="lambda", command=lambda: do_something())
button1.pack(padx=20, pady=20)
button2.pack(padx=20, pady=20)
root.mainloop()
当我单击任一按钮时,我在控制台输出中看到了这一点,这证明 get_callback
方法 return 是一个可调用的。
button1: <function do_something at 0x103386040> type: <class 'function'>
button2: <function <lambda> at 0x103419700> type: <class 'function'>
按钮是一个可以分配属性的对象,只需在按钮外定义您的函数并将函数分配给一个属性
func_print = lambda: print("nice")
x = Button(..., command=func_print)
x.my_func = func_print
def something():
x.my_func()
something()
>>> nice
我一直在寻找同样的问题,但我找不到任何好的答案,然后我创建了我的答案,实际上这很容易