2014-01-17 55 views
5

我有一個嵌套函數,我使用作爲回調平等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')) 

這並不是因爲蟒蛇方式對待職能的工作:

my_stop_function = get_stop_function('ENTER') 
my_stop_function is get_stop_function('ENTER') # False 
my_stop_function == get_stop_function('ENTER') # False 

感謝兩位similarquestions我明白髮生了什麼,但我不確定我的案例的解決方法。我正在查看pyglet source code,它看起來像pyglet使用平等來找到要刪除的處理程序。

所以我的最後一個問題是:如何覆蓋內部函數的__eq__方法(或其他一些dunder),以使相同的嵌套函數相等?

(另一種解決方法是將存儲功能自己的參考,但就是複製pyglet的工作,會變得混亂與許多回調以及反正我很好奇這個問題!)

編輯:實際上在上面鏈接的問題中,解釋了方法具有價值平等,但沒有引用平等。使用嵌套函數,您甚至不會獲得值相等,這是我所需要的。

EDIT2:我可能會接受畢波多黎各的答案,但沒有人知道爲什麼下面不工作:

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

是否「相同功能」的意思是相同的代碼(如,通過兩個'get_stop_function'創建),等於代碼,或以相同的字節碼串不同的代碼?相同或相等的封閉?等等...... – abarnert

+0

你實際上可以_can_覆蓋內部函數的'__eq__'。在Python 3.x中,'function'是可變的。問題是它不會有任何好處。 Python 3.x指定'__eq__'查找允許忽略實例的方法並直接進入類,並且至少CPython 3.0-3.4和PyPy3 2.1(它們是唯一存在的3.x實現)就是這樣做的。 – abarnert

+0

另外,對於你的編輯:我敢肯定,如果函數是相等的,並且綁定的對象是相等的,這意味着方法與嵌套函數有同樣的問題 - 函數仍然需要比較相等。只是通常你不會自己創建綁定方法,它們是由類的描述符創建的,在這種情況下,它們的函數將始終是相同的。 (我感覺我沒有很好地解釋......) – abarnert

回答

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 
+0

如果pyglet在內部比較平等或身份,我並不確定。這可能不適用於後面的情況。然而,將嵌套函數轉換爲類可以提高可讀性,並且可以通過重寫'__new__'來確保爲每個stop_key創建一個對象。 – jsbueno

+0

@jsbueno:根據它使用平等的問題。 – BrenBarn

+0

@jsbueno該問題明確詢問如何重寫函數的比較方法,但記憶函數也應該在這裏工作,並可能有其他好處。 –

3

的解決辦法是保持一個字典圍繞包含生成的功能, 這樣,當你打第二個電話,你會得到和第一個電話相同的對象。

也就是說,只需建立一些記憶化邏輯,或使用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 
+0

我在問題中沒有聲明我不想保存對函數的引用,儘管這比我想象的更優雅......我喜歡這個解決方案,但我認爲我更喜歡@ BiRico's。 – ontologist

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