Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
255 views
in Technique[技术] by (71.8m points)

python - Decorator for partial application /and/or/ currying: How to determine if mandatory arguments are supplied?

I've been tinkering with decorators lately and (as an academic exercise) tried to implement a decorator that allows for partial application and/or currying of the decorated function. Furthermore this decorator should be optionally parameterizable and take a kwarg asap which determines if the decorated function should return as soon as all mandatory args/kwargs are aquired (default: asap=True) or if the decoratored function should keep caching args/kwargs until the function is called without arguments (asap=False).

Here is the decorator I came up with:

def partialcurry(_f=None, *, asap: bool=True):
    """ Decorator; optionally parameterizable; Allows partial application /and/or/ currying of the decorated function F. Decorated F fires as soon as all mandatory args and kwargs are supplied, or, if ASAP=False, collects args and kwargs and fires only if F is called without args/kwargs. """

    def _decor(f, *args, **kwargs):

        _all_args, _all_kwargs = list(args), kwargs

        @functools.wraps(f)
        def _wrapper(*more_args, **more_kwargs):

            nonlocal _all_args, _all_kwargs # needed for resetting, not mutating
            _all_args.extend(more_args)
            _all_kwargs.update(more_kwargs)

            if asap:
                try:
                    result = f(*_all_args, **_all_kwargs)
                    # reset closured args/kwargs caches
                    _all_args, _all_kwargs = list(), dict()
                except TypeError:
                    result = _wrapper
                return result

            elif not asap:
                if more_args or more_kwargs:
                    return _wrapper
                else:
                    result = f(*_all_args, **_all_kwargs)
                    # again, reset closured args/kwargs caches
                    _all_args, _all_kwargs = list(), dict()
                    return result

        return _wrapper

    if _f is None:
        return _decor
    return _decor(_f)


### examples
@partialcurry
def fun(x, y, z=3):
    return x, y, z

print(fun(1))     # preloaded function object
print(fun(1, 2))  # all mandatory args supplied; (1,1,2); reset
print(fun(1)(2))  # all mandatory args supplied; (1,2,3); reset

print()

@partialcurry(asap=False)
def fun2(x, y, z=3):
    return x, y, z

print(fun2(1)(2, 3))  # all mandatory args supplied; preloaded function object
print(fun2())         # fire + reset
print(fun2(1)(2))     # all mandatory args supplied; preloaded function object
print(fun2(4)())      # load one more and fire + reset

I am sure that this can be generally improved (implementing this as a class would be a good idea for example) and any suggestions are much appreciated, my main question however is how to determine if all mandatory args/kwargs are supplied, because I feel like to check for a TypeError is too generic and could catch all kinds of TypeErrors. One idea would be to define a helper function that calculates the number of mandatory arguments, maybe something like this:

def _required_args_cnt(f):
    """ Auxiliary function: Calculate the number of /required/ args of a function F. """
    
    all_args_cnt = f.__code__.co_argcount + f.__code__.co_kwonlyargcount
    def_args_cnt = len(f.__defaults__) if f.__defaults__ else 0
    return all_args_cnt - def_args_cnt

Obviously unsatisfactory..

Any suggestions are much appreciated!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
等待大神答复

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...