Last active
March 12, 2025 08:35
-
-
Save asher-pembroke/164705466cfc5a6a3985e18736a4b339 to your computer and use it in GitHub Desktop.
A partial function decorator that doesn't suck (python3.7)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # pip install decorator | |
| # pip install python-forge | |
| from decorator import decorator, decorate | |
| import forge | |
| import inspect | |
| def decorator_wrapper(f, *args, **kwargs): | |
| """Wrapper needed by decorator.decorate to pass through args, kwargs""" | |
| return f(*args, **kwargs) | |
| def get_defaults(func): | |
| sig = inspect.signature(func) | |
| defaults = {} | |
| for k, v in sig.parameters.items(): | |
| if v.default is not inspect._empty: | |
| defaults[k] = v.default | |
| return defaults | |
| def get_args(func): | |
| sig = inspect.signature(func) | |
| return tuple(sig.parameters.keys()) | |
| def partial(_func=None, **partial_kwargs): | |
| """A partial function decorator | |
| Reduces function signature to reflect partially assigned kwargs | |
| """ | |
| verbose = partial_kwargs.pop('verbose', False) | |
| def decorator_partial(f): | |
| orig_args = get_args(f) | |
| orig_defaults = get_defaults(f) | |
| orig_defaults.update(partial_kwargs) | |
| if verbose: | |
| print('partial kwargs', partial_kwargs) | |
| print('original args:', orig_args) | |
| print('new defaults', orig_defaults) | |
| # collect only the arguments not assigned by partial | |
| sig_defaults = {} | |
| sig_args = [] | |
| for arg in orig_args: | |
| if arg in partial_kwargs: | |
| continue | |
| if arg in orig_defaults: | |
| sig_defaults[arg] = orig_defaults[arg] | |
| else: | |
| sig_args.append(arg) | |
| if verbose: | |
| print('updated signature:', sig_args, sig_defaults) | |
| @forge.sign(*construct_signature(*sig_args, **sig_defaults)) | |
| def wrapped(*args, **kwargs): | |
| """simple wrapper""" | |
| kwargs.update(partial_kwargs) | |
| if verbose: | |
| print('kwargs to pass:', kwargs) | |
| return f(*args, **kwargs) | |
| if verbose: | |
| print('wrapped docs', wrapped.__doc__) | |
| wrapped = decorate(wrapped, decorator_wrapper) | |
| wrapped.__name__ = f.__name__ | |
| orig_docs = f.__doc__ | |
| for k, v in sig_defaults.items(): | |
| sig_args.append('{}={}'.format(k, v)) | |
| doc_args = ', '.join(sig_args) | |
| rhs_args = [] | |
| for arg in orig_args: | |
| if arg in partial_kwargs: | |
| rhs_args.append('{}={}'.format(arg, partial_kwargs[arg])) | |
| else: | |
| rhs_args.append(arg) | |
| doc_orig_args = ', '.join(rhs_args) | |
| wrapped.__doc__ = """Calling {fname}({orig_args}) for fixed {partial_keys}: | |
| {fname}({doc_args}) = {fname}({orig_args_fixed})\n""".format( | |
| orig_args_fixed=doc_orig_args, | |
| doc_args=doc_args, | |
| fname=f.__name__, | |
| orig_args = ', '.join(orig_args), | |
| partial_keys=', '.join(partial_kwargs.keys()), | |
| ) | |
| if orig_docs is not None: | |
| wrapped.__doc__ += "\n" + f.__doc__ | |
| return wrapped | |
| if _func is None: | |
| return decorator_partial | |
| else: | |
| return decorator_partial(_func) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This partial decorator reduces a function's signature by the number of arguments specified. This is more in keeping with the expected behavior of a partial function, as opposed to
functools.partialwhich just changes the defaults.('x', 'y')@partialwill update your function docs: