Skip to content

Instantly share code, notes, and snippets.

@asher-pembroke
Last active March 12, 2025 08:35
Show Gist options
  • Select an option

  • Save asher-pembroke/164705466cfc5a6a3985e18736a4b339 to your computer and use it in GitHub Desktop.

Select an option

Save asher-pembroke/164705466cfc5a6a3985e18736a4b339 to your computer and use it in GitHub Desktop.
A partial function decorator that doesn't suck (python3.7)
# 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)
@asher-pembroke
Copy link
Author

asher-pembroke commented Jun 18, 2021

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.partial which just changes the defaults.

@partial(z=3)
def myfunc(x,y,z):
    """myfunc does this"""
    return x+y+zassert myfunc(1,2) == 1+2+3

print(get_args(myfunc))
('x', 'y')

@partial will update your function docs:

help(myfunc)
Calling myfunc(x, y, z) for fixed z:
        myfunc(x, y) = myfunc(x, y, z=3)

myfunc does this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment