Question

Based on this thread , I have the following

import functools
def predicate(author, version, **others):
    def _predicate(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            func.meta = {
                'author':  author,
                'version': version
            }
            func.meta.update(others)
            func(*args, **kwargs)
        return wrapper
    return _predicate

but I am unable to get the simple use case of

@predicate('some author', 'some version')
def second_of_two(a, b):
    return b

to work as expected:

>>> second_of_two.meta['author']
'some author'
>>> second_of_two(1, 2)
2

What am I doing wrong here?

Was it helpful?

Solution 2

You are doing multiple things incorrectly, so let’s see go through it:

In your code, predicate is a generator for a decorator; the actual decorator is _predicate. Decorators take a function and return a function; the latter will be assigned where the original code was added. So with your @predicate(…) def second_of_two, the name second_of_two will get the value that the decorator returns.

So let’s see what your decorator does:

def _predicate(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        …
    return wrapper

It builds a new internal function, uses functool.wraps (which is good practice), and then returns that function. So far so good; so the second_of_two gets the decorated function correctly.

Now, what does the wrapper function do though?

def wrapper(*args, **kwargs):
    func.meta = {
        'author':  author,
        'version': version
    }
    func.meta.update(others)
    func(*args, **kwargs)

The wrapper function sets those meta data on the original function object. That’s the first mistake; you set the meta data on the original function which later isn’t referred to again. second_of_two is the wrapper function, so it doesn’t have those meta functions. In addition, you only set those meta data when the function runs. So if you set the meta data correctly on the wrapper function, then it would still be only available once you have called it once. And the third mistake is that you call the original function and simply throw away its return value. That’s why you didn’t get any output.

So what should you do instead? First of all, set the meta data outside of the wrapper function, and set it on the function you return. And inside the wrapper function, return the original function’s return value:

def predicate(author, version, **others):
    def _predicate(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        wrapper.meta = {
            'author':  author,
            'version': version
        }
        wrapper.meta.update(others)

        return wrapper
    return _predicate

And now, your wrapper function no longer does anything special, so you can just use the original function instead:

def predicate(author, version, **others):
    def _predicate(func):
        func.meta = {
            'author':  author,
            'version': version
        }
        func.meta.update(others)
        return func
    return _predicate

OTHER TIPS

well, I don't see why you're using the functools stuff over here, whereas you need to make it a simple decorator with arguments:

def predicate(author, version, **others):
    def _predicate(func):
        func.meta = {
            'author':  author,
            'version': version
        }
        func.meta.update(others)
        return func
    return _predicate

@predicate('foo', 'bar')
def myfunc(i,j):
    return i+j

print myfunc.meta
print myfunc(1,2)

gives:

{'version': 'bar', 'author': 'foo'}
3
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top