Search code examples
pythonsingle-dispatch

Unregister for singledispatch?


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

Solution

  • 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'