i2mint / i2

Python Mint creation, manipulation, and use
Apache License 2.0
1 stars 1 forks source link

An alternative way of creating a function from a signature #34

Closed thorwhalen closed 2 years ago

thorwhalen commented 2 years ago

mk_func_from_params is a practical testing tool: It makes functions for a given signatures.

For instance, we can use it to see if a function's call matches it's signature...

>>> from i2.tests.util import mk_func_from_params
>>> f = mk_func_from_params('(x, /, y=2)')
>>> f(x=1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/var/folders/j5/brqb67791slb1f04g53vcb780000gp/T/ipykernel_5390/2789613873.py in <module>
      1 f = mk_func_from_params('(x, /, y=2)')
----> 2 f(x=1)

~/Dropbox/dev/p3/proj/i/i2/i2/tests/util.py in arg_str_func(*args, **kwargs)
    209     @sig
    210     def arg_str_func(*args, **kwargs):
--> 211         _call_kwargs = sig.kwargs_from_args_and_kwargs(
    212             args, kwargs, apply_defaults=True
    213         )

~/Dropbox/dev/p3/proj/i/i2/i2/signatures.py in kwargs_from_args_and_kwargs(self, args, kwargs, apply_defaults, allow_partial, allow_excess, ignore_kind, debug)
   2263             args = args[:max_allowed_num_of_posisional_args]
   2264 
-> 2265         b = binder(*args, **sig_relevant_kwargs)
   2266         if apply_defaults:
   2267             b.apply_defaults()

~/.pyenv/versions/3.8.6/lib/python3.8/inspect.py in bind(self, *args, **kwargs)
   3023         if the passed arguments can not be bound.
   3024         """
-> 3025         return self._bind(args, kwargs)
   3026 
   3027     def bind_partial(self, /, *args, **kwargs):

~/.pyenv/versions/3.8.6/lib/python3.8/inspect.py in _bind(self, args, kwargs, partial)
   2919                                   'but was passed as a keyword'
   2920                             msg = msg.format(arg=param.name)
-> 2921                             raise TypeError(msg) from None
   2922                         parameters_ex = (param,)
   2923                         break

TypeError: 'x' parameter is positional only, but was passed as a keyword

The message is helpful, but different from what a "normal function" (one defined explicitly) would give us:

>>> def f(x, /, y=2):
...          return x + y
... 
>>> f(x=1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/var/folders/j5/brqb67791slb1f04g53vcb780000gp/T/ipykernel_5390/1830888932.py in <module>
      2     return x + y
      3 
----> 4 f(x=1)

TypeError: f() got some positional-only arguments passed as keyword arguments: 'x'

We'd like a similar function to mk_func_from_params but where the function is created by exec, allowing python to see the function more or less as a "real" function, and amongst other things, give us the same errors for the same situations:

>>> f = sig_to_func('(x, /, y=2)')
>>> f(x=1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/var/folders/j5/brqb67791slb1f04g53vcb780000gp/T/ipykernel_5390/262668693.py in <module>
      1 f = sig_to_func('(x, /, y=2)')
----> 2 f(x=1)

TypeError: f01() got some positional-only arguments passed as keyword arguments: 'x'