Skip to content

Using a NotSet sentinel to solve make FuncFactory instances' signature more truthful about argument requirements. #48

@thorwhalen

Description

@thorwhalen

There is something with FuncFactory that might be unintuitive to the novice here:
that a FuncFactory can be called with anything between none to all of the
parameters given by it's signature, regardless of whether the argument appears to
be required (because the signature shows it as not having a default).

from i2 import FuncFactory

def foo(x, y, z=2):
    return x + y * z

f = FuncFactory(foo)
Sig(f)
# <Sig (x, y, z=2)>

We see that x and y are required arguments of f, and yet, I can call f like this:

t = f()
tt = f(x=1)
ttt = f(y=2)

What's unintuitive is that most callables can only be called if all their required arguments
(i.e. those that don't have defaults) are given.
But in the case of a FuncFactory instance it is not so because it is meant to
be a factory where any thing between all and none of the arguments of the
wrapped function can be given (having the effect of making a version of the
wrapped function where that subset of specified argument have be given default
values).

One solution maybe to all non-defaulted values show up (in signature) as NotSet sentinel
so that these arguments don't appear to be "required".

This signature would just appear a FuncFactory instance, but not in the partial object it creates when called.

Related issue: rm_params with allow_removal_of_non_defaulted_params doesn't exactly work

Proposal code:

In the init of FuncFactory:

...
      shown_factory_sig = actual_factory_sig.ch_defaults(
          **{name: NotSet for name in actual_factory_sig.required_names}
      )
      shown_factory_sig = shown_factory_sig[self.include]
      self.__signature__ = shown_factory_sig
...

and in _process_args_and_kwargs:

    def _process_args_and_kwargs(self, args, kwargs):
        _kwargs = self.factory_sig.kwargs_from_args_and_kwargs(
            args, kwargs, allow_partial=True, ignore_kind=True
        )
        _kwargs = {k: v for k, v in _kwargs.items() if v is not NotSet}  # <-- New part!
        __args, __kwargs = self.func_sig.args_and_kwargs_from_kwargs(
            _kwargs, allow_partial=True, ignore_kind=False
        )
        return __args, __kwargs

where

from i2 import mk_sentinel

def _not_set_repr(self):
    return 'NotSet'

NotSet = mk_sentinel('NotSet', repr_=_not_set_repr)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions