python-varname icon indicating copy to clipboard operation
python-varname copied to clipboard

functions will always return the first argument

Open HeinrichAD opened this issue 1 year ago • 6 comments

While looking for a fix for issue #114 I got the problem that only the first argument is evaluated. In other words inside a function it is always the first argument which will be returned.

Reproduce

from utils import nameof

def fn(a, b):
    # nameof returns here always the first argument of function fn.
    # Instead of resolving the argument b it is resolving argument a here.
    print(nameof(b, frame=2))
    # ...

x = 5
y = 42
fn(x, y)  # Output expected: 'y', actual: 'x'

Problem

It is quite difficult to identify the right point of failure, since there are multiple ways to approach this issue. I also do not know at the moment how this issue should be fixed. But the final moment where we get the wrong return is line 340:

https://github.com/pwwang/python-varname/blob/1e07fcecef2d15c6802b76fe45bf9230a77d4832/varname/core.py#L333-L340

out contains all variables ('x', 'y') from the function fn (which should IMHO not be the case) and returns the first one since nameof is called with a single argument (no *more_vars).

Workaround

If possible, adjust the argument order.

HeinrichAD avatar Sep 16 '24 08:09 HeinrichAD

Well, I have been thinking of deprecating nameof. It's actually misleading. What you actually should use here, is argname.

In [1]: from varname import argname
   ...: 
   ...: def fn(a, b):
   ...:     print(argname("b"))
   ...: 
   ...: x = 5
   ...: y = 42
   ...: fn(x, y) 
y

pwwang avatar Sep 17 '24 15:09 pwwang

Well, I am fine with using argname. Unfortunally, I run into issus using argname as well.

Reproduce

from varname import argname

def fn(*, b=1):
    print(argname("b"))

a = 42
fn(b=a)  # Error
Stacktrace
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.values())
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  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 3138, in _bind
    raise TypeError(
TypeError: too many positional arguments

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 9, in <module>
    fn(b=a)
  File "/home/hein_f0/dev/slki-code/test.py", line 5, in fn
    print(argname("b"))
          ^^^^^^^^^^^^
  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`?

HeinrichAD avatar Sep 19 '24 16:09 HeinrichAD

@HeinrichAD I can't reproduce it with python3.12:

15:24:01 ❯ ipython  
Python 3.12.2 | packaged by conda-forge | (main, Feb 16 2024, 20:50:58) [GCC 12.3.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.26.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from varname import argname
   ...: 
   ...: def fn(*, b=1):
   ...:     print(argname("b"))
   ...: 
   ...: a = 42
   ...: fn(b=a)
a

Could you attach the environment you used (e.g. python version and varname version)?

pwwang avatar Sep 24 '24 22:09 pwwang

I do not know why, but it differs between python and ipython. I used Python 3.10, but I also tested Python 3.11 and 3.12.

What I did:

virtualenv -p $(which python3.10) .venv
. .venv/bin/activate
pip install -U varname
python
>>> from varname import argname
>>> def fn(*, b=1):
...     print(argname("b"))
...
>>> a = 42
>>> fn(b=a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
  File "/home/<user>/dev/test/python-varname-tests/.venv/lib/python3.10/site-packages/varname/core.py", line 451, in argname
    raise VarnameRetrievingError(
varname.utils.VarnameRetrievingError: Cannot retrieve the node where the function is called.

If I do the some command with ipython, it is working:

pip install -U ipython
ipython
In [1]: from varname import argname
In [2]: def fn(*, b=1):
   ...:     print(argname("b"))
   ...:
In [3]: a = 42
In [4]: fn(b=a)
a

The problem is that if I run that in a normal script, I run it with python and not ipython.

HeinrichAD avatar Sep 26 '24 11:09 HeinrichAD

See #107

pwwang avatar Sep 26 '24 15:09 pwwang

Ok I see. Thank you. I will think about it, check it and come back to you later. (Most likely just to close this issue.)

HeinrichAD avatar Sep 27 '24 07:09 HeinrichAD

Since nameof is already deprecated, I am closing this issue.

HeinrichAD avatar Dec 05 '24 08:12 HeinrichAD