我有一个嵌套函数,我在pyglet
中使用它作为回调:
def get_stop_function(stop_key):
def stop_on_key(symbol, _):
if symbol == getattr(pyglet.window.key, stop_key):
pyglet.app.exit()
return stop_on_key
pyglet.window.set_handler('on_key_press', get_stop_function('ENTER'))
但是当我需要再次引用嵌套函数时,我会遇到问题:
pyglet.window.remove_handler('on_key_press', get_stop_function('ENTER'))
由于python处理函数的方式,这不起作用:
my_stop_function = get_stop_function('ENTER')
my_stop_function is get_stop_function('ENTER') # False
my_stop_function == get_stop_function('ENTER') # False
感谢两位similar questions我明白发生了什么,但我不确定解决方法是针对我的情况。我正在查看pyglet source code,看起来pyglet使用相等来查找要删除的处理程序。
所以我的最后一个问题是:如何覆盖内部函数的__eq__
方法(或其他一些dunder),以便相同的嵌套函数相等?
(另一个解决方法是自己存储对该函数的引用,但这会复制pyglet的工作,会因许多回调而变得混乱,反正我对这个问题很好奇!)
编辑:实际上,在我上面链接的问题中,它解释了方法具有值相等但不具有引用相等性。使用嵌套函数,您甚至无法获得值相等,这就是我所需要的。
Edit2:我可能接受Bi Rico的回答,但是有人知道为什么以下不起作用:
def get_stop_function(stop_key):
def stop_on_key(symbol, _):
if symbol == getattr(pyglet.window.key, stop_key):
pyglet.app.exit()
stop_on_key.__name__ = '__stop_on_' + stop_key + '__'
stop_on_key.__eq__ = lambda x: x.__name__ == '__stop_on_' + stop_key + '__'
return stop_on_key
get_stop_function('ENTER') == get_stop_function('ENTER') # False
get_stop_function('ENTER').__eq__(get_stop_function('ENTER')) # True
答案 0 :(得分:3)
解决方案是保留包含所生成函数的字典, 因此,当您进行第二次调用时,您将获得与第一次调用中相同的对象。
即,只需构建一些memoization逻辑,或使用其中一个库 与memoizing装饰器一起存在:
ALL_FUNCTIONS = {}
def get_stop_function(stop_key):
if not stop_key in ALL_FUNCTIONS:
def stop_on_key(symbol, _):
if symbol == getattr(pyglet.window.key, stop_key):
pyglet.app.exit()
ALL_FUNCTIONS[stop_key] = stop_on_key
else:
stop_on_key = ALL_FUNCTIONS[stop_key]
return stop_on_key
答案 1 :(得分:3)
您可以为停止功能创建一个类,并定义自己的比较方法。
class StopFunction(object):
def __init__(self, stop_key):
self.stop_key = stop_key
def __call__(self, symbol, _):
if symbol == getattr(pyglet.window.key, self.stop_key):
pyglet.app.exit()
def __eq__(self, other):
try:
return self.stop_key == other.stop_key
except AttributeError:
return False
StopFunciton('ENTER') == StopFunciton('ENTER')
# True
StopFunciton('ENTER') == StopFunciton('FOO')
# False
答案 2 :(得分:2)
你可以概括化Bi Rico的解决方案,以便很容易地用任何特定的相等函数包装任何函数。
第一个问题是定义相等函数应检查的内容。我猜测这种情况,你希望代码是相同的(意味着从相同的def
语句创建的函数将是相同的,但是两个函数是从def
的字符到字符副本创建的语句不会),并且闭包是相等的(意味着如果你用两个相等但不相同的get_stop_function
调用stop_key
,函数将是相等的),并且没有任何其他相关内容。但这只是猜测,还有很多其他的可能性。
然后你只需要包装一个函数,就像你包装任何其他类型的对象一样;只需确保__call__
是您委派的内容之一:
class EqualFunction(object):
def __init__(self, f):
self.f = f
def __eq__(self, other):
return (self.__code__ == other.__code__ and
all(x.cell_contents == y.cell_contents
for x, y in zip(self.__closure__, other.__closure__)))
def __getattr__(self, attr):
return getattr(self.f, attr)
def __call__(self, *args, **kwargs):
return self.f(*args, **kwargs)
如果你想支持不需要通过getattr
的其他dunder方法(我不认为它们对函数至关重要,但我可能错了......),要么明确地执行它(与__call__
一样)或循环遍历它们并为每个类型添加一个通用包装。
使用包装器:
def make_f(i):
def f():
return i
return EqualFunction(f)
f1 = f(0)
f2 = f(0.0)
assert f1 == f2
或者,请注意EqualFunction
实际上是作为装饰器工作,这可能更具可读性。
所以,对于你的代码:
def get_stop_function(stop_key):
@EqualFunction
def stop_on_key(symbol, _):
if symbol == getattr(pyglet.window.key, stop_key):
pyglet.app.exit()
return stop_on_key