【问题标题】:Workaround for equality of nested functions嵌套函数相等的解决方法
【发布时间】:2014-02-07 07:08:49
【问题描述】:

我有一个嵌套函数,在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__ 方法(或其他一些笨蛋),以便相同的嵌套函数相等?

(另一种解决方法是自己存储对函数的引用,但这是重复 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

【问题讨论】:

  • “相同的函数”是指相同的代码(如,均由get_stop_function 创建)、相同的代码,还是具有相同字节码字符串的不同代码?相同或相等的闭合?等等……
  • 你实际上可以覆盖内部函数的__eq__。在 Python 3.x 中,function 是可变的。问题是它不会有任何好处。 Python 3.x 指定允许__eq__ 查找忽略实例的方法并直接进入类,并且至少 CPython 3.0-3.4 和 PyPy3 2.1(它们是唯一存在的 3.x 实现)正是这样做的。
  • 另外,对于您的编辑:我很确定如果函数相等且绑定对象相等,则方法相等,这意味着方法实际上与嵌套函数具有相同的问题 - 函数仍然需要比较相等。只是通常您不会自己构建绑定方法,它们是由类的描述符创建的,在这种情况下,它们的功能将始终相同。 (我觉得我解释得不是很好……)
  • 感谢@abarnert,内容丰富。

标签: python python-3.x nested pyglet


【解决方案1】:

您可以概括 Bi Rico 的解决方案,以允许使用某些特定的相等函数非常容易地包装任何函数。

第一个问题是定义相等函数应该检查的内容。我猜在这种情况下,您希望代码相同(这意味着从同一 def 语句创建的函数将相等,但从 def 语句的逐字符副本创建的两个函数不会) ,并且闭包是相等的(这意味着如果你用两个相等但不相同的stop_keys 调用get_stop_function,那么函数将是相等的),没有其他相关的。但这只是猜测,还有很多其他的可能性。

然后,您只需像包装任何其他类型的对象一样包装一个函数;只需确保 __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

【讨论】:

    【解决方案2】:

    您可以为停止函数创建一个类并定义自己的比较方法。

    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
    

    【讨论】:

    • 如果 pyglet 内部比较是否相等或身份,我不放心。这可能不适用于后一种情况。不过,将嵌套函数转换为类可以提高可读性 - 并且可以通过覆盖 __new__ 来确保为每个 stop_key 创建一个对象。
    • @jsbueno:根据问题它使用平等。
    • @jsbueno 这个问题明确询问如何覆盖函数的比较方法,尽管记忆函数也应该在这里工作并且可能有其他好处。
    • @BiRico 还有什么好处?
    • 记忆化的主要好处通常是在涉及大量计算时的性能,但可能会出现一些小问题。例如,您可以将函数用作字典中的键。为了使用StopFunction 的实例作为字典的键,该类需要定义一个__hash__ 方法。此外,正如@jsbueno 所提到的,这两种方法并不冲突,如果存在性能问题,您可以记住该类的实例。
    【解决方案3】:

    解决方案是保留一个包含生成函数的字典, 这样当您进行第二次调用时,您会得到与第一次调用相同的对象。

    也就是说,只需构建一些记忆逻辑,或使用其中一个库 与记忆装饰器一起存在:

    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
    

    【讨论】:

    • 我确实在问题中声明我不想保存对该函数的引用,尽管这比我想象的更优雅......我确实喜欢这个解决方案,但我认为我还是更喜欢@BiRico's。
    猜你喜欢
    • 2019-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-12
    • 1970-01-01
    • 2018-03-08
    • 1970-01-01
    相关资源
    最近更新 更多