7

Is there way to "unregister" a registered function for a generic ?

For example:

from functools import singledispatch

@singledispatch
def foo(x):
    return 'default function'

foo.register(int, lambda x: 'function for int')

# later I would like to revert this.

foo.unregister(int) # does not exist - this is the functionality I am after
lgautier
  • 11,363
  • 29
  • 42
  • It's probably worth pointing out that although Martijn's answer proves you *can*, in fact, do this, you probably *shouldn't*: the designers of singledispatch made a [conscious decision](http://permalink.gmane.org/gmane.comp.python.devel/139709) to make it difficult by exposing a [`mappingproxy`](https://docs.python.org/3/library/types.html#types.MappingProxyType) rather than a regular dictionary. – Zero Piraeus Sep 20 '14 at 18:30
  • @ZeroPiraeus It is not apparent to me that unregistering a function is precisely what they were trying to prevent. It seems like like reflection from the registered function (the functions changing the generic). – lgautier Sep 20 '14 at 20:30

1 Answers1

8

singledispatch is meant to be append only; you cannot really unregister anything.

But as with all things Python, the implementation can be forced to unregister. The following function will add a unregister() method to a singledispatch function:

def add_unregister(func):
    # build a dictionary mapping names to closure cells
    closure = dict(zip(func.register.__code__.co_freevars, 
                       func.register.__closure__))
    registry = closure['registry'].cell_contents
    dispatch_cache = closure['dispatch_cache'].cell_contents
    def unregister(cls):
        del registry[cls]
        dispatch_cache.clear()
    func.unregister = unregister
    return func

This reaches into the closure of the singledispatch.register() function to access the actual registry dictionary so we can remove an existing class that was registered. I also clear the dispatch_cache weak reference dictionary to prevent it from stepping in.

You can use this as a decorator:

@add_unregister
@singledispatch
def foo(x):
    return 'default function'

Demo:

>>> @add_unregister
... @singledispatch
... def foo(x):
...     return 'default function'
... 
>>> foo.register(int, lambda x: 'function for int')
<function <lambda> at 0x10bed6400>
>>> foo.registry
mappingproxy({<class 'object'>: <function foo at 0x10bed6510>, <class 'int'>: <function <lambda> at 0x10bed6400>})
>>> foo(1)
'function for int'
>>> foo.unregister(int)
>>> foo.registry
mappingproxy({<class 'object'>: <function foo at 0x10bed6510>})
>>> foo(1)
'default function'
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Thanks. I am surprised that this feature is not part of singledispatch. – lgautier Sep 20 '14 at 18:10
  • 1
    @lgautier: the perceived use-case for this is that you construct this *once*, not alter dispatch rules as you go along. – Martijn Pieters Sep 20 '14 at 18:20
  • 1
    @martjin-pieters: Since Python is dynamic it seems unnecessary limiting to not allow it. Technically, dispatch rules _are_ altered as one goes along (as the Python code is evaluated, and as new functions for the generic are registered). Anyway, this is more likely a discussion for python-ideas, or possibly python-dev. Thanks again for stopping by with an answer. – lgautier Sep 20 '14 at 20:20