嵌套函数相等的解决方法

时间:2014-01-17 19:50:17

标签: python python-3.x nested pyglet

我有一个嵌套函数,我在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

3 个答案:

答案 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