pwwang / python-varname

Dark magics about variable names in python
MIT License
314 stars 22 forks source link

functions with kwargs #114

Open HeinrichAD opened 1 week ago

HeinrichAD commented 1 week ago

The signature binding fails if the function contains kwarg_sources.

Reproduce

from varname import nameof

def fn(var, **kwargs):
    print(nameof(var, frame=2))
    # ...

x = 5
fn(x)       # OK
fn(x, y=6)  # Error
Traceback ```txt Traceback (most recent call last): File "/home/hein_f0/dev/slki-code/.venv/lib/python3.11/site-packages/varname/core.py", line 469, in argname argument_sources = get_argument_sources( ^^^^^^^^^^^^^^^^^^^^^ File "/home/hein_f0/dev/slki-code/.venv/lib/python3.11/site-packages/varname/utils.py", line 442, in get_argument_sources bound_args = signature.bind_partial(*arg_sources, **kwarg_sources) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/inspect.py", line 3219, in bind_partial return self._bind(args, kwargs, partial=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/inspect.py", line 3201, in _bind raise TypeError( TypeError: got an unexpected keyword argument 'y' The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/home/hein_f0/dev/slki-code/test.py", line 11, in fn(x, y=6) # Error ^^^^^^^^^^ File "/home/hein_f0/dev/slki-code/test.py", line 5, in fn print(nameof(var, frame=2)) ^^^^^^^^^^^^^^^^^^^^ File "/home/hein_f0/dev/slki-code/.venv/lib/python3.11/site-packages/varname/core.py", line 333, in nameof out = argname( ^^^^^^^^ File "/home/hein_f0/dev/slki-code/.venv/lib/python3.11/site-packages/varname/core.py", line 476, in argname raise ImproperUseError( varname.utils.ImproperUseError: Have you specified the right `frame` or `func`? ```

Problem

If I am not mistaken, the issue lies in line 442:

https://github.com/pwwang/python-varname/blob/1e07fcecef2d15c6802b76fe45bf9230a77d4832/varname/utils.py#L431-L442

The signature here is the nameof function: <Signature (var: Any, *more_vars: Any, frame: int = 1, vars_only: bool = True) -> Union[str, Tuple[str, ...]]> and therefore does not take any kwarg_sources from the source function fn.

Workaround

None, as far as I know.

pwwang commented 3 days ago

Would you mind submitting a PR?

HeinrichAD commented 3 days ago

To be honest, I do not know how much the following fix would destroy, but a possible fix could be:

  # <Signature (a, b, c, d=4)>
  signature = inspect.signature(func, follow_wrapped=False)
  # func(y, x, c=z)
  # ['y', 'x'], {'c': 'z'}
  arg_sources = [
      argnode_source(source, argnode, vars_only) for argnode in node.args
  ]
  kwarg_sources = {
      argnode.arg: argnode_source(source, argnode.value, vars_only)
      for argnode in node.keywords
      if argnode.arg is not None
  }
- bound_args = signature.bind_partial(*arg_sources, **kwarg_sources)
+ bound_args = signature.bind_partial(*arg_sources, *kwarg_sources.values())
  argument_sources = bound_args.arguments

An alternative (quick and dirty) fix, which ignores all kwargs, would be to adjust the final signature of nameof (and others):

  def nameof(
      var: Any,
      *more_vars: Any,
      frame: int = 1,
      vars_only: bool = True,
+   **kwargs,
  ) -> Union[str, Tuple[str, ...]]:

I could look further into it if issue #115 is solved.