Search code examples
pythondecoratorpython-decorators

Decorator with parameters


Can you explain me how the following decorator works:

def set_ev_cls(ev_cls, dispatchers=None):
    def _set_ev_cls_dec(handler):
        if 'callers' not in dir(handler):
            handler.callers = {}
        for e in _listify(ev_cls):
            handler.callers[e] = _Caller(_listify(dispatchers), e.__module__)
        return handler
    return _set_ev_cls_dec


@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def _switch_features_handler(self, ev):
    datapath = ev.msg.datapath
    ....

Please, don't go into details on what's going on inside the function. I'm interested in how the decorator with parameters wrap methods here. By the way, it's a code snippet from Ryu (event registration mechanism).

Thank you in advance


Solution

  • First, a decorator is just a function that gets called with a function. In particular, the following are (almost) the same thing:

    @spam
    def eggs(arg): pass
    
    def eggs(arg): pass
    eggs = spam(eggs)
    

    So, what happens when the decorator takes parameters? Same thing:

    @spam(arg2)
    def eggs(arg): pass
    
    def eggs(arg): pass
    eggs = spam(arg2)(eggs)
    

    Now, notice that the function _set_ev_cls_dec, which is ultimately returned and used in place of _switch_features_handler, is a local function, defined inside the decorator. That means it can be a closure over variables from the outer function—including the parameters of the outer function. So, it can use the handler argument at call time, plus the ev_cls and dispatchers arguments that it got at decoration time.

    So:

    • set_ev_cls_dev creates a local function and returns a closure around its ev_cls and dispatchers arguments, and returns that function.
    • That closure gets called with _switch_features_handler as its parameter, and it modifies and returns that parameter by adding a callers attribute, which is a dict of _Caller objects built from that closed-over dispatchers parameter and keyed off that closed-over ev_cls parameter.