Adamsan
Adamsan

Reputation: 662

Can python decorators be used to change behavior of a function returned by a function?

I have functions, that return validator functions, simple example:

def check_len(n):
    return lambda s: len(s) == n

Is it possible to add a decorator, that prints out a message, in case the check evaluates to false? Something like this:

@log_false_but_how
def check_len(n):
    return lambda s: len(s) == n

check_one = check_len(1)
print(check_one('a')) # returns True
print(check_one('abc')) # return False

Expected output:

True
validator evaluated to False
False

I've tried creating an annotation, but can only access the function creation with it. One way would be to define the functions like this:

def log_false(fn):
    def inner(*args):
        res = fn(*args)
        if not res:
            print("validation failed for {}".format(fn.__name__))
        return res
    return inner


@log_false
def check_one(s):
    return check_len(1)(s)

But this way we lose the dynamic creation of validation functions.

Upvotes: 4

Views: 209

Answers (1)

Aran-Fey
Aran-Fey

Reputation: 43246

You're doing the validation in the wrong place. check_len is a function factory, so res is not a boolean - it's a function. Your @log_false decorator has to wrap a validator function around each lambda returned by check_len. Basically you need to write a decorator that decorates the returned functions.

def log_false(validator_factory):
    # We'll create a wrapper for the validator_factory
    # that applies a decorator to each function returned
    # by the factory

    def check_result(validator):
        @functools.wraps(validator)
        def wrapper(*args, **kwargs):
            result = validator(*args, **kwargs)
            if not result:
                name = validator_factory.__name__
                print('validation failed for {}'.format(name))
            return result
        return wrapper

    @functools.wraps(validator_factory)
    def wrapper(*args, **kwargs):
        validator = validator_factory(*args, **kwargs)
        return check_result(validator)
    return wrapper

Result:

@log_false
def check_len(n):
    return lambda s: len(s) == n

check_one = check_len(1)
print(check_one('a')) # prints nothing
print(check_one('abc')) # prints "validation failed for check_len"

Upvotes: 3

Related Questions