Zelphir Kaltstahl
Zelphir Kaltstahl

Reputation: 6189

Difference between a decorator using functools and one without?

I have the following two decorators:

Decorator 1:

import functools

def overrides(interface_class):
    print('interface_class:', interface_class.__name__)

    def decorator(method):
        print('method:', method.__name__)

        @functools.wraps(method)
        def func(*args, **kwargs):
            assert (method.__name__ in dir(interface_class))
            return method

        return func

    return decorator

Decorator 2:

def overrides(interface_class):

    def overrider(method):
        print('Method name:', method.__name__)
        assert(method.__name__ in dir(interface_class))
        return method

    return overrider

For decorator 1 I rewrote an example, which is using functools. Decorator 2 I found as a working example on Stackoverflow. Decorator 1 is not executing the assert statement, while decorator 2 does, whenever I run the containing program. What is the difference between these two, which causes this?

EDIT#1:

Following the answer of this question, the first comment and another example, I've rewritten it and it works now:

def overrides(interface_class):
    def my_decorator(method):
        assert (method.__name__ in dir(interface_class)), \
            'Trying to override a method named ' + \
            method.__name__ + \
            ' in the interface ' + \
            interface_class.__name__ + \
            ', which does not exist in the interface. Is this a method naming issue?'

        @functools.wraps(method)
        def wrapped(*args, **kwargs):
            return method(*args, **kwargs)

        return wrapped

    return my_decorator

I'll try to explain the placement of the assert statement in my own words: Since the assert statement is not part of the "new functionality" of the method (in fact there is no new functionality, but only assertions about it), it does not belong inside of the function func, but outside of it. It'll only be executed once, when the method is decorated, which will happen, when the program is started, because that's the time, when function decorators are applied, while it would be executed everytime the decorated function is called, if it was within that decorated function. Running the assertion everytime is not desirable, because of its nature.

– Is there still any mistake in this explanation?

Upvotes: 1

Views: 108

Answers (1)

Blckknght
Blckknght

Reputation: 104712

Your first decorator attempts to do the check that the method is part of the interface when the method is called. The second version does the check when the decorator is run (at method definition time).

There's an error in your first version, which may be preventing you from seeing it work. The inner most function is returning method, when it should be calling it and returning the result: return method(*args, **kwargs)

The functools.wraps function is useful in the first decorator because you're replacing the original method with a wrapper. The wraps decorator makes it so the name and docstring of the wrapper function match those of the wrapped function (so that things like help work as expected). The second decorator doesn't replace the method at all (the decorator returns it unchanged if the assertion is not hit), so there's no wrapper function that needs functools.wraps's help.

Upvotes: 1

Related Questions